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.
- Bind
shadowFramebufferwithshadowMap. - Set the viewport to the shadow-map resolution.
- Clear the depth buffer.
- Enable a small polygon offset to reduce self-shadowing.
- Render the scene from the light with
renderSceneToShadowMap. - 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.
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
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.