How to do frustum culling in OpenGL with the view and projection matrix?

2k views Asked by At

I'm trying to implement frustum culling to my voxel engine, basically I'm rendering chunks and I want to cull every chunk that is outside of the frustum of the camera. I tried a lot of different approaches and code that I found on the web, but yet I can't get it to work. The algorithm is in two parts:

• First I'm extracting the frustum planes from the projectionview matrix.

• Then I'm checking for each chunk if it is inside or colliding with the frustum.

The behavior is generally the same: when looking from the origin to positive direction it seems to work, but when looking to the negative direction it doesn't, and when I'm going away from the origin it starts breaking and doing non-sense. Also when looking up and down the culling is weird.

Here is my frustum plane extraction:


    public static Plane[] frustumPlanes(Matrix4f mat, boolean normalize)
    {   
        Plane[] p = new Plane[6];
        p[0] = normalizePlane(mat.m30 + mat.m00, mat.m31 + mat.m01, mat.m32 + mat.m02, mat.m33 + mat.m03); // left
        p[1] = normalizePlane(mat.m30 - mat.m00, mat.m31 - mat.m01, mat.m32 - mat.m02, mat.m33 - mat.m03); // right
        p[2] = normalizePlane(mat.m30 - mat.m10, mat.m31 - mat.m11, mat.m32 - mat.m12, mat.m33 - mat.m13); // top
        p[3] = normalizePlane(mat.m30 + mat.m10, mat.m31 + mat.m11, mat.m32 + mat.m12, mat.m33 + mat.m13); // bottom
        p[4] = normalizePlane(mat.m30 + mat.m20, mat.m31 + mat.m21, mat.m32 + mat.m22, mat.m33 + mat.m23); // near
        p[5] = normalizePlane(mat.m30 - mat.m20, mat.m31 - mat.m21, mat.m32 - mat.m22, mat.m33 - mat.m23); // far
        
        return p;
    }
    
    public static Plane normalizePlane(float A, float B, float C, float D) {

        float nf = 1.0f / (float)Math.sqrt(A * A + B * B + C * C);

        return new Plane(new Vector3f(nf * A, nf * B, nf * C), nf * D);
    }

mat is the projectionview matrix, here is the projection matrix:


    private void createProjectionMatrix() {
        
        float aspectRatio = (float) DisplayManager.WIDTH / (float) DisplayManager.HEIGHT;
        float y_scale = (float) ((1f / Math.tan(Math.toRadians(FOV / 2f))));
        float x_scale = y_scale / aspectRatio;
        float frustum_length = FAR_PLANE - NEAR_PLANE;

        projectionMatrix = new Matrix4f();
        projectionMatrix.m00 = x_scale;
        projectionMatrix.m11 = y_scale;
        projectionMatrix.m22 = -((FAR_PLANE + NEAR_PLANE) / frustum_length);
        projectionMatrix.m23 = -1;
        projectionMatrix.m32 = -((2 * NEAR_PLANE * FAR_PLANE) / frustum_length);
        projectionMatrix.m33 = 0;
    }

Here is the view matrix:


public static Matrix4f createViewMatrix(Camera camera) {
        Matrix4f viewMatrix = new Matrix4f();
        viewMatrix.setIdentity();
        
        Matrix4f.rotate((float) Math.toRadians(camera.getRotation().x), new Vector3f(1, 0, 0), viewMatrix, viewMatrix);
        Matrix4f.rotate((float) Math.toRadians(camera.getRotation().y), new Vector3f(0, 1, 0), viewMatrix, viewMatrix);
        Matrix4f.rotate((float) Math.toRadians(camera.getRotation().z), new Vector3f(0, 0, 1), viewMatrix, viewMatrix);
        
        Vector3f cameraPos = camera.getPosition();
        Vector3f negativeCameraPos = new Vector3f(-cameraPos.x,-cameraPos.y,-cameraPos.z);
        Matrix4f.translate(negativeCameraPos, viewMatrix, viewMatrix);
        return viewMatrix;
    }

Here is the collision detection code aabb vs plane:


public static int boxToPlaneCollision(Plane plane, Vector3f[] minMax)
    {
        int result = 2; //Inside
        
                    // planes have unit-length normal, offset = -dot(normal, point on plane)
            int nx = plane.normal.x > 0?1:0;
            int ny = plane.normal.y > 0?1:0;
            int nz = plane.normal.z > 0?1:0;
            
            // getMinMax(): 0 = return min coordinate. 1 = return max.
            float dot = (plane.normal.x*minMax[nx].x) + (plane.normal.y*minMax[nx].y) + (plane.normal.z*minMax[nx].z);
            
            if ( dot < -plane.offset )
                return 0; //Outside
            
            float dot2 = (plane.normal.x*minMax[1-nx].x) + (plane.normal.y*minMax[1-nx].y) + (plane.normal.z*minMax[1-nx].z);
            
            if ( dot2 <= -plane.offset )
                result = 1; //Intersect
        
        return result;
    }

And finally here is where everything is called:


public boolean chunkInsideFrustum(Vector3f chunkPos) {
        Vector3f chunkPosMax = new Vector3f(chunkPos.x + Terrain.CHUNK_SIZE, Terrain.CHUNK_HEIGHT, chunkPos.z + Terrain.CHUNK_SIZE);
        for (int i = 0; i < 6; i++) {

            if(Collider.boxToPlaneCollision(frustumPlanes[i], new Vector3f[] {chunkPos,chunkPosMax}) == 0)
                return false;
        }
        return true;
    }

I'm using openGL with LWJGL 2 (Java).

My questions are: Where is the problem? In the frustum plane extraction code? In the collision detection?

and

I saw people calculating the frustum with projection and modelview matrix, what about this technique? is it better?

Thank you very much for your help!

EDIT:

for the second question, I saw here Extracting View Frustum Planes (Gribb & Hartmann method) someone posted that:

The missing part:

comboMatrix = projection_matrix * Matrix4_Transpose(modelview_matrix)

And then he did the exact same algorithm that I did to extract the planes, but what is modelview_matrix? What model should I use?

0

There are 0 answers