Screen-space reflections (SSR)
A polished floor often reflects a baked cubemap, so a rolling ball never shows up in it. Screen-space reflections march rays through the depth buffer and reuse the frame already drawn.
The problem
Reflection probes are captured once, from one point. Anything that moves, and anything the probe didn't see, is missing from the reflection. The depth buffer and the colour buffer of the current frame already describe everything on screen.
How it works
The source is a Unity URP renderer feature. Its PBR Accumulation mode runs three passes per frame:
- Hit: for each glossy pixel, rebuild the world position from depth and pick one reflection direction from the GGX distribution of its roughness. Rougher surfaces scatter the rays wider.
- March the ray in world space. The step starts at 0.2 m and grows by 1.33x each step, up to 64 steps. Once the ray goes behind the depth buffer, it halves the step and walks back (binary search). A hit counts if the ray is less than the thickness (0.25 m, growing with the step) behind the surface.
- Resolve: read the colour at the hit point, weight it by a Fresnel term and the surface's reflectivity, fade it near the screen edge and on surfaces below a smoothness of 0.6.
- Temporal denoise: reproject last frame's result, clamp it to the colour range of the pixel and its 4 neighbours, and blend in 75% of it. The blend drops as the camera moves.
What I hit
The source also has a cheaper mirror-ray mode. I ported the PBR path because the floor has two finishes: mirror-polished tiles (smoothness 0.95) and every third tile honed (0.72). On the honed tiles one ray per pixel is plain noise until the temporal pass averages it.
Rays that leave the screen find nothing. In the tilt-down shot the top of the hall leaves the frame and its reflection is cut off. The magenta view marks every glossy pixel whose ray missed.
Unity provides motion vectors. I compute camera-only motion from depth and the previous view-projection, so the moving ball's own motion is not tracked; the colour clamp limits the smear.
My build
three.js (r187), WebGL2 fragment passes. A G-buffer pass writes normal, smoothness and specular colour; then hit, resolve and temporal passes at 1920 x 1080. Settings are the source's High preset and defaults. Scene: a 64 m hall with 20 pillars, ceiling light panels, neon strips, a glowing ball and a cube.
Trade-offs
- Correct reflections of moving objects at the cost of a few full-screen passes
- Contact reflections line up exactly with the geometry
- Only what is on screen can be reflected; off-screen and hidden surfaces fall back to nothing here
- One ray per pixel needs the temporal pass, which lags on fast motion
- Thin objects can be missed by the thickness test
- Rendered on a software GPU, so no timings
When to use it
Wet streets, polished floors and water, where the reflected objects are usually on screen too. Pair it with probes for the parts that leave the frame.
Source and credits
Technique source: UnitySSReflectionURP by jiaozi158, https://github.com/jiaozi158/UnitySSReflectionURP, MIT license, commit 555d94651b8ec61f8795ed5018301989dfef315f. Ported to three.js and GLSL from its screen-space reflection shader and URP renderer feature: the PBR Accumulation passes (GGX importance-sampled ray per pixel, world-space ray march with growing steps and binary search, thickness test, Fresnel and reflectivity blend, screen-edge and smoothness fades) and the temporal accumulation pass (neighbour colour clamp, 75% history blend). The High quality preset and default settings come from the source. No files were copied unchanged.
Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, branch dev at commit fa01dd6ad164c48554f1bb9456f6500db052ec4f. Used for rendering and render targets. It is not the source of the reflection technique.
My own code: the G-buffer pass, camera-only motion vectors, the procedural hall, tiled floor, lights and moving objects, the split views, the missed-ray view, labels and video. No external models or assets. No IA model.