Reflection probes give every room its own reflections for almost nothing per frame: one probe per room,
one in the doorway. They can't show what moves, where things touch, or what the probe never saw. SSR and
one traced ray per pixel, denoised by ffxReflections(), cover that. The scene is the two rooms
from Ben Houston's reflection probes example for three.js, with a polished floor. Step through the
situations below; drag the divider to compare, drag the view to look around.
Pick a situation
Reflections: ...
Left side
Right side
Animate room
the lights drift through the hues, the knot turns
Live capture
on: the probes are re-baked every frame the room changes. Off: they show the room as it was captured.
Mirror ball
Reflection denoiser
Roughness cutoff
rougher than this: probes only, no SSR, no rays
Floor gloss
roughness of the floor
What it costs on your GPU
measuring...
Measured live from GPU timestamp queries for the modes selected above, in ms per frame at 960x540, averaged
over the last second. GPUs overlap passes, so each row is an estimate; turn the right side off and compare the
frame time between modes, or live capture on and off. The re-bake is also timed once at load, in bursts with the render loop stopped.
How it fits together
Probes: the base layer
A probe is six renders of the room from one point (a cubemap), pre-blurred for each roughness. Each probe has
a box: surfaces inside it use it, and its reflections are projected onto the room's walls. Smaller boxes win
where boxes overlap, so the doorway probe takes over between the rooms, and the edges blend.
Every pixel gets its own room's reflection, at any roughness, with no noise and no rays.
The probes also light the materials: their diffuse ambient comes from the room's probe.
They are snapshots. When the lights change, they are stale until re-baked. Here a re-bake of all three is about 2 ms, so with "Live capture" on the page re-bakes them every frame the room changes.
SSR and rays: what probes can't do
Below the roughness cutoff, each pixel takes one random direction in its reflection lobe per frame. SSR looks
for it in the frame already rendered. Where SSR finds nothing, a ray is traced against the scene's triangles
(a BVH from three-mesh-bvh) and shaded at the hit, with the probes as the hit's ambient light.
ffxReflections() turns the one noisy sample per pixel into a clean image.
Moving objects: the mirror ball is kept out of the probes, and still shows up in the floor.
Contact and furniture: reflections start where things touch the floor.
Rough surfaces stay on the probes: one sample there is mostly noise, and the blur hides the probe's errors.
How this demo is built
Probes: our port of three.js #35046 to three r186 (see reflection probes on r186). The materials get them as scene.environmentNode; the reflection passes sample them at any point with sampleReflectionProbes().
The room animates as in the PR's example: the lights drift through the hues and the knot turns. Both are in the captures; the mirror ball is hidden during a bake, like the moving objects in engines.
SSR: three's SSRNode. Rays: three-mesh-bvh's WebGPU BVH, refitted every frame for the ball and the turning knot. Hits get direct light from both lamps (one shadow ray each), no highlights.
The same pipeline as the one-room reflections explainer, which compares each method on its own.
When to add what
Rooms, corridors, interiors: a probe per space, plus one at each opening. Cheap, and right for everything rough and everything on the walls.
Glossy floors, things that move: add SSR. It is cheap and exact for what is on screen.
Mirrors, contact, anything off screen: add rays where SSR misses, and a denoiser.
Lights that change: re-bake on change if you can afford it, or a probe per frame; SSR and rays stay live either way.