Three.js From Zero · Article s5-04

S5-04 Deferred Rendering

Season 5 · Article 04

Deferred Rendering — separate geometry from lighting

Forward: shade every object against every light. N×M. Deferred: write geometry+material attributes to a G-buffer, then shade the G-buffer with all lights in one pass. Handles 100+ dynamic lights.

1. The problem with forward

For each mesh, for each fragment, shade against every light that affects it. 100 lights × 1M fragments × N meshes → cost explodes. Even with light culling, overdraw hurts.

2. Deferred in one paragraph

Pass 1 — Geometry pass: render scene, but instead of lighting, write out multiple targets (MRT):

  • Albedo (base color)
  • Normal (world or view)
  • Depth (linear)
  • Material (roughness, metalness, etc.)

Pass 2 — Lighting pass: for each light, draw a fullscreen quad (or light volume). Sample G-buffer, apply BRDF, accumulate.

// Geometry pass (GBuffer)
layout(location = 0) out vec4 gAlbedo;
layout(location = 1) out vec4 gNormal;
layout(location = 2) out vec4 gMaterial;
// depth auto

// Light pass (fullscreen)
vec3 pos = reconstructPosition(depth, uv);
vec3 N = decodeNormal(gNormal);
vec3 albedo = gAlbedo.rgb;
for each light: color += BRDF(N, V, L, albedo, material);

3. Live demo — 200 lights on a floor

200 point lights scattered above a bumpy surface. Emulated G-buffer + lighting in Three.js.

4. Light volumes vs fullscreen

Fullscreen quad lights every pixel regardless of light radius. Wasteful for point lights.

Better: draw a sphere mesh at each light, scaled to its attenuation radius. Only those pixels run the lighting shader. 10-50× speedup on many-small-lights scenes.

5. Tradeoffs

ProCon
Scales to many lightsMSAA hard (G-buffer pre-shade)
Decoupled material from light logicTransparency needs forward fallback
SSR/SSAO free (depth+normal)G-buffer bandwidth cost
Post effects trivialLimited BRDF variety per frame

6. Forward+ (clustered forward)

Hybrid. Pre-pass: build a 3D light grid, bin lights into frustum cells. Main forward pass: each fragment loops only the lights in its cell. Handles transparency, still scales. S5-07 dives deep.

7. When deferred still wins

  • Hundreds of small lights (torches, fires, muzzle flashes).
  • Heavy screen-space post (SSAO, SSR, volumetric) that wants G-buffer anyway.
  • Decal projection, light volumes as geometry.

Games: Killzone 2, Battlefield 3, Witcher 3, The Last of Us. Deferred is their backbone.

8. Three.js & WebGL2 support

WebGL 2 supports MRT (up to 8 color attachments). Deferred is fully possible in Three.js with custom WebGLMultipleRenderTargets. Stock Three.js is forward, but you can layer deferred as a custom pipeline.

WebGPU makes this trivial — fragment-per-target writes are first-class.

9. Takeaways

  • Deferred = split geometry from lighting into two passes.
  • G-buffer: albedo, normal, depth, material.
  • Many lights → deferred wins.
  • Transparency → forward fallback OR forward+.
  • All AAA engines use deferred or forward+ with clustered culling.