How it works...

The first block of the preceding code demonstrates how to create an FBO for our shadow map texture. The FBO contains only a single texture connected to its depth buffer attachment. The first few lines of code create the shadow map texture. The texture is allocated using the glTexStorage2D function with an internal format of GL_DEPTH_COMPONENT24.

We use GL_NEAREST for GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MIN_FILTER here, although GL_LINEAR could also be used, and might provide slightly better-looking results. We use GL_NEAREST here so that we can see the aliasing artifacts clearly, and the performance will be slightly better.

Next, the GL_TEXTURE_WRAP_* modes are set to GL_CLAMP_TO_BORDER. When a fragment is found to lie completely outside of the shadow map (outside of the light's frustum), then the texture coordinates for that fragment will be greater than one or less than zero. When that happens, we need to make sure that those points are not treated as being in shadow. When GL_CLAMP_TO_BORDER is used, the value that is returned from a texture lookup (for coordinates outside the 0–1 range) will be the border value. The default border value is (0,0,0,0). When the texture contains depth components, the first component is treated as the depth value. A value of zero will not work for us here because a depth of zero corresponds to points on the near plane. Therefore, all points outside of the light's frustum will be treated as being in shadow! Instead, we set the border color to (1,0,0,0) using the glTexParameterfv function, which corresponds to the maximum possible depth.

The next two calls to glTexParameteri affect settings that are specific to depth textures. The first call sets GL_TEXTURE_COMPARE_MODE to GL_COMPARE_REF_TO_TEXTURE. When this setting is enabled, the result of a texture access is the result of a comparison, rather than a color value retrieved from the texture. The third component of the texture coordinate (the p component) is compared against the value in the texture at location (s,t). The result of the comparison is returned as a single floating-point value. The comparison function that is used is determined by the value of GL_TEXTURE_COMPARE_FUNC, which is set on the next line. In this case, we set it to GL_LESS, which means that the result will be 1.0 if the p value of the texture coordinate is less than the value stored at (s,t). (Other options include GL_LEQUAL, GL_ALWAYS, GL_GEQUAL, and so on.)

The next few lines create and set up the FBO. The shadow map texture is attached to the FBO as the depth attachment with the glFramebufferTexture2D function. For more details about FBOs, check out the Rendering to a texture recipe in Chapter 5, Using Textures.

The vertex shader is fairly simple. It converts the vertex position and normal-to-camera coordinates and passes them along to the fragment shader via the output variables Position and Normal. The vertex position is also converted into shadow map coordinates using ShadowMatrix. This is the matrix S that we referred to in the previous section. It converts a position from object coordinates to shadow coordinates. The result is sent to the fragment shader via the ShadowCoord output variable.

As usual, the position is also converted to clip coordinates and assigned to the built-in gl_Position output variable.

In the fragment shader, we provide different functionality for each pass. In the main function, we call RenderPass, which is a subroutine uniform that will call either recordDepth or shadeWithShadow. For the first pass (shadow map generation), the recordDepth subroutine function is executed. This function does nothing at all! This is because we only need to write the depth to the depth buffer. OpenGL will do this automatically (assuming that gl_Position was set correctly by the vertex shader), so there is nothing for the fragment shader to do.

During the second pass, the shadeWithShadow function is executed. We compute the ambient component of the shading model and store the result in the ambient variable.  We then compute the diffuse and specular components and store those in the diffuseAndSpec variable.

The next step is the key to the shadow mapping algorithm. We use the built-in textureProj texture access function to access the ShadowMap shadow map texture. Before using the texture coordinate to access the texture, the textureProj function will divide the first three components of the texture coordinate by the fourth component. Remember that this is exactly what is needed to convert the homogeneous position (ShadowCoord) to a true Cartesian position.

After this perspective division, the textureProj function will use the result to access the texture. As this texture's sampler type is sampler2DShadow, it is treated as a texture containing depth values, and rather than returning a value from the texture, it returns the result of a comparison. The first two components of ShadowCoord are used to access a depth value within the texture. That value is then compared against the value of the third component of ShadowCoord.

We need to use vec3 as the lookup coordinate when using a sampler of type sampler2DShadow,  because we need a 2D position and a depth.

When GL_NEAREST is the interpolation mode (as it is in our case) the result will be 1.0, or 0.0. As we set the comparison function to GL_LESS, this will return 1.0, but only if the value of the third component of ShadowCoord is less than the value within the depth texture at the sampled location. This result is then stored in the shadow variable. Finally, we assign a value to the output variable FragColor. The result of the shadow map comparison (shadow) is multiplied by the diffuse and specular components, and the result is added to the ambient component. If shadow is 0.0, that means that the comparison failed, meaning that there is something between the fragment and the light source. Therefore, the fragment is only shaded with ambient light. Otherwise, shadow is 1.0, and the fragment is shaded with all three shading components.

When rendering the shadow map, note that we culled the front faces. This is to avoid the z-fighting that can occur when front faces are included in the shadow map. Note that this only works if our mesh is completely closed. If back faces are exposed, you may need to use another technique (that uses glPolygonOffset) to avoid this. I'll talk a bit more about this in the next section.

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

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