toNDC() function for USD Cameras

   4098   7   4
User Avatar
Member
15 posts
Joined: May 2020
Offline
Hey folks, I'm trying to implement a way to cull out instances outside of the camera frustum, in an efficient way (so with a SOP Modify it would work, but with millions instances, quite heavy). By cull out I mean to append them to the invisibleIds Array. It should be like an Shot override for a full sequence layout, so culling the points at the creation level wouldn't be an option.
For SOPs and normal Houdini land the toNDC() VEX function is perfect - take the @P to the NDC space of the given camera and every point outside of the 0-1 range can be deleted. This function doesn't work (yet) for USD Cams.
Is there a way to see into the toNDC() function, what it's technically doing? I looked into the Houdini folders to maybe find the description of this function but no success.
Then I tried to wrap my head around to manually code a way to get the Position vectors of the instances to the ndc space of the given USD cam. Tried the projection() vex function, other ways to build the projection matrix I found online, tried this in the post https://www.sidefx.com/forum/topic/16953/ [www.sidefx.com] but nothing brings the same result as the toNDC() function.

My dirty solution was to create an obj network, LOPimport the USD cam to normal Houdini land and use that cam for the toNDC function. Not super elegant AND that would only work for static cams (with a moving cam you might see shadows popping in/out of elements outside the cam view). With the usd_attrib functions you can also get time sampled values, so culling instances never visible in the full range would be possible.

Thanks in advance!
User Avatar
Staff
1448 posts
Joined: July 2005
Offline
There was a bug 107793 about `toNDC()` that has been fixed in the development branch, but it's about camera nodes, not USD cameras, so it may or may not work for you in the future releases.

Otherwise, `toNDC()` VEX implementation is a bit scattered, but essentially it obtains the relative transform from objects space (which in case of LOP's is identity) to camera, and then constructs the perspective matrix with HDKs `UT_Matrix4::perspective()` and multiplying them togethr.
User Avatar
Member
4 posts
Joined: Jan. 2014
Offline
Hi,

is this working in houdini19, I haven't been able to get toNdc() working in lops with USD cameras
User Avatar
Member
7771 posts
Joined: Sept. 2011
Offline
Chris Russell
is this working in houdini19, I haven't been able to get toNdc() working in lops with USD cameras

My feature request bug is still open, so I assume no.
User Avatar
Member
240 posts
Joined: Oct. 2014
Offline
The prune LOP lets you deactivate prims or cull points from a pointinstancer using camera bounds. See: https://www.sidefx.com/docs/houdini/solaris/pattern.html [www.sidefx.com]

%bound:/path/to/camera/prim
- Tim Crowson
Technical/CG Supervisor
User Avatar
Member
51 posts
Joined: July 2005
Offline
just tried NDC in a LOPS wrangle, hoping it gets fixed
User Avatar
Member
15 posts
Joined: May 2020
Offline
As this topic is still relevant today, I came up with this VEX Code to replicate the camera culling with the NDC-Method. Drop an Attribute Wrangle and select the PointInstancer and give it a camera. Only tested it on PointInstancers because it doesn't delete outside points, it just appends them to the invisibleIds array of a PointInstancer (non-destructive wooooh!).

int n = usd_attriblen(0, @primpath, "positions"); // make sure to have the PointInstancers selected under "Primitves" so @primpath works

vector pos[] = usd_attrib(0, @primpath, "positions");

string campath = chs("camera"); // select the camera primitive
matrix mcam = usd_worldtransform(0, campath);
float focal = usd_attrib(0, campath, "focalLength");
float hap = usd_attrib(0, campath, "horizontalAperture");
float vap = usd_attrib(0, campath, "verticalAperture");
vector2 clipping = usd_attrib(0, campath, "clippingRange");
float near = clipping.x;
float far = clipping.y;

float o = chf("overscan"); // default could be 0.2

for(int i=0; i<n; i++){
    vector pcam = pos[i] * invert(mcam); // get instance positions into camera space
    float cambackup = o*chf("cam_backup");
    
    pcam.z -= cambackup; // overscan with additional cam -z translation
    
    float xclip = focal * pcam.x / (hap/2);
    float yclip = focal * pcam.y / (vap/2);
    float wclip = pcam.z;
    
    float ndcx = xclip / wclip;
    float ndcy = yclip / wclip;
    
    if(-pcam.z < near || -pcam.z > far-cambackup) append(i[]@invisibleIds, i); // cull outside cam clipping planes
    if(ndcx+o < -1 || ndcx-o > 1 || ndcy+o < -1 || ndcy-o > 1) append(i[]@invisibleIds, i);
} // this creates a NDC-Space between -1 - 1 inside the cam view. toNDC VEX function results in a 0 - 1 range.
Edited by David Krepelka - March 22, 2023 04:39:52
User Avatar
Member
51 posts
Joined: July 2005
Offline
edit: sorry I posted wrong code.
thanks again David
//thanks David Krepelka!!! modified camera culling to flatten_to_CAM_9hole
int n = usd_attriblen(0, @primpath, "points"); // make sure to have the mesh set under "Primitves" so @primpath works
vector pos[] = usd_attrib(0, @primpath, "points");
//string campath = chs("camera"); // select the camera primitive
string campath = ch("`opinputpath(".", 1)`"+/primpath);//pipe camera into second wrangle input (and must be somewhere upstream in first input). maybe fudgy?
matrix mcam = usd_worldtransform(0, campath);
matrix mprim = usd_worldtransform(0, @primpath);
float focal = usd_attrib(0, campath, "focalLength");
float hap = usd_attrib(0, campath, "horizontalAperture");
float vap = usd_attrib(0, campath, "verticalAperture");

for(int i=0; i<n; i++){

    vector pprim = pos[i] * mprim;
    vector pcam = pprim * invert(mcam); // get point positions into camera space
    
    float xcam = (focal * pcam.x / (hap/2))/pcam.z;
    float ycam = (focal * pcam.y / (vap/2)/(hap/vap))/pcam.z;

    pcam = set(-xcam,-ycam,pcam.z);
    
    pcam.z *= 0;
    pcam.z-=1;
    
    //seems really dumb to just reverse above, better way?
    pcam.x= ((-pcam.x*focal)*(hap*2))*pcam.z;
    pcam.y= ((-pcam.y*focal)*(vap*2)*(hap/vap))*pcam.z;
    
    pcam = pcam * mcam;
    pcam = pcam * invert(mprim);
    
    usd_setattribelement(0, @primpath, "points", i, pcam);
}
Edited by GrahamDClark - March 23, 2023 22:30:38
  • Quick Links