# ==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]]