OpenGL Frustum visibility test with sphere : Far plane not working

1.5k views Asked by At

I am doing a program to test sphere-frustum intersection and being able to determine the sphere's visibility. I am extracting the frustum's clipping planes into camera space and checking for intersection. It works perfectly for all planes except the far plane and I cannot figure out why. I keep pulling the camera back but my program still claims the sphere is visible, despite it having been clipped long ago. If I go far enough it eventually determines that it is not visible, but this is some distance after it has exited the frustum.

I am using a unit sphere at the origin for the test. I am using the OpenGL Mathematics (GLM) library for vector and matrix data structures and for its built in math functions. Here is my code for the visibility function:

void visibilityTest(const struct MVP *mvp) {
    static bool visLastTime = true;
    bool visThisTime; 
    const glm::vec4 modelCenter_worldSpace = glm::vec4(0,0,0,1);    //at origin
    const int negRadius = -1;   //unit sphere

    //Get cam space model center
    glm::vec4 modelCenter_cameraSpace = mvp->view * mvp->model * modelCenter_worldSpace;
    //---------Get Frustum Planes--------
    //extract projection matrix row vectors
    //NOTE: since glm stores their mats in column-major order, we extract columns
    glm::vec4 rowVec[4];
    for(int i = 0; i < 4; i++) {
        rowVec[i] = glm::vec4( mvp->projection[0][i], mvp->projection[1][i], mvp->projection[2][i], mvp->projection[3][i] );
    }

    //determine frustum clipping planes (in camera space)
    glm::vec4 plane[6];
    //NOTE: recall that indices start at zero. So M4 + M3 will be rowVec[3] + rowVec[2]
    plane[0] = rowVec[3] + rowVec[2];   //near
    plane[1] = rowVec[3] - rowVec[2];   //far
    plane[2] = rowVec[3] + rowVec[0];   //left
    plane[3] = rowVec[3] - rowVec[0];   //right
    plane[4] = rowVec[3] + rowVec[1];   //bottom
    plane[5] = rowVec[3] - rowVec[1];   //top

    //extend view frustum by 1 all directions; near/far along local z, left/right among local x, bottom/top along local y
    // -Ax' -By' -Cz' + D = D'
    plane[0][3] -= plane[0][2];     // <x',y',z'> = <0,0,1>
    plane[1][3] += plane[1][2];         // <0,0,-1>
    plane[2][3] += plane[2][0];         // <-1,0,0> 
    plane[3][3] -= plane[3][0];         // <1,0,0>
    plane[4][3] += plane[4][1];         // <0,-1,0>
    plane[5][3] -= plane[5][1];         // <0,1,0>

    //----------Determine Frustum-Sphere intersection--------
    //if any of the dot products between model center and frustum plane is less than -r, then the object falls outside the view frustum
    visThisTime = true; 
    for(int i = 0; i < 6; i++) {
        if( glm::dot(plane[i], modelCenter_cameraSpace) < static_cast<float>(negRadius) ) {
            visThisTime = false;    
        }
    }
    if(visThisTime != visLastTime) {
        printf("Sphere is %s visible\n", (visThisTime) ? "" : "NOT " );
        visLastTime = visThisTime;
    }
} 

The polygons appear to be clipped by the far plane properly so it seems that the projection matrix is set up properly, but the calculations make it seem like the plane is way far out. Perhaps I am not calculating something correctly or have a fundamental misunderstanding of the calculations that are required?

The calculations that deal specifically with the far clipping plane are:

plane[1] = rowVec[3] - rowVec[2];   //far

and

plane[1][3] += plane[1][2];         // <0,0,-1>

I'm setting the plane to be equal to the 4th row (or in this case column) of the projection matrix - the 3rd row of the projection matrix. Then I'm extending the far plane one unit further (due to the sphere's radius of one; D' = D - C(-1) )

I've looked over this code many times and I can't see why it shouldn't work. Any help is appreciated.

EDIT: I can't answer my own question as I don't have the rep, so I will post it here. The problem was that I wasn't normalizing the plane equations. This didn't seem to make much of a difference for any of the clip planes besides the far one, so I hadn't even considered it (but that didn't make it any less wrong). After normalization everything works properly.

0

There are 0 answers