Computer Graphics

Assignment 7 – Shadows & Cube mapping

Martin Heistermann and Valentin Wyss
Computer Graphics Group

Review of Phong Lighting with Shadows

images/practical_08/phong_lighting_diagram.png

\[ I = I_{a}m_{a} + \sum_{l} \text{shadow}_l(\vec{p}) * \left( I_{l}m_{d}(\mathbf{n}\cdot \mathbf{l}) + I_{l}m_{s}(\mathbf{r} \cdot \mathbf{v})^{s} \right) \\ \quad \text{with shadow}_l(\vec{p}) \;=\; \left\{\begin{array}{ll} 1 & \text{light $l$ is visible from } \vec{p},\\ 0 & \text{light $l$ is blocked from } \vec{p} \end{array} \right. \]

Review of Shadow Rays

images/lighting-shadow-1.svg images/lighting-shadow-2.svg

  • Shadows from Assignment 2
    • Cast “shadow ray” from point to light
    • Check for other intersections appearing before the light
  • An equivalent test:
    • Cast ray from light to point
    • Check for intersections closer than the point

Shadow Mapping

  • Casting light rays into the scene is just like OpenGL rendering
    • Instead of lighting the fragments, calculate their intersection distances
    • Write these distances to a “shadow map” texture:
images/practical_08/shadow_map_figure_left.png images/practical_08/shadow_map_figure_right.png
Akenine-Möller, “Real-Time Rendering”

Shadow Mapping

  • Test for shadows while lighting a fragment:
    • Determine where ray from light to fragment pokes through the shadow map image plane
    • Sample shadow map at these texture coordinates to read light ray’s intersection distance
    • Compare as in Assignment 2
  • This is a constant time operation!
images/practical_08/shadowmap_lookup.png

Cube Shadow Mapping

  • What about omnidirectional point lights?

images/practical_08/omnidirectional.jpg images/practical_08/shadowmap_lookup.png ??

  • We can’t just render a single shadow map image…

Cube Shadow Mapping

  • Instead, we render 6 shadow maps, one for each face of a cube surrounding the light:
images/practical_08/shadow_cube_cropped.svg

Cube Shadow Mapping

  • Instead, we render 6 shadow maps, one for each face of a cube surrounding the light:
  • Example implementation:
images/practical_08/cubeshadowmap_demo.png

Cube Mapping

  • By creating our shadow maps on cubes, we can use GPUs’ cube mapping functionality.
    • Sampling a cube map at 3D coordinates \((s, t, r)\) give us the value where this vector pokes through the cube.
    • This is exactly the operation we need to determine where light rays pierce the shadow map image plane:

images/practical_08/cubemap_sample.png images/practical_08/shadowmap_lookup.png

Multi-Pass Rendering

  • To avoid storing a cube map textures for each light, render each light’s contribution in a separate pass
    (Re-use a single shadow map)
images/practical_08/multipass_figure.svg

Multi-Pass Rendering Pseudocode

  • For each light:

    1. Render scene for each face of the cube surrounding the light
      • Store Euclidean distance to fragments in the shadow cube map
    2. Render scene from eye point. For each fragment:
      • Sample the shadow cube map with the vector pointing from the light to the fragment (in eye space)
      • If distance fragment-to-light > dist stored in map:
        • Point is in shadow; skip
      • Otherwise:
        • Accumulate diffuse and specular components to output image

Multi-Pass Rendering in OpenGL

  • Just render the scene multiple times with appropriate settings
  • Some important details:
    • Configure the depth buffer test to allow the frontmost fragments to be redrawn in each pass.
      • We already did this for you: glDepthFunc(GL_LEQUAL);
    • Configure the blending mode to make fragment color outputs add to the final image’s color

Task 1: Light View and Projection Matrices

  • Construct the view and projection matrices for rendering the shadow map for a given light and cube face.
images/practical_08/shadow_cube_cropped.svg
  • Same projection matrix for all lights/faces:
    • Determine the fovy and aspect ratio parameters that produce the viewing frustum shown above.
    • You can use near = 0.1, far = 6.

Task 1: Light View and Projection Matrices

  • The cube (and camera) need to be oriented in eye space, so that the cube map can be sampled with your light ray vector computed in eye space.
images/practical_08/depth_test.svg

Task 1: Light View and Projection Matrices

  • You need a different view matrix for each cube face
    • Use mat4::look_at(vec3 eye, vec3 center, vec3 up)
    • Camera must point through the cube face and have correct up vector
      (\({\bf y}_{LC}\) in this figure):
images/practical_08/shadow_cube_faces_and_light_camera_coord_systems.svg
images/practical_08/Cube_map.svg
cube map wikipedia

Debugging Light View/Projection Matrices

  • Disable backface culling: glDisable(GL_CULL_FACE);
    • Allows to see debug boxes around lights from the inside
  • If you press the ‘C’ key and then repeatedly press the ‘F’ key, you should see the following images in sequence:
images/practical_08/debug_sequence.svg
  • Textures appear horizontally flipped because we are viewing from inside the cube.
  • However, the axes shown still point in the correct direction!

Task 2: Drawing the Shadow Map

  • Edit shadowmap_gen.frag to output the Euclidean distance form the light to each fragment rendered.
  • Now you should see the following images for neutral.off if you press ‘C’ twice and cycle through with the ‘F’ key:
images/practical_08/debug_sequence_shadowmap.svg

Task 3: Phong Shading with Shadows

  • Edit phong_shadow.frag to compute the diffuse and specular light contributions
  • Perform all calculations in eye space
  • Query the shadow cubemap with the light ray to read a distance value: texture(shadow_map, light_ray).r

Questions?