Chapter 18. Depth Textures and Shadows

by Benjamin Lipchak

WHAT YOU'LL LEARN IN THIS CHAPTER:

How To

Functions You'll Use

Draw your scene from the light's perspective

gluLookAt/gluPerspective

Copy texels from the depth buffer into a depth texture

glCopyTexImage2D

Use eye linear texture coordinate generation

glTexGen

Set up shadow comparison

glTexParameter

Shadows are an important visual cue, both in reality and in rendered scenes. At a very basic level, shadows give us information about the location of objects in relation to each other and to light sources, even if the light sources are not visible in the scene. When it comes to games, shadows can make an already immersive environment downright spooky. Imagine turning the corner in a torch-lit dungeon and stepping into the shadow of your worst nightmare. Peter Pan had it easy.

In Chapter 5, “Color, Materials, and Lighting: The Basics,” we described a low-tech way of projecting an object onto a flat plane, in effect “squishing” it to appear as a shadow. Another technique utilizing the stencil buffer, known as shadow volumes, has been widely used, but it tends to require significant pre-processing of geometry and high fill rates to the stencil buffer. In OpenGL 1.4, a more elegant approach to shadow generation has been made possible: shadow mapping.

The theory behind shadow mapping is simple. What parts of your scene would fall in shadow? Answer: The parts that light doesn't directly hit. Think of yourself in the light's position in your virtual scene. What would the light see if it were the camera? Everything the light sees would be lit. Everything else falls in shadow. Figure 18.1 will help you visualize the difference between the camera's viewpoint and the light's viewpoint.

The camera and the light have different perspectives on the scene.

Figure 18.1. The camera and the light have different perspectives on the scene.

When the scene is rendered from the light's perspective, the side effect is a depth buffer full of useful information. At every pixel in the resulting depth buffer, we know the relative distance from the light to the nearest surface. These surfaces are lit by the light source. Every other surface farther away from the light source remains in shadow.

What we'll do is take that depth buffer, copy it into a texture, and project it back on the scene, now rendered again from the normal camera angle. We'll use that projected texture to automatically determine what parts of what objects are in light, and which remain in shadow. Sounds easy, but each step of this technique requires careful attention.

Be That Light

Our first step is to draw the scene from the light's perspective. We'll use several built-in GLUT objects to show off how well this technique works, even when casting shadows on nonplanar surfaces, such as other objects in the scene. You can change the viewpoint by manually setting the modelview matrix, but for this example, we use the gluLookAt helper function to facilitate the change:

gluLookAt(lightPos[0], lightPos[1], lightPos[2],
          0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);

Fit the Scene to the Window

In addition to this modelview matrix, we also need to set up the projection matrix to maximize the scene's size in the window. Even if the light is far away from the objects in the scene, to achieve the best utilization of the space in our shadow map, we would still like the scene to fill the available space. We'll set up the near and far clipping planes based on the distance from the light to the nearest and farthest objects in the scene. Also, we'll estimate the field of view to contain the entire scene as closely as possible:

// Save the depth precision for where it's useful
lightToSceneDistance = sqrt(lightPos[0] * lightPos[0] +
                            lightPos[1] * lightPos[1] +
                            lightPos[2] * lightPos[2]);
nearPlane = lightToSceneDistance - 150.0f;
if (nearPlane < 50.0f)
    nearPlane = 50.0f;
// Keep the scene filling the depth texture
fieldOfView = 17000.0f / lightToSceneDistance;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(fieldOfView, 1.0f, nearPlane, nearPlane + 300.0f);

No Bells or Whistles, Please

When we draw the first pass of the scene, the light's viewpoint, we don't actually want to see it. We just want to tap into the resulting depth buffer. So we'll draw to the back buffer and never bother swapping. We can further accelerate this pass by masking writes to the color buffer. And because all we care about is the depth values, we obviously don't care about lighting, smooth shading, or anything else that isn't going to affect the result. Shut it all off. All we need to specify is the raw geometry:

glShadeModel(GL_FLAT);
glDisable(GL_LIGHTING);
glDisable(GL_COLOR_MATERIAL);
glDisable(GL_NORMALIZE);
glColorMask(0, 0, 0, 0);

The output from Listing 18.1 is not visible, but Figure 18.2 illustrates via grayscale what the depth buffer contains.

Example 18.1. Rendering the Light's Viewpoint into the Shadow Map

// Called to draw scene objects
void DrawModels(void)
{
    // Draw plane that the objects rest on
    glColor3f(0.0f, 0.0f, 0.90f); // Blue
    glNormal3f(0.0f, 1.0f, 0.0f);
    glBegin(GL_QUADS);
        glVertex3f(-100.0f, -25.0f, -100.0f);
        glVertex3f(-100.0f, -25.0f, 100.0f);
        glVertex3f(100.0f,  -25.0f, 100.0f);
        glVertex3f(100.0f,  -25.0f, -100.0f);
    glEnd();

    // Draw red cube
    glColor3f(1.0f, 0.0f, 0.0f);
    glutSolidCube(48.0f);

    // Draw green sphere
    glColor3f(0.0f, 1.0f, 0.0f);
    glPushMatrix();
    glTranslatef(-60.0f, 0.0f, 0.0f);
    glutSolidSphere(25.0f, 50, 50);
    glPopMatrix();

    // Draw yellow cone
    glColor3f(1.0f, 1.0f, 0.0f);
    glPushMatrix();
    glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    glTranslatef(60.0f, 0.0f, -24.0f);
    glutSolidCone(25.0f, 50.0f, 50, 50);
    glPopMatrix();

    // Draw magenta torus
    glColor3f(1.0f, 0.0f, 1.0f);
    glPushMatrix();
    glTranslatef(0.0f, 0.0f, 60.0f);
    glutSolidTorus(8.0f, 16.0f, 50, 50);
    glPopMatrix();

    // Draw cyan octahedron
    glColor3f(0.0f, 1.0f, 1.0f);
    glPushMatrix();
    glTranslatef(0.0f, 0.0f, -60.0f);
    glScalef(25.0f, 25.0f, 25.0f);
    glutSolidOctahedron();
    glPopMatrix();
}

// Called to regenerate the shadow map
void RegenerateShadowMap(void)
{
    GLfloat lightToSceneDistance, nearPlane, fieldOfView;
    GLfloat lightModelview[16], lightProjection[16];

    // Save the depth precision for where it's useful
    lightToSceneDistance = sqrt(lightPos[0] * lightPos[0] +
                                lightPos[1] * lightPos[1] +
                                lightPos[2] * lightPos[2]);
    nearPlane = lightToSceneDistance - 150.0f;
    if (nearPlane < 50.0f)
        nearPlane = 50.0f;
    // Keep the scene filling the depth texture
    fieldOfView = 17000.0f / lightToSceneDistance;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(fieldOfView, 1.0f, nearPlane, nearPlane + 300.0f);
    glGetFloatv(GL_PROJECTION_MATRIX, lightProjection);
    // Switch to light's point of view
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(lightPos[0], lightPos[1], lightPos[2],
              0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview);
    glViewport(0, 0, shadowSize, shadowSize);

    // Clear the depth buffer only
    glClear(GL_DEPTH_BUFFER_BIT);

    // All we care about here is resulting depth values
    glShadeModel(GL_FLAT);
    glDisable(GL_LIGHTING);
    glDisable(GL_COLOR_MATERIAL);
    glDisable(GL_NORMALIZE);
    glColorMask(0, 0, 0, 0);

    // Overcome imprecision
    glEnable(GL_POLYGON_OFFSET_FILL);

    // Draw objects in the scene
    DrawModels();

    // Copy depth values into depth texture
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
                     0, 0, shadowSize, shadowSize, 0);

    // Restore normal drawing state
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    glColorMask(1, 1, 1, 1);
    glDisable(GL_POLYGON_OFFSET_FILL);

    // Set up texture matrix for shadow map projection
    glMatrixMode(GL_TEXTURE);
    glLoadIdentity();
    glTranslatef(0.5f, 0.5f, 0.5f);
    glScalef(0.5f, 0.5f, 0.5f);
    glMultMatrixf(lightProjection);
    glMultMatrixf(lightModelview);
}
If we could see the depth buffer, this is what it would look like.

Figure 18.2. If we could see the depth buffer, this is what it would look like.

A New Kind of Texture

We want to copy the depth values from the depth buffer into a texture for use as the shadow map. OpenGL allows you to copy color values directly into textures via glCopyTexImage2D. Until OpenGL 1.4, this capability was possible only for color values. But now depth textures are available.

Depth textures simply add a new type of texture data. We've had base formats with red, green, and blue color data and/or alpha, luminosity, or intensity. To this list, we now add a depth base format. The internal formats that can be requested include GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, and GL_DEPTH_COMPONENT32, each reflecting the number of bits per texel. Typically, you'll want a format that matches the precision of your depth buffer. OpenGL makes it easy by letting you use the generic GL_DEPTH_COMPONENT internal format that usually adopts whichever specific format matches your depth buffer.

After drawing the light's view into the depth buffer, we want to copy that data directly into a depth texture. This saves us the trouble of using both glReadPixels and glTexImage2D:

glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
                 0, 0, shadowSize, shadowSize, 0);

Note that Listing 18.1, for drawing the light's view and regenerating the shadow map, needs to be executed only when objects in the scene move or the light source moves. If the only thing moving is the camera angle, you can keep using the same depth texture. Remember, when only the camera moves, the light's view of the scene isn't affected. (The camera is invisible.) We can reuse the existing shadow map in this case.

Draw the Shadows First?!

Yes, we will draw the shadows first. But, you ask, if a shadow is defined as the lack of light, why do we need to draw shadows at all? Strictly speaking, you don't need to draw them if you have a single spotlight. If you leave the shadows black, you'll achieve a stark effect that may suit your purposes well. But if you don't want pitch black shadows and still want to make out details inside the shadowed regions, you'll need to simulate some ambient lighting in your scene:

GLfloat lowAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
GLfloat lowDiffuse[4] = {0.35f, 0.35f, 0.35f, 1.0f};

glLightfv(GL_LIGHT0, GL_AMBIENT, lowAmbient);
glLightfv(GL_LIGHT0, GL_DIFFUSE, lowDiffuse);

// Draw objects in the scene
DrawModels();

We've added a bit of diffuse lighting as well to help convey shape information. If you use only ambient lighting, you end up with ambiguously shaped solid-colored regions. Figure 18.3 shows the scene so far, entirely in shadow.

The entire scene is in shadow before the lit areas are drawn.

Figure 18.3. The entire scene is in shadow before the lit areas are drawn.

Some OpenGL implementations support an extension, GL_ARB_shadow_ambient, which makes this first shadow drawing pass unnecessary. In this case, both the shadowed regions and the lit regions are drawn simultaneously. More on that optimization later.

And Then There Was Light

Right now, we just have a very dimly lit scene. To make shadows, we need some brightly lit areas to contrast the existing dimly lit areas, turning them into shadows. But how do we determine which areas to light? This is key to the shadow mapping technique. After we've decided where to draw, we'll draw brighter simply by using greater lighting coefficients, twice as bright as the shadowed areas:

GLfloat ambientLight[] = { 0.2f, 0.2f, 0.2f, 1.0f};
GLfloat diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f};
 ...
glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

Projecting Your Shadow Map: The “Why”

Remember that texture matrix code from Listing 18.1? Now's the time to explain it. The goal here is to project the shadow map (the light's viewpoint) of the scene back onto the scene as if emitted from the light, but viewed from the camera's position. We're projecting those depth values, which represent the distance from the light to the first object hit by the light's rays. Reorienting the texture coordinates into the right coordinate space is going to take a bit of math. If you care only about the “how” and not the “why,” you can safely skip over this section.

In Chapter 4, “Geometric Transformations: The Pipeline,” we explained the process of transforming vertices from object space to eye space, then to clip space, on to normalized device coordinates, and finally to window space. We have two different sets of matrices in play performing these transformations: one for the light view and the other for the regular camera view. Figure 18.4 shows the two sets of transformations in use.

The large arrow in the center shows the transformations we need to apply to our eye linear texture coordinates.

Figure 18.4. The large arrow in the center shows the transformations we need to apply to our eye linear texture coordinates.

Any texture projection usually begins with eye linear texture coordinate generation. This process will automatically generate our texture coordinates. Unlike object linear texture coordinate generation, the eye linear coordinates aren't tied to the geometry. Instead, it's as if there is a film projector casting the texture onto the scene. But it doesn't just project onto flat surfaces like a movie screen. Think about what happens when you walk in front of projector. The movie is projected on your irregularly shaped body. The same thing happens here.

We need to end up with texture coordinates that will index into our shadow map in the light's clip space. We start off with our projected eye linear texture coordinates in the camera's eye space. So we need to first backtrack to world space and then transform to the light's eye space and finally to the light's clip space. This transformation can be summarized by the following series of matrix multiplications:

The large arrow in the center shows the transformations we need to apply to our eye linear texture coordinates.

But wait, there's more. The light's clip space doesn't quite bring us home free. Remember that clip space is in the range [–1,1] for each of the x, y, and z coordinates. The shadow map depth texture, like all standard 2D textures, needs to be indexed in the range [0,1]. Also, the depth values against which we're going to be comparing are in the range [0,1], so we'll also need our z texture coordinate in that range. A simple scale by one half (S) and bias by one half (B) will do the trick:

The large arrow in the center shows the transformations we need to apply to our eye linear texture coordinates.

If you're unfamiliar with OpenGL matrix notation, you're probably asking why these matrices are in reverse order. After all, we need to apply the inverse of the camera's modelview first, and the bias by one half translation is the last transformation we need. What's the deal? It's really simple, actually. OpenGL applies a matrix (M) to a coordinate (T) in a seemingly backward way, too. So, you want to read everything right to left when thinking about the order of transformations being applied to your coordinate:

The large arrow in the center shows the transformations we need to apply to our eye linear texture coordinates.

This is standard representation. Nothing to see here. Move along.

Projecting Your Shadow Map: The “How”

We understand what matrix transformations need to be applied to our eye linear-generated texture coordinate to have something useful to index into our shadow map texture. But how do we apply these transformations?

Texture coordinates, like vertex positions, also are automatically subjected to matrix transformation. However, instead of both a modelview and a projection matrix, texture coordinates are multiplied by only a single texture matrix. There's nothing special to enable; the texture matrix is always applied to every texture coordinate, whether automatically generated or explicitly provided via immediate mode entrypoints, vertex arrays, and so on. You may not have known the texture matrix was at work because, by default, the identity matrix causes it to leave texture coordinates unchanged.

We'll use this texture matrix in combination with texture coordinate generation to achieve the necessary texture coordinate manipulation. To set up the texture matrix, we'll start with an identity matrix and multiply in each of our required transformations discussed in the preceding section:

glGetFloatv(GL_PROJECTION_MATRIX, lightProjection);
...
glGetFloatv(GL_MODELVIEW_MATRIX, lightModelview);
...
// Set up texture matrix for shadow map projection
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
glTranslatef(0.5f, 0.5f, 0.5f);
glScalef(0.5f, 0.5f, 0.5f);
glMultMatrixf(lightProjection);
glMultMatrixf(lightModelview);

When setting our light's projection and modelview matrices before drawing the light's view, we conveniently queried and saved off these matrices so we could apply them later to the texture matrix. Our scale and bias operations to map [–1,1] to [0,1] are easily expressed as glScalef and glTranslatef calls.

But where's the multiplication by the inverse of the camera's modelview matrix? Glad you asked. OpenGL anticipated the need for this transformation when using eye linear texture coordinate generation. A post-multiply by the inverse of the current modelview matrix is included in the eye plane equations. All you have to do is make sure your camera's modelview is installed at the time you call glTexGenfv:

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2],
          0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
...
GLfloat sPlane[4] = {1.0f, 0.0f, 0.0f, 0.0f};
GLfloat tPlane[4] = {0.0f, 1.0f, 0.0f, 0.0f};
GLfloat rPlane[4] = {0.0f, 0.0f, 1.0f, 0.0f};
GLfloat qPlane[4] = {0.0f, 0.0f, 0.0f, 1.0f};
...
// Set up the eye plane for projecting the shadow map on the scene
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
glTexGenfv(GL_R, GL_EYE_PLANE, rPlane);
glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane);
...
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

The Shadow Comparison

We have rendered our light view and copied it into a shadow map. We have our texture coordinates for indexing into the projected shadow map. The scene is dimly lit, ready for the real lights. The moment is near for completing our scene. We just need to combine the ingredients. First, there's some important state we can “set and forget” during initialization:

// Hidden surface removal
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);

// Set up some texture state that never changes
glGenTextures(1, &shadowTextureID);
glBindTexture(GL_TEXTURE_2D, shadowTextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

We set the depth test to less than or equal so that we can draw the lit pass on top of the dim pass. Otherwise, because the geometry is identical, the lit pass would always fail the depth test, and nothing would show up after the dimly lit shadow pass.

Then we generate and bind to our shadow map, which is the only texture used in this demo. We set our texture coordinate wrap modes to clamp. It makes no sense to repeat the projection. For example, if the light affects only a portion of the scene, but the camera is zoomed out to reveal other unlit parts of the scene, you don't want your shadow map to be repeated infinitely across the scene. You want your texture coordinates clamped so that only the lit portion of the scene has the shadow map projected onto it.

Depth textures contain only a single source component representing the depth value. But texture environments expect four components: red, green, blue, and alpha. OpenGL gives you the flexibility as to how you want the depth mapped. Choices for the depth texture mode include GL_ALPHA (0,0,0,D), GL_LUMINANCE (D,D,D,1), and GL_INTENSITY (D,D,D,D). We're going to need the depth broadcast to all four channels, so we choose the intensity mode:

glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);

Obviously, we need to enable texturing to put the shadow map into effect. We set the compare mode to GL_COMPARE_R_TO_TEXTURE. If we don't set this, all we'll get is the depth value in the texture. But we want more than that. We want the depth value compared to our texture coordinate's R component:

// Set up shadow comparison
glEnable(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
                GL_COMPARE_R_TO_TEXTURE);

The R component of the texture coordinate represents the distance from the light source to an object's surface. The shadow map's depth value represents the distance from the light to the first surface it hits. By comparing one to the other, we can tell whether a surface was the first to be hit by a ray of light, or if that surface is farther away from the light, and hence is in the shadow cast by the first lit surface:

The Shadow Comparison

Other comparison functions are also available. In fact, OpenGL 1.5 enables you to use all the same relational operators that you can use for depth test comparisons. GL_LEQUAL is the default, so we don't need to change it.

Another two settings we need to consider are the minification and magnification filters. We're going to use point sampling:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

However, some implementations may be able to smooth the edges of your shadows if you enable bilinear or trilinear filtering. On such an implementation, multiple comparisons are performed and the results are averaged. This is called percentage-closer filtering.

Great. We have a bunch of 0s and 1s. But we don't want to draw black and white. Now what? Easy. We just need to set up a texture environment mode, GL_MODULATE, that multiplies the 0s and 1s by the incoming color resulting from lighting:

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

Finally, we're done, right?! We have drawn our lit areas now. But wait. Where shadows appear, we just drew black over our previous ambient lighting pass. How do we preserve the ambient lighting for shadowed regions? Alpha testing will do the trick. We asked for intensity depth texture mode. Therefore, our 0s and 1s are present in the alpha component as well as the color components. Using an alpha test, we can tell OpenGL to discard any fragments in which we didn't get a 1:

// Enable alpha test so that shadowed fragments are discarded
glAlphaFunc(GL_GREATER, 0.9f);
glEnable(GL_ALPHA_TEST);

Okay. Now we're done. Figure 18.5 shows the output from Listing 18.2, shadows and all.

Example 18.2. Drawing the Ambient Shadow and Lit Passes of the Scene

// Called to draw scene
void RenderScene(void)
{
    // Track camera angle
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, 1.0f, 1.0f, 1000.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(cameraPos[0], cameraPos[1], cameraPos[2],
              0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f);
    glViewport(0, 0, windowWidth, windowHeight);

    // Track light position
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    if (showShadowMap)
    {
        // Display shadow map for educational purposes
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glMatrixMode(GL_TEXTURE);
        glPushMatrix();
        glLoadIdentity();
        glEnable(GL_TEXTURE_2D);
        glDisable(GL_LIGHTING);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        // Show the shadowMap at its actual size relative to window
        glBegin(GL_QUADS);
            glTexCoord2f(0.0f, 0.0f);
            glVertex2f(-1.0f, -1.0f);
            glTexCoord2f(1.0f, 0.0f);
            glVertex2f(((GLfloat)shadowSize/(GLfloat)windowWidth)*2.0-1.0f,
                       -1.0f);
            glTexCoord2f(1.0f, 1.0f);
            glVertex2f(((GLfloat)shadowSize/(GLfloat)windowWidth)*2.0-1.0f,
                       ((GLfloat)shadowSize/(GLfloat)windowHeight)*2.0-1.0f);
            glTexCoord2f(0.0f, 1.0f);
            glVertex2f(-1.0f,
                       ((GLfloat)shadowSize/(GLfloat)windowHeight)*2.0-1.0f);
        glEnd();
        glDisable(GL_TEXTURE_2D);
        glEnable(GL_LIGHTING);
        glPopMatrix();
        glMatrixMode(GL_PROJECTION);
        gluPerspective(45.0f, 1.0f, 1.0f, 1000.0f);
        glMatrixMode(GL_MODELVIEW);
    }
    else if (noShadows)
    {
        // Set up some simple lighting
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

        // Draw objects in the scene
        DrawModels();
    }
    else
    {
        GLfloat sPlane[4] = {1.0f, 0.0f, 0.0f, 0.0f};
        GLfloat tPlane[4] = {0.0f, 1.0f, 0.0f, 0.0f};
        GLfloat rPlane[4] = {0.0f, 0.0f, 1.0f, 0.0f};
        GLfloat qPlane[4] = {0.0f, 0.0f, 0.0f, 1.0f};

        if (!ambientShadowAvailable)
        {
            GLfloat lowAmbient[4] = {0.1f, 0.1f, 0.1f, 1.0f};
            GLfloat lowDiffuse[4] = {0.35f, 0.35f, 0.35f, 1.0f};

            // Because there is no support for an "ambient"
            // shadow compare fail value, we'll have to
            // draw an ambient pass first...
            glLightfv(GL_LIGHT0, GL_AMBIENT, lowAmbient);
            glLightfv(GL_LIGHT0, GL_DIFFUSE, lowDiffuse);

            // Draw objects in the scene
            DrawModels();

            // Enable alpha test so that shadowed fragments are discarded
            glAlphaFunc(GL_GREATER, 0.9f);
            glEnable(GL_ALPHA_TEST);
        }

        glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);

        // Set up shadow comparison
        glEnable(GL_TEXTURE_2D);
        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE,
                        GL_COMPARE_R_TO_TEXTURE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        // Set up the eye plane for projecting the shadow map on the scene
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
        glEnable(GL_TEXTURE_GEN_R);
        glEnable(GL_TEXTURE_GEN_Q);
        glTexGenfv(GL_S, GL_EYE_PLANE, sPlane);
        glTexGenfv(GL_T, GL_EYE_PLANE, tPlane);
        glTexGenfv(GL_R, GL_EYE_PLANE, rPlane);
        glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane);

        // Draw objects in the scene
        DrawModels();

        glDisable(GL_ALPHA_TEST);
        glDisable(GL_TEXTURE_2D);
        glDisable(GL_TEXTURE_GEN_S);
        glDisable(GL_TEXTURE_GEN_T);
        glDisable(GL_TEXTURE_GEN_R);
        glDisable(GL_TEXTURE_GEN_Q);
    }

    if (glGetError() != GL_NO_ERROR)
        fprintf(stderr, "GL Error!
");

    // Flush drawing commands
    glutSwapBuffers();
}

// This function does any needed initialization on the rendering
// context.
void SetupRC()
{
    const GLubyte *version;

    fprintf(stdout, "Shadow Mapping Demo

");

    // Make sure required functionality is available!
    version = glGetString(GL_VERSION);
    if (((version[0] != '1') || (version[1] != '.') ||
         (version[2] < '4') || (version[2] > '9')) &&   // 1.4+
        (!gltIsExtSupported("GL_ARB_shadow")))
    {
        fprintf(stderr, "Neither OpenGL 1.4 nor GL_ARB_shadow"
                        " extension is available!
");
        Sleep(2000);
        exit(0);
    }

    // Check for optional extension
    if (gltIsExtSupported("GL_ARB_shadow_ambient"))
    {
        ambientShadowAvailable = GL_TRUE;
    }
    else
    {
        fprintf(stderr, "GL_ARB_shadow_ambient extension not available!
");
        fprintf(stderr, "Extra ambient rendering pass will be required.

");
        Sleep(2000);
    }

    fprintf(stdout, "Controls:
");
    fprintf(stdout, "	c		Control camera movement
");
    fprintf(stdout, "	l		Control light movement

");
    fprintf(stdout, "	x/X		Move +/- in x direction
");
    fprintf(stdout, "	y/Y		Move +/- in y direction
");
    fprintf(stdout, "	z/Z		Move +/- in z direction

");
    fprintf(stdout, "	f/F		Change polygon offset factor +/-

");
    fprintf(stdout, "	s		Toggle showing current shadow map
");
    fprintf(stdout, "	n		Toggle showing scene without shadows

");
    fprintf(stdout, "	q		Exit demo

");

    // Black background
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f );

    // Hidden surface removal
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glPolygonOffset(factor, 0.0f);

    // Set up some lighting state that never changes
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    glEnable(GL_LIGHT0);

    // Set up some texture state that never changes
    glGenTextures(1, &shadowTextureID);
    glBindTexture(GL_TEXTURE_2D, shadowTextureID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
    if (ambientShadowAvailable)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB,
                        0.5f);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);

    RegenerateShadowMap();
}
A brightly lit pass is added to the previous ambient shadow pass.

Figure 18.5. A brightly lit pass is added to the previous ambient shadow pass.

Two Out of Three Ain't Bad

In Listing 18.2, you'll notice code hinging on the ambientShadowAvailable variable. The minimum requirement for the rest of this example is OpenGL 1.4 support, or at least support for the GL_ARB_shadow extension. If, however, your implementation supports the GL_ARB_shadow_ambient extension, you can cut down the amount of work significantly.

Currently, we've described three rendering passes: one to draw the light's perspective into the shadow map, one to draw the dimly lit ambient pass, and one to draw the shadow-compared lighting. Remember, the shadow map needs to be regenerated only when the light position or objects in the scene change. So sometimes there are three passes, and other times just two. With GL_ARB_shadow_ambient, we can eliminate the ambient pass entirely.

Instead of 0s and 1s resulting from the shadow comparison, this extension allows us to replace another value for the 0s when the comparison fails. So if we set the fail value to a half, the shadowed regions are still halfway lit, the same amount of lighting we were previously achieving in our ambient pass:

if (ambientShadowAvailable)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB,
                    0.5f);

This way, we also don't need to enable the alpha test.

A Few Words About Polygon Offset

Even on a surface closest to the light source, you will always discover minor differences in the floating-point values associated with the R texture coordinate and the shadow map's depth value. This can result in “self-shadowing,” whereby the imprecision leads to a surface shadowing itself. You can mitigate this problem by applying a depth offset when rendering into the shadow map:

// Overcome imprecision
glEnable(GL_POLYGON_OFFSET_FILL);
...
glPolygonOffset(factor, 0.0f);

While the depth offset will help guarantee that surfaces that shouldn't be shadowed won't be, it also artificially shifts the position of shadows. A balance needs to be struck when it comes to polygon offset usage. Figure 18.6 illustrates what you'll see if you don't use enough depth offset.

Shadow comparison imprecision looks a lot like depth buffer “Z-fighting.”

Figure 18.6. Shadow comparison imprecision looks a lot like depth buffer “Z-fighting.”

Summary

Shadow mapping is a useful technique for achieving realistic lighting without a lot of additional processing. The light's viewpoint can be used to determine what objects are lit and which remain in shadow. Depth textures are special textures designed to store the contents of your depth buffer for use as a shadow map. Eye linear texture coordinate generation is the basis for projected textures. The texture matrix can be used to reorient the texture coordinates back into the light's clip space. Shadow comparison can be used to make the distinction between shadowed and lit regions. The GL_ARB_shadow_ambient extension can be used to reduce the number of passes that must be rendered.

Reference

glAlphaFunc

Purpose:

Sets the alpha test function.

Include File:

<gl.h>

Syntax:

void glAlphaFunc(GLenum func, GLclampf ref);

Description:

This function establishes the alpha test function to be used along with a reference alpha value. When alpha testing is enabled, each fragment's alpha value is compared against this reference value according to the alpha test function's relational operator. If the comparison is not true, the fragment is discarded. This test occurs after the scissor test, but before stencil and depth testing.

Parameters:

func

GLenumThe relational operator to use for alpha test comparisons. It can be one of the following constants:

GL_ALWAYSThe alpha test will always pass.

GL_EQUALThe alpha test will pass if the fragment's alpha value is equal to the reference value.

GL_GEQUALThe alpha test will pass if the fragment's alpha value is greater than or equal to the reference value.

GL_GREATERThe alpha test will pass if the fragment's alpha value is greater than the reference value.

GL_LEQUALThe alpha test will pass if the fragment's alpha value is less than or equal to the reference value.

GL_LESSThe alpha test will pass if the fragment's alpha value is less than the reference value.

GL_NEVERThe alpha test will always fail.

GL_NOTEQUALThe alpha test will pass if the fragment's alpha value is not equal to the reference value.

ref

GLclampfThe reference alpha value against which incoming alpha values are compared. This value is clamped to the range [0,1].

Returns:

None.

See Also:

glDepthFunc, glScissor, glStencilFunc

glColorMask

Purpose:

Sets the writemask for sending colors to the framebuffer.

Include File:

<gl.h>

Syntax:

void glColorMask(GLboolean red, GLboolean green, 
GLclampf:GLboolean blue, GLboolean alpha);

Description:

This function establishes the writemask used for allowing or disallowing writes of individual color components to the framebuffer. A true value indicates that the component should be written. Otherwise, the component will remain unwritten.

Parameters:

red

GLbooleanThe writemask selector for the red color component.

green

GLbooleanThe writemask selector for the green color component.

blue

GLbooleanThe writemask selector for the blue color component.

alpha

GLbooleanThe writemask selector for the alpha color component.

Returns:

None.

See Also:

glDepthMask, glStencilMask

glCopyTexSubImage

Purpose:

Replaces part of a texture image with pixels from the framebuffer.

Include File:

<gl.h>

Syntax:

void glCopyTexSubImage1D(GLenum target, Glint 
GLboolean:level, GLint xoffset, GLint x, GLint y, 
GLboolean:GLsizei width);
void glCopyTexSubImage2D(GLenum target, Glint 
GLboolean:level, GLint xoffset, GLint yoffset, GLint
GLboolean: x, GLint y, GLsizei width, GLsizei height);
void glCopyTexSubImage3D(GLenum target, Glint 
GLboolean:level, GLint xoffset, GLint yoffset, GLint
GLboolean: zoffset, GLint x, GLint y, GLsizei width);

Description:

This function copies color or depth values out of the framebuffer, replacing some or all of the texels in a previously specified texture image. If the internal format of the texture is a color format, the color values are copied from the current GL_READ_BUFFER. If the internal format is a depth format, depth values are copied from the depth buffer.

Parameters:

target

GLenumThe target texture, which must be GL_TEXTURE_1D for glCopyTexSubImage1D, GL_TEXTURE_2D for glCopyTexSubImage2D, and GL_TEXTURE_3D for glCopyTexSubImage3D.

level

GLintThe level-of-detail mipmap array to be partially or wholly replaced.

xoffset

GLintOffset in the x direction where replaced values will start.

yoffset

GLintOffset in the y direction where replaced values will start.

zoffset

GLintOffset in the z direction where replaced values will start.

x

GLintThe x window coordinate of the left edge of the region to copy from the framebuffer.

y

GLintThe y window coordinate of the lower edge of the region to copy from the framebuffer.

width

GLsizeiThe width of the region to copy from the framebuffer.

height

GLsizeiThe height of the region to copy from the framebuffer.

Returns:

None.

See Also:

glCopyPixels, glCopyTexImage, glReadBuffer, glTexImage, glTexSubImage

glPolygonOffset

Purpose:

Sets the depth offset applied to polygons.

Include File:

<gl.h>

Syntax:

void glPolygonOffset (GLfloat factor, GLfloat units);

Description:

This function establishes the depth offset factor and units applied to polygons. The depth offset can be enabled and disabled individually for polygons in point, line, or fill mode using the tokens GL_POLYGON_OFFSET_POINT, GL_POLYGON_OFFSET_LINE, and GL_POLYGON_OFFSET_FILL, respectively. The depth offset adds or subtracts a single computed value for all the fragments of each polygon. If enabled, this occurs during rasterization.

Parameters:

factor

GLfloatA scale factor used to generate a variable depth offset by multiplying it with the maximum depth slope of the polygon.

units

GLfloatA constant depth offset, multiplied by the minimum granularity of the depth buffer.

Returns:

None.

See Also:

glDepthFunc, glPolygonMode

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset