Frustum Culling In Object Space         Frustum culling using a custom SimpleFrustum class

Sometimes you want to do your own frustum culling and you may want to do that culling in object space instead of world space. If you don't know what I mean by object space then it should be enough for you to imagine if you picked an object in your scene then re-oriented the entire scene so that the object you picked is at the center, now everything is in that object's space.

The SimpleFrustum class below uses the Gribb-Hartmann method for extracting frustum planes from a model/view/projection matrix.

One important note I will make is that this example only tests bounding spheres. The isVisible() function takes a Ogre::Sphere object as input. If you only want to test to see if a specific point is outside the view frustum you can save some cpu cycles and modify SimpleFrustum::setModelViewProjMatrix() by removing all the Plane.normalise() calls and then you'll want to modify the isVisible() function to take an Ogre::Vector3 parameter and just check if the mPlanesi.getDistance(point) is less than 0. Read the Gribb-Hartmann PDF for more details and all the math.

Example Usage

// bounding sphere of radius 1 centered at the origin
Sphere boundingSphere(Vector3(0, 0, 0), 1.0f);
SimpleFrustum f;

f.setModelViewProjMatrix(  
    camera->getProjectionMatrix() *  
    camera->getViewMatrix() * 
    sceneNode->_getFullTransform() );

if(!f.isVisible(boundingSphere)) {
    // don't draw this object because the bounding sphere is outside 
    // the view frustum
}

SimpleFrustum.h

#include <Ogre.h>

    using namespace Ogre;

    class SimpleFrustum
    {
    public:
        SimpleFrustum();
        ~SimpleFrustum();

        void setModelViewProjMatrix(Matrix4 m);
       
        inline bool isVisible(const Sphere* s) {
            Vector3 position = s->getCenter();
            Real radius      = s->getRadius();
           
            for(int i = 0; i < 6; ++i) {
                if(mPlanes[i].getDistance(position) < -radius) {
                    return false;
                }
            }
           
            return true;
        }
       
    private:
        Plane mPlanes[6];
    };

SimpleFrustum.cpp

#include "SimpleFrustum.h"

    SimpleFrustum::SimpleFrustum()
    {
    }

    SimpleFrustum::~SimpleFrustum()
    {
    }

    void SimpleFrustum::setModelViewProjMatrix(Matrix4 m)
    {
        // Left clipping plane
        mPlanes[0].normal.x = m[3][0] + m[0][0];
        mPlanes[0].normal.y = m[3][1] + m[0][1];
        mPlanes[0].normal.z = m[3][2] + m[0][2];
        mPlanes[0].d        = m[3][3] + m[0][3];

        // Right clipping plane
        mPlanes[1].normal.x = m[3][0] - m[0][0];
        mPlanes[1].normal.y = m[3][1] - m[0][1];
        mPlanes[1].normal.z = m[3][2] - m[0][2];
        mPlanes[1].d        = m[3][3] - m[0][3];
                           
        // Top clipping plane
        mPlanes[2].normal.x = m[3][0] - m[1][0];
        mPlanes[2].normal.y = m[3][1] - m[1][1];
        mPlanes[2].normal.z = m[3][2] - m[1][2];
        mPlanes[2].d        = m[3][3] - m[1][3];

        // Bottom clipping plane
        mPlanes[3].normal.x = m[3][0] + m[1][0];
        mPlanes[3].normal.y = m[3][1] + m[1][1];
        mPlanes[3].normal.z = m[3][2] + m[1][2];
        mPlanes[3].d        = m[3][3] + m[1][3];
       
        // Near clipping plane
        mPlanes[4].normal.x = m[3][0] + m[2][0];
        mPlanes[4].normal.y = m[3][1] + m[2][1];
        mPlanes[4].normal.z = m[3][2] + m[2][2];
        mPlanes[4].d        = m[3][3] + m[2][3];
       
        // Far clipping plane
        mPlanes[5].normal.x = m[3][0] - m[2][0];
        mPlanes[5].normal.y = m[3][1] - m[2][1];
        mPlanes[5].normal.z = m[3][2] - m[2][2];
        mPlanes[5].d        = m[3][3] - m[2][3];

        // we do need to normalize the planes because
        // we need to get real distances to compare bounding spheres
        for(int i = 0; i < 6; ++i) {
            mPlanes[i].normalise();
        }
    }

Why Do I Need This?

I wanted to do frustum culling for my terrain in object space because I have 6 scene nodes that are each oriented differently and for each scene node I have one MovableObject attached, and each MovableObject has hundreds of Renderable() terrain meshes associated with it. Now, the scene manager will frustum cull based on the bounding boxes of scene nodes, but in my case I wanted to cull the individual Renderables(), which is a finer level of culling. You may think, "Why not make each of the meshes derive from MovableObject and Renderable and attach each to its' own Scenenode?" Because doing that would mean more unnecessary memory usage and a great deal more scene graph manipulation, which can hurt your framerate. At the time of writing, the PagedGeometry Engine uses a similar approach for handling the vast number of meshes it needs to display, although it batches meshes which is even more efficient if you can afford the processing time and you don't update your meshes really often.


Alias: Frustum_Culling_In_Object_Space