O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Culling and Clipping

Avoid telling OpenGL to render things that aren’t visible anyway. Sounds easy, right? In practice, this guideline can be more difficult to follow than you might think.

Polygon Winding

Consider something simple: an OpenGL scene with a spinning, opaque sphere. All the triangles on the “front” of the sphere (the ones that face that camera) are visible, but the ones in the back are not. OpenGL doesn’t need to process the vertices on the back of the sphere. In the case of OpenGL ES 2.0, we’d like to skip running a vertex shader on back-facing triangles; with OpenGL ES 1.1, we’d like to skip transform and lighting operations on those triangles. Unfortunately, the graphics processor doesn’t know that those triangles are occluded until after it performs the rasterization step in the graphics pipeline.

So, we’d like to tell OpenGL to skip the back-facing vertices. Ideally we could do this without any CPU overhead, so changing the VBO at each frame is out of the question.

How can we know ahead of time that a triangle is back-facing? Consider a single layer of triangles in the sphere; see Figure 9-3. Note that the triangles have consistent “winding”; triangles that wind clockwise are back-facing, while triangles that wind counterclockwise are front-facing.

Triangle winding

Figure 9-3. Triangle winding

OpenGL can quickly determine whether a given triangle goes clockwise or counterclockwise. Behind the scenes, the GPU can take the cross product of two edges in screen space; if the resulting sign is positive, the triangle is front-facing; otherwise, it’s back-facing.

Face culling is enabled like so:


You can also configure OpenGL to define which winding direction is the front:

glFrontFace(GL_CW);  // front faces go clockwise
glFrontFace(GL_CCW); // front faces go counterclockwise (default)

Depending on how your object is tessellated, you may need to play with this setting.

Use culling with caution; you won’t always want it to be enabled! It’s mostly useful for opaque, enclosed objects. For example, a ribbon shape would disappear if you tried to view it from the back.

As an aside, face culling is useful for much more than just performance optimizations. For example, developers have come up with tricks that use face culling in conjunction with the stencil buffer to perform CSG operations (composite solid geometry). This allows you to render shapes that are defined from the intersections of other shapes. It’s cool stuff, but we don’t go into it in detail in this book.

User Clip Planes

User clip planes provide another way of culling away unnecessary portions of a 3D scene, and they’re often useful outside the context of performance optimization. Here’s how you enable a clip plane with OpenGL ES 1.1:

void EnableClipPlane(vec3 normal, float offset)
    GLfloat planeCoefficients[] = {normal.x, normal.y, normal.z, offset}; 
    glClipPlanef(GL_CLIP_PLANE0, planeCoefficients);

Alas, with OpenGL ES 2.0 this feature doesn’t exist. Let’s hope for an extension!

The coefficients passed into glClipPlanef define the plane equation; see Equation 9-1.

Equation 9-1. Implicit plane equation

Ax + By + Cz + D = 0

One way of thinking about Equation 9-1 is interpreting A, B, and C as the components to the plane’s normal vector, and D as the distance from the origin. The direction of the normal determines which half of the scene to cull away.

Older Apple devices support only one clip plane, but newer devices support six simultaneous planes. The number of supported planes can be determined like so:

GLint maxPlanes;
glGetIntegerv(GL_MAX_CLIP_PLANES, &maxPlanes);

To use multiple clip planes, simply add a zero-based index to the GL_CLIP_PLANE0 constant:

void EnableClipPlane(int clipPlaneIndex, vec3 normal, float offset)
    glEnable(GL_CLIP_PLANE0 + clipPlaneIndex);
    GLfloat planeCoefficients[] = {normal.x, normal.y, normal.z, offset}; 
    glClipPlanef(GL_CLIP_PLANE0 + clipPlaneIndex, planeCoefficients);

This is consistent with working with multiple light sources, which requires you to add a light index to the GL_LIGHT0 constant.

CPU-Based Clipping

Unlike the goofy toy applications in this book, real-world applications often have huge, sprawling worlds that users can explore. Ideally, you’d give OpenGL only the portions of the world that are within the viewing frustum, but at first glance this would be incredibly expensive. The CPU would need to crawl through every triangle in the world to determine its visibility!

The way to solve this problem is to use a bounding volume hierarchy (BVH), a tree structure for facilitating fast intersection testing. Typically the root node of a BVH corresponds to the entire scene, while leaf nodes correspond to single triangles, or small batches of triangles.

A thorough discussion of BVHs is beyond the scope of this book, but volumes have been written on the subject, and you should be able to find plenty of information. You can find an excellent overview in Real-Time Rendering (AK Peters) by Möller, Haines, and Hoffman.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required