# ==Shadow mapping==
<p class="doc-sub">// status: seedling</p>
Shadow mapping is the classic real-time shadow technique: render the scene from the light's point of view, keep only depth, then at shading time re-project each pixel into the light's clip space and compare depths. It's been in every real engine since the late 90s and its failure modes are practically a rite of passage.
# The two passes
Pass 1 — from the light:
- Build a view matrix from the light (directional: orthographic; point: six cube faces; spot: perspective with cone angle).
- Render only depth into a depth texture (`DEPTH_COMPONENT*`).
Pass 2 — during shading:
- Transform the shaded point into the light's clip space.
- Compare `currentDepth` to the shadow map depth at that texel.
- If `currentDepth > mapDepth + bias`, the point is in shadow.
```glsl
vec4 lightClip = lightVP * vec4(worldPos, 1.0);
vec3 projCoords = lightClip.xyz / lightClip.w;
projCoords = projCoords * 0.5 + 0.5; // [-1,1] -> [0,1]
float shadowDepth = texture(shadowMap, projCoords.xy).r;
float shadow = projCoords.z - bias > shadowDepth ? 0.0 : 1.0;
```
This is simple and wrong in interesting ways.
# The classic artefacts
Every one has a name and a fix:
- **Shadow acne** — diagonal-stripe self-shadowing on lit surfaces. Cause: depth resolution at glancing angles. Fix: a small ==bias== or, better, a ==slope-scale bias== that scales with surface angle. Better still: render the shadow map with ==front-face culling== so you sample the far side.
- **Peter-panning** — objects float above their shadows. Cause: too much bias. The trade-off with acne is eternal; slope-scale bias + front-face culling lets you use a much smaller constant bias.
- **Perspective aliasing** — near objects get a handful of shadow texels, far objects barely any. Fix: Cascaded Shadow Maps (below) for directional lights.
- **Projective aliasing** — surfaces parallel to the light direction have horrible shadow resolution. Fix: mostly live with it; soft filtering hides a lot.
- **Hard edges** — real shadows have ==penumbras==; shadow maps are a boolean per-texel. Fix: filtering (PCF, VSM, PCSS) or ray traced shadows.
# Cascaded Shadow Maps (CSM)
For directional lights covering a large view, you split the camera frustum into N slices (typically 3–4), fit a tight ortho projection to each slice, and render a shadow map per slice. At shade time, pick the cascade containing the pixel's view-space depth.
Gets you near-pixel-precision shadows near the camera and cheap-but-blurry shadows far away. Pitfalls:
- **Cascade seams** — switching from cascade N to N+1 shows a line. Fix: blend a few pixels across the boundary.
- **Stable projection** — as the camera moves, cascade bounds wobble, making the shadow map contents shimmer. Fix: snap the ortho projection to the texel grid ("Sample Distribution Shadow Maps" or simpler texel-snap).
- **Counting cascades is expensive** — keep N small; 3 is usually enough with good filtering.
# Point and spot lights
- **Spot** — one shadow map, perspective projection along the cone axis. Same as directional but perspective.
- **Point (omni)** — six shadow maps (a cube), or a dual-paraboloid map. Cube is standard; paraboloid is cheaper but has edge artefacts.
- **Many point lights** — a single atlas texture with tiles per light, plus a lookup to find each light's tile.
# Filtering — from hard to soft
From cheap to expensive:
- **Percentage-Closer Filtering (PCF)** — sample a small kernel (e.g. 3×3 or Poisson disk) around the projected coord, average the 0/1 shadow results. Hardware PCF (`sampler2DShadow`) does a 2×2 bilinear-filtered depth comparison for free.
- **Random rotated PCF** — rotate the kernel per pixel by a hash to turn banding into noise.
- **Variance Shadow Maps (VSM)** — store depth + depth²; compute a Chebyshev upper bound. Nice soft shadows, but suffer from ==light bleeding== near occluders.
- **Exponential Shadow Maps (ESM)** — store `exp(k·depth)`; works nicely with summed-area or standard filtering. Also bleeds at high `k`.
- **Moment Shadow Maps** — 4-moment generalisation of VSM, better quality at higher memory cost.
- **Percentage-Closer Soft Shadows (PCSS)** — kernel size scales with blocker distance. Genuinely physically motivated penumbra. Expensive.
- **Contact-hardening + screen-space** — fake PCSS on the far pixels, real PCSS near contact. Nvidia HFTS and similar.
# Beyond shadow maps
Alternatives worth knowing:
- **Ray traced shadows** — on hardware that supports it (RTX, DXR, VK_KHR_ray_tracing), cast a shadow ray per pixel per light. Denoise. Beautiful results, not cheap.
- **Signed distance field shadows** — if you have an SDF of the scene (see [[Ray marching and SDFs]]), soft shadows fall out of the march almost for free.
- **Screen-space shadows** — short-range ray march in screen depth, great for contact shadows filling in where the shadow map is too coarse.
# Biasing, properly
A couple of tricks that together dissolve most acne/peter-panning:
- **Slope-scale bias** — `bias = constant + slopeScale * tan(acos(dot(N, L)))` — the driver can apply this with `glPolygonOffset` / Vulkan `depthBiasSlopeFactor`.
- **Normal offset** — before projecting into the shadow map, push the shaded point along the surface normal by a small distance scaled by `1 - NoL`. The shadow "floats" a bit but acne goes away.
- **Front-face culling in the shadow pass** — only back faces write depth. Self-shadowing uses the far side, which is less prone to z-fighting.
Combine all three. Tune on the worst-case surface angle you'll see.
# Things that tripped me up
- **Shadow map format** — 16-bit depth aliases hard at long distances. Use `D32F` everywhere you can afford it.
- **Texel snapping in CSM** — without it, shadows shimmer. With it, they're rock solid.
- **Seam between cascades** — always noticeable at first and always fixable with a small overlap/blend.
- **Alpha-tested geometry** — leaves, grids, etc. The shadow pass needs to discard the same way the main pass does, or you get solid-block shadows from leaves.
- **`sampler2DShadow` ≠ `sampler2D`** — the hardware PCF path needs the depth texture bound as a comparison sampler. Easy to miss the bind change.
- **Orthographic frustum fitting** — too tight and shadows cull off-screen; too loose and resolution craters. Fit to the light-space AABB of the cascade's world-space corners.
# References
- _Real-Time Shadow Algorithms_ — Eisemann et al., still the best textbook.
- _Shadow Mapping: GPU-based Tips and Techniques_ — GPU Gems 1 chapter, canonical.
- _Sampling Distribution Shadow Maps_ — Lauritzen et al.
- _Percentage-Closer Soft Shadows_ — Fernando (NVIDIA) 2005.
---
Back to [[Index|Notes]] · see also [[Physically Based Rendering]] · [[Deferred vs forward rendering]]