OpenGL-based Real-time Rendering of Arbitary Objects
Casting Shadows onto Arbitary Geometry
In the real-world, arbitarily shaped object cast shadows onto other
arbitaryily shaped objects. This technical note describes how implement
such shadows in real-time using OpenGL rendering. An easy-to-use API is
described (including full source code) that will make it easy for you
to incorporate such shadows into your OpenGL programs.
What am I really talking about? Consider holding a pretzel in front of
a flashlight and letting the shadow fall on a basket ball. That's a bit
contrived, but you get the idea. The result is going to be a fairly
complex shadow (an irregular object casing a shadow onto a curved
surface). If you look around you right now (and there is not too much
ambient light), you'll probably see some fairly intricate shadows being
cast all about you. Such shadows are vital clues to our understanding
of the 3D reality around us.
Real-time computer generated 3D scenes often lack shadows. When they
are supported, they are often very contrained. Blinn's planar projected
shadow approach, described in a now famous article titled "Me and
My (Fake) Shadow", makes it straightforward to project the shadow
of an arbitary object onto a plane.
I've already show how to generate such planar projected shadows in a
technical note titled "Shadows,
Reflections, Lighting, Textures. Easy with OpenGL." This
approach works and is very easy to incorporate into OpenGL programs.
The problem with Blinn's approach is that real shadows aren't just cast
onto planes. Real shadows block lighting of whatever is behind the
shadowing object. Here's a simple image showing a planar projected
shadow:
The shadow is reasonable, but if there was anything else behind the
dinosaur such as a sphere or a wooden post, it would not be shadowed
appropriately. Let's look at two interesting OpenGL-rendered images
showing arbitary objects casting shadows on other arbitary geometry:
Notice that the light source in the red double torus is casting a
shadow on both the orange sphere and on the wall behind the sphere. In
the right image, the double torus (rotated from the previous image) is
now casting a shadow on the cone and walls (the shadow moved because
the light source location moved).
While in some sense, this is pretty simple stuff (the shadowing effects
shown above are simplistic to what you see around you everyday), how
you render such shadows is a bit involved. Fortunately, OpenGL supplies
you with all the machinery you need to generate these shadows. The best
part is that because almost all the rendering operations to generate
these shadows are extremely amenable to hardware acceleration, you can
generate such shadows in interactive rates. On my SGI workstation
(admittedly more powerful than most of today's PCs), the program above
runs at over 15 frames/second with both the light moving and the double
torus spinning continously). Some straightforward optimizations could
get the program running faster than that!
So how do you achieve such effects?
As I said, OpenGL supplies all the machinery necessary to render the
scenes above in real-time. The critical features for the real-time
shadows pictured above are:
-
OpenGL feedback. This mode lets you "capture" transformed
vertices. By rendering the double torus above (and in pricinple, any
object) as viewed from the light source with feedback mode enabled, you
can find out all the 2D triangles making up the shape of the shadowing
object as viewed from the light source.
-
The GLU 1.2 polygon tessellator. Once you get the 2D triangles of the
shadowing object as viewed from the light source, you can feed these
triangles into OpenGL's GLU polygon tessellator and request the
silhouette (that is, the boundary) of the object.
-
Stencil testing. If you project out the silhouette in 3D from the
object in the direction opposite of the light sources, you can build
what is called the "shadow volume" for the shadowing object.
With OpenGL stenciling (as described in more detail below), you can
render the front and back facing polygons of the shadow volume in such
a way that you update the stencil buffer to indicate which pixels in
the scene are within the shadow volume (that is, which pixels should
not be illuminated by the light source).
-
Dynamic lighting. OpenGL let's you enable and disable light sources
quickly and efficiently so that you can re-render portions of the scene
that should not be illuminated. Effectively, this lets you eliminate
the lighting from shadowed pixels in the scene.
-
Reverse subtractive blending. An extension to OpenGL's blending
functionality (very likely to be included in OpenGL 1.2, but available
already on some OpenGL platforms) lets you subtract out the
contribution of an OpenGL light source to pixels within the scene. This
is useful to correctly handle the interactive of multiple light sources
in a scene are casting shadows. You can get away with the default
additive OpenGL blending, but reverse subtractive can be more efficient.
Now, I've described what features that OpenGL supports that makes the
shadows shown above renderable and I've alluded to what each feature
gets used for. Now is a good time to interject that Microsoft's
competing Direct3D Immediate Mode API lacks all these features listed
above except for dynamic lighting. While the last feature (reverse
subtractive blending) is not required (it definitely helps though), the
first three features are vital. The lack of these features in Direct3D
(particularly the lack of stencil testing) mean the shadowing algorithm
I'm describing really just isn't available to Direct3D programmers.
Moreover, the shadowing algorithm discussed is what's known as a
"multi-pass" graphics algorithms (something that will become very
trendy as PC graphics hardware gets faster and faster). The problem is
that Direct3D really has no strict requirements about whether multiple
passes will update identical pixels (something very important for
multi-pass techniques). This is because Direct3D implementations tend
to cut corners (and in the process screw up programs that need to rely
on reasonable rendering behaviors across multiple passes).
Now, let's continue looking at the real-time shadow algorithm in more
detail. Here's a simple flow chart that should get you started: