Shadow Mapping

In this page I will show you how to implement some variants of the Shadow Mapping technique in OpenGL.

The ultimate resource in real time shadows is the well named book “Real-Time Shadows”

Consult this page  to download the base code, or start from the code you wrote during Lesson 2.  You can implement this technique using either Forward Shading or Deferred Shading.

Shadow Mapping

Shadow Mapping

Rendering a shadow map

The shadow mapping technique consists in  comparing a depth stored in a depth buffer computed from the light point of view, to a depth computed in screen space and then projected to light space.

Build a scene looking like this, with a single spot light aimed to the ground.

shadowgen

Create a new Framebuffer Object.

Create a renderbuffer   of dimensions 512×512 and attach it to the framebuffer. We use a renderbuffer instead of a texture because only depth will be used for the rest of the technique.  If you use a version of OpenGL greater than 4.1, you can build a framebuffer without any color buffer/texture attached to it. Check your framebuffer completion.

Create a depth texture of dimensions 512×512. Use the parameter GL_CLAMP_TO_BORDER in order to control the value of texture() calls outside of normalized texture coordinates.

Attach the texture to the framebuffer.

Test the validity of your framebuffer and revert back to the default framebuffer.

Create a new minimal fragment shader or pass-through shader, it will be used only for rendering depth.

For the vertex shader it is important to use the same than for the rendering of the scene from the camera point of view in order to render the same scene from the light point of view.

Now you need to render the scene from the light point of view. This rendering pass should happen before computing the illumination.

Compute needed transform matrices.

Render the scene.

Display your depth texture as an overlay using a blit shader. Modify or animate the light position to verify your results.

Using a shadow map

Add an uniform sampler2D in the lighting shader.

Bind the shadow map as a texture in the lighting pass.

Display it in the lighting shader to verify your progression.

shadowviz

Pass the matrix used to transform from world space to light space to your lighting shader.

In the shader apply this transform to the world position and display the z coordinate and display the result.

depthlight

Use the xy coordinates to read from the depth texture and display the result.

shadowdepth

Display the difference between projected z and the value read from the depth texture.

shadowdiff

You can now compare both values to light or not your scene. Identify the visual artifact (probably) appearing in your renders and present on the image below.

shadowcomp

To correct this artifact,  you need to apply a bias to the depth test. This bias depends from the depth texture precision, and from surface to camera distance. Test multiple values by tweaking it in the UI.

shadowbias

Vary the resolution of your depth texture from 128×128 to 4096×4096 and compare the differences in quality and performance. Observe the difference on the limits of your shadows in the case of an animated light or an animated scene.

2×2 Percentage Closer Filtering

The OpenGL API and all current graphic cards supports a dedicated filtering for shadow maps.

Add the two following parameters to your depth texture, be aware that with those parameters you cannot read the texture using simple texture() glsl calls.

Modify the shadow map sampler2D declaration in your lighting shader.

Instead of the texture() function you now have to use the textureProj() function. This function returns a value between 0 and 1.

Arbitrary Percentage Closer Filtering

To improve shadow quality, a possible solution is to test visibility multiple times for each fragment while jittering  the shading position.

A fragment can now be in penumbra if it is not occluded in all samples.  Tweak the SampleCount and Spread parameter using the UI.

shadowpcf