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.
Table of contents
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.