Shadow mapping
Render the scene from the light's point of view and keep only the depths. Then, while shading a pixel, ask: is anything closer to the light than I am? If yes, something is blocking the light and this pixel is in shadow. That is the entire technique, and every real implementation is a pile of fixes for the four ways it goes wrong.
The depth comparison
Transform the pixel into the light's clip space, then compare its depth against the depth stored at that position in the shadow map. The shadow pass is a cheap, depth-only draw — no materials, no lighting, often no pixel shader at all — but it is a second traversal of the scene, so your draw-call count doubles per shadow-casting light. The trouble is that the stored depth was sampled on the light's grid, not yours, so the two numbers never quite agree.
Acne, and the bias that fixes it
A surface shadows itself because one stored depth has to stand in for a whole texel's worth of surface. Half of that footprint ends up marginally farther from the light than the recorded value, so it fails its own test and you get stripes of false shadow. The fix is a bias: subtract a little before comparing.
A constant bias is not enough, because the error across a texel grows as the surface tilts away from the light. Slope-scaled bias scales it by the incidence angle; normal offset instead nudges the sample position along the surface normal, trading a depth error for a positional one.
Peter-panning
Too much bias and contact shadows detach — objects float above their own shadow. Tune the bias to the smallest value that kills the acne at your resolution, and lower it again whenever you raise that resolution. Rendering only back faces into the shadow map helps for closed, solid geometry.
Aliasing and blocky edges
One shadow texel covering many screen pixels means staircase edges, and no bias will fix it. The cure is texel density: raise the resolution, and fit the light's frustum tightly around what is actually visible. A directional light over a whole level has no single good frustum, so use cascades: several maps over nested slices of view distance, dense near the camera, coarse far away, picked per pixel by depth. An omnidirectional point light needs a cube map — six faces, six shadow passes.
Soft edges
PCF means averaging several neighbouring comparisons, not several depths. Compare first, filter after: that is what produces a fractional "in shadow" value. More taps and a wider spread give a softer edge at a fixed cost per pixel. PCSS goes further, widening the filter by the average blocker distance, so shadows harden at contact and blur with distance.
Demo
Shadow mapping in 2D — a 1D shadow map on a flat scene; drag the light, drop the resolution, and ride the bias slider from acne to peter-panning.
Gotchas
| Symptom | Cause |
|---|---|
| Stripey shadow patterns on lit surfaces | depth bias too small for the texel footprint |
| Objects float above their shadows | depth bias too large (peter-panning) |
| Chunky staircase shadow edges | too few texels per screen pixel; tighten the frustum or add cascades |
| Shadows pop or swim as the camera moves | the light frustum is refit each frame; snap it to texel increments |
| Objects stop casting when they leave the screen | shadow frustum culled against the camera, not the light |
| Everything beyond a distance is lit | outside the shadow frustum; clamp the lookup or fade out |
| Visible seams between cascades | no blend band over the cascade transition |
| Shadow cost scales badly with light count | each caster re-draws the scene; cap shadow-casting lights, cache static ones |