Pscale by the camera Link Icon

I've described how we can set a unified pscale by the camera in SOPs here which uses this simple algebraic approach: $$ \text{Pixel Size} = \left(\frac{\text{Aperture}}{\text{Focal Length}}\right) \times \left(\frac{\text{Distance}}{\text{Resolution}}\right) $$ However, if we want to set up the same behavior in LOPs, you will notice that getting distance (Pz) by NDC or UV might not be straightforward, as it would require jumping back into the SOP/OBJ context.

As an alternative, we can calculate it by projecting the vector from the camera to the point onto the camera Z. You can also read more about vector projections here

VEXpression
string campath = chs("camera");
float apertH = usd_attrib(0, campath, "horizontalAperture");
float apertV = usd_attrib(0, campath, "verticalAperture");
float focal = usd_attrib(0, campath, "focalLength");
float resx = chf("render_resolution_x");
float pixsize = apertH / focal ;

// get camera position
matrix camM = usd_worldtransform(0, campath);
vector camPos = set(camM.wx, camM.wy, camM.wz);

// vector from cam to point
vector camPtoP = v@points-camPos;
// camera direction vector
vector camDir = -set(camM.zx, camM.zy, camM.zz)/100;
// projection distance and direction
float dotC = max(0, dot(camPtoP, normalize(camDir)));
// projected vector
vector ProjZ = dotC*normalize(camDir);
float Pz = length(ProjZ);

// pscale size of pixel (based on camera RESOLUTION)
f@widths = pixsize * (Pz/resx);

// adjust unified pscale by user input
f@widths *= ch("pscale_multi");

lop-camera-pscale-1

Frustum culling in LOPS Link Icon

Frustum culling in LOPs has been a topic since the beginning of the Solaris context. As always, there are many ways to skin a cat, especially when it comes to Houdini. Ideally, we want to keep everything within the LOP context without creating SOP context nodes for intermediate computations, as that could drastically slow our workflows down.

SideFX gave us amazing primitive matching patterns like bound, where we can specify elements that are within the camera frustum:

VEXpression
%bound(/cameras/camera1, fovscale = 1.1)

lop-frustum-cull-1
But what about the geometry points or the instance points ?. toNDC() function works great with OBJ cameras but it won't accept LOP cameras. We can, of course, import camera into OBJ level and reference it that way:
lop-frustum-cull-2

If we want to keep everything within the LOP context, one way around is to calculate NDC ourselves.

VEXpression
vector toLopNDC(string primpath; string camera; vector position; float frame){
    float focalLength = usd_attrib(0, camera, "focalLength", frame);
    float horizontalAperture = usd_attrib(0, camera, "horizontalAperture",frame);
    float verticalAperture = usd_attrib(0, camera, "verticalAperture", frame);
    float screenWindowWidth = tan(horizontalAperture / (2 * focalLength)) * 2;
    float screenWindowHeight = screenWindowWidth * verticalAperture / horizontalAperture;
    vector pos_world    = position * usd_worldtransform(0, primpath, frame);
    vector pos_cam      = pos_world * invert(usd_worldtransform(0, camera, frame));
    vector posCamNorm = set(pos_cam.x/pos_cam.z, pos_cam.y/pos_cam.z, -pos_cam.z);
    vector posNDC = set(
        0.5 - (posCamNorm.x / screenWindowWidth),
        0.5 - (posCamNorm.y / screenWindowHeight),
        posCamNorm.z
    );
    return posNDC;
}

vector ndc = toLopNDC(@primpath, chs("camera"), v@positions, @Frame);
float overscan = 0.1;
if(ndc.x>=0-overscan && ndc.x <=1+overscan)
    v@scales *= 0.3;

lop-frustum-cull-3

Decimate curves Link Icon

You've got lots of curves in the LOP context and want to decimate without going back to SOPs? You are in luck!, USD stores 'curveVertexCounts' array with number of points per each primitive. We can modify this array by removing unwanted primitives

lop-decimate-curve

VEXpression
int nums = usd_attriblen(0, @primpath, "curveVertexCounts");
int curve_vtx_num[] = usd_attrib(0, @primpath, "curveVertexCounts");
i[]@curveVertexCounts = {};
for (int i=0; i<nums; ++i){
    if (rand(i+ch("seed")) < ch("prune_ammount"))
        push(i[]@curveVertexCounts, curve_vtx_num[i]);
}