Global Illumination and Path Tracing

Stand a matte white card next to a bright red mug in the sun and the card's edge blushes pink. Nothing touched it; no red paint was applied. What reached the card was sunlight that had already bounced off the mug, picking up its colour on the way. That single everyday observation is the whole subject of this page. Real light does not stop at the first surface it strikes — it ricochets around a room, spilling colour, softening shadows and filling every crevice, until the scene settles into the rich, coherent look our eyes trust. Capturing all of that bouncing is called global illumination, and the cleanest way to compute it is a beautifully simple algorithm called path tracing.

This lesson builds on ordinary ray tracing — shooting rays into a scene — and turns it into a physically faithful light simulator. It is the engine behind the flagship frames in modern film and, thanks to computer animation hardware, increasingly your games too.

Direct light vs. everything else

Split the light arriving at a point on a surface into two piles. Direct lighting is what comes straight from a light source — the sun, a lamp, a window. It is easy: draw a line from the point to the light, check nothing blocks it, done. Indirect lighting is everything that reached the point after bouncing off one or more other surfaces first. That second pile is where all the visual richness hides:

Global illumination (GI) is simply the name for computing that second pile — the light that has bounced. Direct lighting alone gives you a scene lit by a torch in a black void: crisp, hard, and lifeless. Add the bounces and the scene becomes a place.

The rendering equation: one equation for all of it

In 1986 Jim Kajiya wrote down a single equation that says how much light leaves any point in any direction, and it accounts for every bounce at once. The outgoing light L_o at a point x toward the eye is the light the surface emits plus the light it gathers from every direction and reflects:

L_o(x,\omega_o) = L_e(x,\omega_o) + \int_{\Omega} f_r(x,\omega_i,\omega_o)\, L_i(x,\omega_i)\, (\omega_i \cdot n)\, d\omega_i

Read it left to right: L_e is emission (zero unless the surface is a light); the integral sweeps over every incoming direction \omega_i in the hemisphere \Omega above the surface; f_r is the BRDF, the material's rule for how much light from \omega_i scatters toward the eye \omega_o; and (\omega_i \cdot n) is the cosine term — light grazing at a shallow angle counts for less. The catch is the term L_i: the incoming light is itself the outgoing light of whatever surface you see in direction \omega_i. The equation refers to itself. Light bouncing forever is exactly that recursion, and a full treatment lives in its own lesson on the rendering equation.

Path tracing: estimate the integral by walking a path

That nested, hemisphere-wide integral has no closed form for a real scene. Path tracing solves it the way you'd estimate any hopeless integral — with Monte Carlo: don't integrate, just sample. Rather than gather light from every incoming direction, pick one random direction, follow it, and let the average of many such random walks converge to the true value. The recipe for a single pixel:

There is no separate "lighting pass". The recursion is the light bouncing. A path that happens to strike a red wall then a white floor before finding the lamp is a photon's worth of colour bleed, computed by the very same loop that does everything else. Because the sampling is done honestly — with no shortcuts that skew the average — path tracing is unbiased: push enough samples through and it converges to the exact solution of the rendering equation, ground truth with no cheats.

Following one path with your eye

Here is a single traced path through a tiny scene. A ray leaves the camera, strikes the red wall, scatters to the floor (carrying a little red with it), then bounces up and finds the light. At that moment the whole path lights up: the pixel receives the lamp's energy, tinted by every surface the path touched on the way. Trace thousands of such paths, each taking a different random turn at each bounce, and their average is the pixel's true colour.

The more bounces a path is allowed, the more indirect light it can capture — one bounce gives you direct lighting only, two bounces bring in first-order colour bleed, and deeper paths fill the subtle inter-reflections that make a room feel continuous. Each extra bounce contributes a little less, which is what makes the next trick — knowing when to stop — safe.

Russian roulette: stopping paths without cheating

Paths could bounce forever, but each bounce loses energy, so far-flung bounces barely matter. Simply capping the depth would throw away that energy and bias the result (too dark). The unbiased fix is Russian roulette: at each bounce, with probability 1-p kill the path; with probability p keep it but divide its contribution by p. The survivors are boosted by exactly the amount needed to compensate for the paths that died, so the average is left unchanged — you get shorter paths on average with no darkening.

\langle L \rangle = \begin{cases} \dfrac{L_{\text{path}}}{p} & \text{with probability } p \\[4pt] 0 & \text{with probability } 1-p \end{cases} \qquad \mathbb{E}[\langle L\rangle] = p\cdot\frac{L_{\text{path}}}{p} = L_{\text{path}}

The expectation is untouched — that little algebra is the whole reason path tracing can terminate honestly.

Next-event estimation: aim at the lights

A pure random bounce only contributes when it happens to hit a light. For a small lamp that is a rare, lucky event, so most paths carry nothing and the image is grainy. Next-event estimation (NEE) fixes this: at every bounce, in addition to the random continuation ray, send a ray directly toward a light source and, if it is unblocked, add that light's direct contribution then and there. Now every single bounce harvests some direct light instead of praying to stumble onto the source.

The random bounce is still there to gather indirect light; NEE just adds an explicit, deliberate sample of the direct term at each vertex, weighted so nothing is double-counted. It is the single biggest noise reduction in a basic path tracer — the difference between a usable image in minutes and a snowstorm.

Why it is right, but slow

Monte Carlo estimation converges, but the error falls off only as the square root of the sample count. Quadruple the paths per pixel and the noise halves:

\text{noise} \;\propto\; \frac{1}{\sqrt{N}}

That 1/\sqrt{N} law is the tyranny of path tracing. Going from 100 to 400 samples halves the grain; halving it again needs 1600. Cutting noise by 10× costs 100× the work. This is why raw path-traced frames look speckled at low sample counts, and why so much engineering — the Monte Carlo sampling and noise lesson goes deep on it — is about squeezing more convergence out of each sample or cleaning up the residue with a denoiser.

Harder cases and the hardware that saved it

Some effects are stubborn for a plain camera-side path tracer. Caustics — light focused through a wine glass onto a table — require a path that leaves the light, refracts through glass, and lands on the exact spot the eye is looking at; the odds of a camera path randomly finding that route are tiny. Specialised solvers exist for these:

For decades all of this was strictly offline — minutes to hours per frame. Then GPUs grew dedicated ray-tracing cores (NVIDIA's RTX and friends) that intersect rays against the scene in hardware, tens of billions per second, paired with AI denoisers that turn a handful of noisy samples into a clean frame. Path tracing at interactive rates — unthinkable in 2010 — now ships in real games. The algorithm never changed; the silicon caught up.

Worked example: trace one camera path

Follow a single path through a Cornell-box-style scene and see how it earns the pixel its colour. Take a diffuse red wall with reflectance \rho = 0.7.

Step 1 — camera ray. A ray leaves the eye through the pixel and hits the red wall at point x_1. No light is emitted here, so the wall contributes nothing on its own — we must find where its light comes from.

Step 2 — sample a bounce. The wall is diffuse, so we sample a new direction with the cosine-weighted distribution the BRDF prefers: directions near the surface normal are more likely, directions near grazing less so. This importance-sampling choice makes the estimator f_r \cdot (\omega_i\cdot n) / \text{pdf} collapse neatly to the constant albedo \rho — the cosine and the \pi cancel:

\text{throughput after bounce} = \frac{f_r\,(\omega_i\cdot n)}{p(\omega_i)} = \frac{(\rho/\pi)\cos\theta}{\cos\theta/\pi} = \rho = 0.7

Step 3 — continue. The bounced ray travels to the white floor at x_2, another diffuse hit; multiply the running throughput by the floor's albedo (say 0.9), giving 0.7 \times 0.9 = 0.63.

Step 4 — reach a light. The next bounce (or an NEE shadow ray) strikes the lamp, radiance L_e = 12. The path's contribution is the light's energy scaled by everything it passed through:

L_{\text{path}} = 0.63 \times 12 = 7.56

Step 5 — average. One path is a wild, noisy guess. Fire N paths through the same pixel, each taking different random turns — some reach the light in one bounce, some in three, some never — and average them:

\text{pixel} \approx \frac{1}{N}\sum_{k=1}^{N} L_{\text{path}}^{(k)}

As N grows the average settles onto the true radiance, red bleed and all. Allowing deeper paths lets more of the faint indirect light register; a two-bounce cap already gives you the wall's colour on the floor for free — no ambient hack required.

The algorithm is a page of code, but the 1/\sqrt{N} cost is brutal, and for years studios used cheaper, biased tricks (irradiance caching, photon maps, "final gather") that looked good enough for far less compute. The tipping point came around the 2010s: as scenes grew to billions of polygons, the bookkeeping of those clever caches became more expensive and more fragile than just brute-forcing paths. Arnold, the renderer behind a huge fraction of modern blockbusters, won by being unbiased and boringly predictable — an artist could throw samples at a noisy frame and know it would converge, with no cache to tune. Simplicity, once hardware was fast enough, beat cleverness.

The GPU rasterizer that draws most real-time graphics answers one question per pixel — "what surface is visible here, and how is it lit directly?" It has no notion of a ray bouncing to a second surface, so it captures none of the indirect pile: no colour bleed, no true soft fill, no honest ambient occlusion. Left alone, rasterized frames look flat and plasticky — the tell-tale "old CGI" look. Everything indirect must be faked: a flat ambient term to lift the blacks, baked ambient-occlusion maps for the creases, pre-computed light probes or lightmaps for bounce colour. These approximations are fast and often convincing, but they are guesses, and they break when the scene moves.

Path tracing gets every one of those effects for free — they fall out of the same bounce loop — but charges you in samples: at low counts the image is noisy, so you lean on the denoising that comes next. The trade is real: rasterization is fast but faked; path tracing is faithful but hungry.