IntroductionThis article describes how to implement a very realistic shadow effect using nVIDIA's Cg programming language and OpenGL. Although the focus of the article is on Cg, the shadowing algorithm is sufficiently complex to require some detailed explanations at the beginning. Additional shadowing references are listed at the end of the article. The article will assume that the user has a modicum of familiarity with basic Cg. Here is a good introductory article on Cg that you may wish to peruse first. Shadow BasicsFlat ShadowsThe simplest way to render a shadow of a model is to take all the polygons that make that model and project them onto a surface along ray lines starting from the light [1]. Here is what that would look like:
The shadow consists of many polygons patched together. Each such polygon is a projection of a face of the original model, along light rays, onto the wall. Although this technique is fast and reasonably effective, it has many limitations. The most important limitation is that shadows can only be rendered if they fall on flat surfaces (for example, a wall). If you want to project a shadow onto a sphere or some other non-trivial object, this technique falls short. An immediate corollary of this is that objects cannot self-shadow if they are anything but trivial, flat geometrical shapes. The shadow volume [2] is a more computationally expensive but far more accurate technique for rendering shadows that are cast on any object or scene, no matter how complex or irregular. Shadow VolumesAt a high level, a shadow volume is an enclosed area of space that looks somewhat like a cone or a pyramid. The tip of the shadow volume is the light source; the faces of the shadow volume are determined by the outline of the object that is casting the shadow.
Any part of the scene that falls inside the shadow volume is shadowed; any part of the scene that falls outside the shadow volume is lit. Below is what a shadow volume rendered scene would look like. Notice the self-shadowing of the shield on the body; also note that, while the surrounding scene happens to be a flat polygon, it could be any arbitrary shape:
In order to render the shadow volume, we must first determine the outline of model from the perspective of the light. Intuitively, for a given model, the outline will depend on the position of the light source relative to that object. Think of the model as consisting of many adjacent faces (polygons). Some of these polygons will be visible from the light; some other polygons will not be visible. Any edge that is shared between a visible and an invisible polygon is part of the shadow volume outline:
The visibility test for a face is performed simply by computing the dot product (angle) between the face normal and the light direction to that face: if the dot product is negative, the face is invisible, otherwise the face is visible from the light. Once we have the complete set of outline segments, for each segment in the outline, extrude (push out) the extremities of that segment along the light direction to produce the shadow volume faces:
Take a moment to think about the diagrams above, about the definition of an outline segment and the definition of a shadow volume face. At this point we have conceptualized the notion of a shadow volume given a model and a light. We can now think about how to determine what parts of the scene are lit or shadowed relative to this shadow volume. For simplicity, we reduce the problem to two dimensions:
For any point in the scene, if the ray connecting the eye to that point intersects the shadow volume an even number of times, then that point is in the light; otherwise, the point is in the shadow. The shadow volume algorithm described above requires that the model be a closed object: it can be concave or convex, but it must not have "holes" (missing faces). If there is a missing face in the object, then a shadow volume outline segment might be skipped (since outline segments are defined as a shared edges between two faces). |
|