vidar.games

Screen-space global illumination (SSGI)

2026-11-09 ยท Discuss on X

A spotlight hits a red wall and only the wall gets lit. Screen-space GI shoots rays from every pixel, reads the colour they hit on screen, and spills that red onto the floor, boxes and ceiling.

The problem

Direct lighting stops at the first surface. Real light bounces, so a lit red wall tints everything near it. Baked lightmaps capture that, but only for static lights and geometry. Here the light moves every frame.

How it works

The source is a Unity URP renderer feature. It traces screen-space hemisphere rays, not voxel cones, light cascades or baked data:

  1. Trace, at half resolution: from each pixel, 2 rays in random cosine-weighted directions around the normal.
  2. March each ray against the depth buffer: 4 tiny steps (1.5 cm), 8 medium steps (5 cm), then 40 cm steps, 32 steps in all. A hit counts within 10 cm behind the surface.
  3. A hit reads last frame's final image at that point, so light keeps bouncing over frames. Brightness is clamped to cut fireflies.
  4. Denoise: reproject the history and average up to 8 frames, then blend 94% history. A 3x3 depth- and normal-aware blur with random spacing follows.
  5. Combine: indirect light times the surface colour, added to direct light.

What I hit

Two rays per pixel is mostly noise on its own. Where the history can't be reprojected (a cut, or a surface just uncovered), the source switches to 4 rays with a shorter, coarser march.

Turning away shows the limit. With the red wall off screen, rays that would hit it leave the frame and find nothing, so the red on the boxes fades. The source falls back to reflection probes and sky there; this room has neither, so misses return black.

My build

three.js (r187), WebGL2 fragment passes, 1920 x 1080 with GI at 960 x 540. A G-buffer pass writes colour and normals. I ported the trace, temporal reprojection, spatial blur and combine passes with the source defaults. Two extra denoise passes in the source (a Poisson-disk blur and temporal stabilization) are not ported. Room: 6.4 x 9.2 m, 4 m high, one moving spotlight plus a dim fill light.

Trade-offs

When to use it

Interiors with moving lights or opening doors, layered over a baked or probe fallback for whatever is off screen.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique source: UnitySSGIURP by jiaozi158, https://github.com/jiaozi158/UnitySSGIURP, MIT license, commit 8450297537b658218be1abbc973e9240a2aab71c. Ported to three.js and GLSL from its screen-space global illumination shader and URP renderer feature: the hemisphere ray trace with adaptive steps and reuse of the previous frame's image, the brightness clamp, the temporal reprojection (up to 8 samples, then 94% history), the edge-avoiding spatial blur and the combine pass. Step sizes, sample count and other defaults 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, shadows and render targets. It is not the source of the GI technique.

My own code: the G-buffer pass, camera-only motion vectors, the procedural room, boxes and lights, the split views, labels and video. No external models or assets. No IA model.