Sheet 06: Deferred Shadows

Exercise: Shadow Mapping in a Deferred Renderer (25 points)

In the previous sheet you implemented deferred shading. The scene was first rendered into a G-buffer and the final Phong illumination was computed in a full-screen image-space pass. In this exercise you will extend that renderer with shadow mapping.

You have already used shadow maps in an earlier assignment. The idea is the same: render the scene from the light into a depth texture, then compare the lit point with the stored light-space depth. The important difference is where this comparison happens. In the older forward renderer the shadow lookup was part of the material shader while drawing the geometry. Here it happens later, in the deferred lighting shader, for the visible image pixels.

This also motivates the small 3 x 3 filter used in this exercise. A single shadow-map lookup produces hard, pixelated shadow edges. Sampling the neighboring texels as well and averaging the nine comparison results gives a softer transition. This costs more texture lookups, but in the deferred pass we shade only the pixels that made it into the final image. We no longer repeat the shadow computation for every material shader and every hidden fragment produced during the geometry pass.

The deferred shading code is already complete. Your task is to add the shadow-map pass in main.cpp and the shadow visibility computation in Shader/deferred.frag.

Task 1: Shadow Matrices (5 points)

Complete the shadow matrix setup in updateState. The shadow map is rendered from the light, so it needs its own projection and view matrices. The matrix worldToShadowMatrix should transform world-space positions into shadow texture coordinates.

The deferred shader does not store world-space positions in the G-buffer. It stores view-space positions. Therefore the shader receives viewToShadowMatrix, which transforms directly from the current camera's view space into the shadow map's texture space.

Task 2: Shadow-Map Render Pass (8 points)

Complete renderSceneToShadowMap, the shadow pass in draw, and the missing attribute connections in setupGeometry. The shadow shader is depth-only and only needs vertex positions. Render the plane and the teapot with the model-view-projection matrix of the light.

  1. Bind shadowFramebuffer with shadowMap.
  2. Set the viewport to the shadow-map resolution.
  3. Clear the depth buffer.
  4. Enable a small polygon offset to reduce self-shadowing.
  5. Render the scene from the light with renderSceneToShadowMap.
  6. Disable polygon offset and unbind the framebuffer.

Task 3: Pass Shadow Data to the Deferred Shader (4 points)

Complete the shadow-related uniforms in renderDeferredImage. The shader already receives the four G-buffer textures. Add viewToShadow and bind the depth texture shadowMap to an additional texture unit.

Task 4: 3 x 3 Shadow Lookup (8 points)

Complete shadowVisibility in Shader/deferred.frag. Transform the view-space position from the G-buffer into shadow texture space. If the projected coordinate lies outside the shadow map, return full visibility.

For visible shadow-map coordinates, perform a 3 x 3 percentage-closer filter, usually abbreviated as PCF. A normal texture filter would average depth values, but that is not what we want: the shadow test is a comparison. PCF first performs several neighboring shadow comparisons and then averages the comparison results. A sampler2DShadow returns 1 if the comparison passes and 0 if it fails. The average of the nine values is a visibility between 0 and 1.

Percentage closer filtering with nine shadow map samples
PCF averages comparison results, not raw depth values. Mixed lit and shadowed samples produce a soft transition at the shadow edge.

Sample the center texel and its eight neighbors, average the nine results, and use this average as the light visibility. Use a small bias in the depth comparison to avoid self-shadowing, but keep it small enough that the shadow does not detach from the object.

Reference Solution

Loading

D toggle G-buffer view, Space animate light, R reset
Left mouse drag rotate, Right mouse drag pan, mouse wheel zoom

Text description and demo controls

The reference scene contains a teapot and a textured ground plane with shadows. A geometry pass first stores view-space positions, normals, diffuse colors and specular strengths in the G-buffer. A separate lighting pass combines these values with a shadow map.

The shadow lookup averages nine neighboring depth comparisons in a 3 by 3 pattern. Their average determines visibility between fully shadowed and fully lit, softening shadow edges. D switches between the final lighting result and a diagnostic G-buffer view.

Focus the demo with Tab. D toggles the G-buffer view; Space starts or pauses the light animation; R resets animation, view and diagnostic mode. Camera rotation, panning and zoom currently require the mouse.