Getting the camera to screen matrix

   16384   7   3
User Avatar
Member
789 posts
Joined: April 2020
Offline
Hello all,

What would be the easiest way to get a camera to screen (NDC) matrix? I would love to have a solution in python, the HDK would work as well.

Any help is appreciated.

Koen
User Avatar
Member
941 posts
Joined: July 2005
Offline
koen
What would be the easiest way to get a camera to screen (NDC) matrix? I would love to have a solution in python, the HDK would work as well.

As I understand it, yes, you can represent the NDC transform as a matrix, but to do anything useful with that matrix, you'll need to work in homogeneous coordinates.

For example, to encode the perspective divide portion (P/P.z) in matrix form, you'd have:

// row-major //
matrix M = 1, 0, 0, 0
0, 1, 0, 0
0, 0, 1, 1
0, 0, 0, 0
When this matrix is used to transform a homogeneous point P, its right-hand column will set Pw to Pz, so when you de-homogenize the resulting P (turn P*M back into Cartesian coords), you'll end up with Pcartesian = {Px/Pw,Py/Pw,Pz/Pw} = {Px/Pw,Py/Pw,1}, which is the perspective divide you want. But without that de-homogenization step the last column is meaningless.

For the actual NDC transform though, you'll need more than just the division by Pz – you'll need to bring in the rest of the camera's viewing parameters into play: focal length, aperture, pixel aspect, etc.
The first step however, is to put all your (homogeneous) P's into camera space (initially all the Pw's will most likely be simply 1, but use the actual attribute just in case). And you can get at the camera transform (let's say some matrix ‘Mcam’) in any number of ways, depending on your context, so I'll assume you can do this. So step 1 is: P = P*Mcam.

Then you'll transform by the NDC matrix, which looks something like this:

float f = focal / aperture;
float a = resy / (resx*aspect);
float b = (far+near) / (far-near);
float c = (2.0*far*near) / (far-near);

Mndc = set( f , 0 , 0 , 0,
0 , f/a , 0 , 0,
0 , 0 , b , -1,
0 , 0 , c , 0 );

That ‘-1’ at Mndc(3,4) could be a +1 depending on your context, and the rest of the variables are the camera's viewing parameters: far/near clipping planes, x/y resolution, etc. Mndc has a “normalized” window of , and all resulting Pw's are set to 1 (or -1 in this case). All that's left now is to put P back to cartesian coords. So, all together:

vector4 Ph = set(P.x,P.y,P.z,P.w);
Ph = Ph*Mcam*Mndc;
vector Pndc = (vector)Ph / Ph.w + {0.5,0.5,0}; // Pndc = NDC version of P


I did a quick mockup in SOPs so you can try it out. View through cam1, which also displays the NDC version in the lower-left corner of the viewport. Then play with cam1's transformation and/or viewing parameters to see how the NDC projection changes. You'll find the above ‘Mndc’ matrix written inside the VOP SOP ‘NDC_0_1’ in the object ‘NDC’.

HTH.
Edited by - Sept. 18, 2009 18:00:13

Attachments:
NDC.jpg (79.0 KB)
NDC.hip (111.9 KB)

Mario Marengo
Senior Developer at Folks VFX [folksvfx.com] in Toronto, Canada.
User Avatar
Member
789 posts
Joined: April 2020
Offline
Very cool, thanks a lot, I'll start pickign this setup apart

I was hoping there is a matrix4 cameraToScreen() function that houdini uses internally to feed the ndc transforms , but that would not nearly be as much fun as this is now would it :-)

Cheers,
Koen
User Avatar
Member
941 posts
Joined: July 2005
Offline
koen
I was hoping there is a matrix4 cameraToScreen() function that houdini uses internally to feed the ndc transforms , but that would not nearly be as much fun as this is now would it :-)

Heh. Well, maybe there is, but I can only think of the ‘oXtransform()’ functions in vex, which return a matrix (the transform to some object's space), the rest just apply a transformation to some element but don't return the matrix (ditto for toNDC() and fromNDC()). But maybe there is one and I just haven't noticed it yet.
Mario Marengo
Senior Developer at Folks VFX [folksvfx.com] in Toronto, Canada.
User Avatar
Member
2 posts
Joined: July 2013
Offline
Hi i build a small otl, for this topic.
I hope, this helps someone

https://vimeo.com/119541167 [vimeo.com]

https://dl.dropboxusercontent.com/u/34893475/bhfx_CamUvProject_v001.otl [dl.dropboxusercontent.com]
User Avatar
Member
183 posts
Joined: Nov. 2008
Offline
UP:

My code for getting point position in NDC space was working fine in H13, but not in H15.

UT_Matrix4D inverseCameraMatrix, projectionMatrix;
cameraptr->getInverseLocalToWorldTransform(context, inverseCameraMatrix);
cameraptr->getProjectionMatrix(context, projectionMatrix);
UT_Matrix4D toNdc = inverseCameraMatrix * projectionMatrix;

//FOR EACH POINT
UT_Vector4 P_ndc;
UT_Vector3 P_screen;
GA_Offset ptoffset = it.getOffset();
P_ndc = ph.get(ptoffset) * toNdc;
P_ndc /= P_ndc.w();
P_screen = (UT_Vector3)P_ndc + UT_Vector3(0.5, 0.5, 0.0);
P_screen *= 0.5; // Don't remember why is that here actually;
ph.set(ptoffset, P_screen);

I'm getting very strange point positions in H14/H15.

I'm sure im doing something wrong here.Any ideas?

I've could use method described here [sidefx.com]
But if HDK provides getProjectionMatrix() method, why would i build one myself.
Aleksei Rusev
Sr. Graphics Tools Engineer @ Nvidia
User Avatar
Staff
1448 posts
Joined: July 2005
Offline
This has come up again recently, so just a note that getProjectionMatrix() returns a different but still valid perspective matrix. It just uses different conventions than usual unit cube centered at the origin. This may explain the strange point positions in the post above.
User Avatar
Member
50 posts
Joined: July 2005
Offline
Mario Marengo
As I understand it, yes, you can represent the NDC transform as a matrix, but to do anything useful with that matrix, you'll need to work in homogeneous coordinates.
Thank you I was killing myself to get x y in camera in python in Houdini and chatbot gpt-4 wasn't saving me until I gave it your method!
def calculate_ndc(point, focal, aperture, resx, resy, aspect, near, far, Mcam):
    # Convert point to homogeneous coordinates
    point_h = np.array([point[0], point[1], point[2], 1])

    # Define the NDC matrix
    f = focal / aperture
    a = resy / (resx * aspect)
    b = (far + near) / (far - near)
    c = (2.0 * far * near) / (far - near)

    Mndc = np.array([[f, 0, 0, 0],
                     [0, f / a, 0, 0],
                     [0, 0, b, -1],
                     [0, 0, c, 0]])

    # Transform the point to camera space and then to NDC space
    point_h = point_h.dot(Mcam).dot(Mndc)

    # Convert back to Cartesian coordinates
    Pndc = np.array([point_h[0] / point_h[3], point_h[1] / point_h[3], point_h[2] / point_h[3]])

    # Add 0.5 to x and y to get the final NDC coordinates
    Pndc[0] += 0.5
    Pndc[1] += 0.5

    return Pndc
Edited by GrahamDClark - April 2, 2023 19:16:44
  • Quick Links