Camera culling instanced scatter

The problem

Download Sample Scene

There was one more problem that appeared interesting on the subject of camera culling. Let’s say you have a scattered point cloud and you have instanced some geometry to these points. How do you remove all the points that are not visible in the scatter? In other words, how to group the points that are occluded when viewing the scatter and the geometry instanced to the points?

If this was all full baked geometry, this setup would do the trick: Group primitives visible to camera (VEX). Or if you just need to cull out points that are not visible in the camera frame, this will do Group points inside camera view (VEX). But in this case I wanted to focus on a scattered point cloud with geometry instanced to the points. Rocks, debris, etc.

I’m not so sure if this setup will be useful or if there is a production use case for it. There’s probably many reasons why you would want to keep the full density of the point scatter (shadows, etc.), but once the idea popped into my head I had to figure it out. In this post I’ll describe my process. The script itself with instructions is at the end of this page.

Hypothetical scatter setup.

The scattered points have a random pscale attribute. There is a primitive sphere copied to each point. Which points will be occluded while viewing from the camera?

The point scales in my illustrations are exaggerated to make room for notations.

Since this example works with spheres, it will be an approximation for more complex geometry instances. For the optimal result, instead of using the pscale attribute, the setup would need an attribute on each point marking the furthest distance from the point to the instanced geometry surface. In this case, I thought it’s close enough to use spheres and add a padding value to add a bit of threshold. If needed, this setup can of course be developed further.

Solving the problem

I started thinking about the problem with sort of a ray tracing approach in mind. My initial thought was to calculate a direction vector pointing towards each point from the camera view, then iterate over all the points and compare the iterated point’s direction vector to all of the other points’ direction vectors. If the direction vectors are pointing roughly in the same direction, the points are roughly in the same line with each other. Then I could just check if which points in this “directional group” are behind the iterated point comparing the points’ distances to the camera. As a very basic setup this would work if all the points had the same pscale attribute. You would still need to dial in a threshold value for the amount of difference allowed between the direction vectors. That’s not good enough so the solution will be a bit more complicated.

Firstly, the threshold of the allowed difference should be dynamic for each point since the perspective makes the points appear larger or smaller. Secondly, the other points compared to the iterated point can be visible even if they are behind the iterated point, since they all have a random pscale attribute.

Even though the points b and c are not visible to the camera, the instanced geometry to these points will be visible behind point a. The only fully occluded point in this illustration is point d.

From the camera view this could look something like this. Since the point d is fully occluded by the point a, it can be removed.

I tried to expand the idea of direction vectors to calculate whether the points will be “peaking out“ from behind the iterated point but it soon got very complicated. I’m sure there was a solution behind that idea but I just couldn’t figure it out with my current mathematics skills. I shifted my focus into working with angles and it turned out to be way more simple.

Example:

If \(\theta + \varphi > \alpha\), the point behind is visible to the camera.

if \(\theta + \varphi < \alpha\), the point behind is hidden from the camera.

The formula for the angle \(\alpha\) is:

$$ \alpha = \sin\biggr({r_1 \over d_1}\biggl) $$

Where:

  • \(r_1\) = iterated point radius \(pscale\)

  • \(d_1\) = distance from the camera to the iterated point position

The formula for the angle \(\varphi\) is:

$$ \varphi = \sin\biggr({r_2 \over d_2}\biggl) $$

Where:

  • \(r_2\) = compared point radius \(pscale\)

  • \(d_2\) = distance from the camera to the compared point position

The formula for the angle \(\theta\) is:

$$ \theta = \cos^{-1}\biggr({(P_1 - P_c)\bullet(P_2 - P_c)\over\vert(P_1 - P_c)\vert \vert(P_2 - P_c)\vert}\biggl) $$

Where:

  • \(P_1\) = iterated point position

  • \(P_2\) = compared point position

  • \(P_c\) = camera position

Then the only thing left is to measure the distances from the camera to the points and check which ones are behind the iterated point.

Final script

Run the script in a point wrangle over the scattered points you want to reduce. The points need to have a pscale attribute. Create the parameters with the “create spare parameters“ button and select the camera in the scene with the parameter “id“. The script will output a group “occluded“.

//path to camera
string op_path = chsop("id");

//get transform matrix and cam position
matrix xform = optransform(op_path);

float x = getcomp(xform,3,0);
float y = getcomp(xform,3,1);
float z = getcomp(xform,3,2);

vector Pc = set(x,y,z); //camera pos

vector P1 = v@P; //iterated point pos

float r1 = f@pscale; //iterate point radius

float padding = chf("padding");
float alpha = sin( (r1-padding) / distance(P1,Pc) );

float d1 = distance(P1,Pc); //distance to iterated point

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

    if ( i != @ptnum ) {
        
        vector P2 = point(0,"P",i); //compared point position
        float r2 = point(0,"pscale",i); //compared point radius
        
        float phi = sin( r2 / distance(P2,Pc) );
        
        float theta = acos( dot((P1-Pc),(P2-Pc)) / (length(P1-Pc)*length(P2-Pc)) );
        float d2 = distance(P2,Pc); //distance to compared point
        
        if ( (phi + theta) < alpha &&  d1 < d2 ){
            setpointgroup(0,"occluded",i,1);
        }
    }
}
Previous
Previous

Detangle Solver

Next
Next

Group primitives visible to camera (VEX)