Baked + dynamic shadows: when to split

Most of a room never moves. Its shadows only need computing once, if the light doesn't move either, and only the things that move need rays every frame. That's the static / dynamic split every game engine with lightmaps uses. This page does it in the browser, two ways, next to plain "ray trace everything every frame", so you can see what the split buys and what it costs.

Try this

Watch the table and chair legs where they touch the rug, and the gold sculpture's shadow on the wall (it's the only thing that moves). Then drag the light size: the split modes have to re-bake.

Shadows: ...Reflections: ...
Shadows
Light size
Look where the table and chair legs touch the floor.
Static bake
baking...
Reflections
Floor gloss
roughness of the floor and table top
Resolution
Camera

What it costs on your GPU

measuring...

Measured live from GPU timestamp queries for the modes selected above, at 960x540 (1080p has 4x the pixels). Passes a mode doesn't need aren't run. GPUs overlap passes, so each row is an estimate. The frame time is measured directly.

The idea

A shadow at a pixel asks: can this point see the light? Ray tracing answers by shooting rays at the light and checking whether anything is in the way. Split the "anything" in two:

Multiply the two and you have the full shadow. The static part comes out like the reference (converged, stable, sharp at contact points), and the per-frame rays only test the few moving objects, however big the rest of the scene is.

Three ways to get the static part

The "here" numbers: whole GPU frame on an Apple M-series GPU at 960x540, with reflections set to "env map only" so only the shadows differ. The table above measures your GPU live.

No split: trace everything every frame

1 ray per pixel against the whole scene, every frame, denoised by FFX. This is the shadows demo.

good
No preparation. The light, the camera and every object can move.
bad
One noisy ray, so the denoiser has to rebuild everything, contact details included, and fine detail comes out softer. Ray cost grows with the whole scene's size.
here
3.1 ms GPU frame (rays 1.8 ms, FFX 0.7 ms).

Bake a lightmap (+ OIDN)

Give the static meshes a second UV set (an automatic unwrap), draw them flat into a 2048x2048 texture, and trace 16 rays per texel. 16 rays is still noisy, so the OIDN denoiser cleans the lightmap once. Compare "baked raw" with "baked (OIDN)".

good
Converged, rock-steady static shadows for the price of a texture lookup. OIDN turns a bake that would need thousands of rays per texel into 16 + one denoise.
bad
The light must stay put: move it and you re-bake. Needs UVs (an unwrap step), a texture big enough for the detail (here about 1 texel per cm), and memory (32 MB for this one). Moving objects don't get the baked part: they trace everything.
here
Unwrap ~105 ms, bake ~70 ms, OIDN ~310 ms, once. Then 3.4 ms GPU frame, or 0.56 ms with the lightmap alone.

A world-space cache

No UVs and no bake step. The visible surfaces are bucketed into small cells in world space; each cell collects rays as the camera sees it, until it has 512, then just answers lookups. Light changes empty it.

good
Works on any geometry (user models, CAD, scanned data), fills in only what you actually look at, and recovers by itself when the light moves.
bad
Keeps tracing while cells fill, and the cell grid has to be hidden with jitter + the denoiser. More moving parts than either other option.
here
3.9 ms GPU frame while it's in use, 24 MB for the table.

So when would I use it?

Use a split when

  • The lights don't move (or move rarely, like a time-of-day change you can re-bake behind a fade): archviz, interiors, most game levels, product stages.
  • The scene is big and mostly static: rays against a few moving props are far cheaper than rays against a million-triangle level.
  • Static shadow quality matters: thin contact shadows, no temporal lag or smearing, nothing for the denoiser to get wrong.
  • Nothing moves at all: then skip the dynamic half and use the lightmap on its own, at almost no cost (0.56 ms here).

Don't bother when

  • The light moves (a flashlight, a draggable sun, an editor): every move means a re-bake. Trace every frame with FFX instead.
  • The scene is small, like this one: ray tracing all of it is already cheap, so the split costs about the same (3.4 vs 3.1 ms) and adds a bake, UVs and memory.
  • You can't unwrap UVs (user-uploaded models): use the world cache, or no split.

Honest result: in this room the split is a quality win (sharper, steadier static shadows), not a speed win. The speed win needs a scene where the static part is most of the triangles.

Where the two denoisers fit

This page uses both kinds the package offers. OIDN (Denoiser) denoises the lightmap once: a whole image, no history, a few hundred ms, done at bake time. FFX (ffxShadows()) denoises the moving objects' shadows every frame: one signal, motion vectors and history, under a millisecond. Bake with the first, run live with the second.

starting...