One integral describes all of light transport
Rasterization — the technique behind real-time games — draws triangles fast and fakes lighting with a pile of separate tricks: shadow maps, ambient occlusion, baked light probes, screen-space reflections, each solving one narrow problem. Path tracing takes a completely different approach: it directly evaluates the physical equation that governs how light actually moves through a scene, first formalised by James Kajiya in 1986 as the rendering equation.
L_o(x, ω_o) = L_e(x, ω_o) + ∫_Ω f_r(x, ω_i→ω_o) · L_i(x, ω_i) · (ω_i · n̂) dω_i L_e = the surface's own emission f_r = the BRDF (how the surface scatters light) L_i(x, ω_i) = L_o(x', −ω_i) — recursive: incoming light IS outgoing light elsewhere ω_i · n̂ = cos θ_i, the Lambertian cosine term; Ω = the hemisphere above the surface
Because L_i on the right depends on L_o at some other point, the equation is recursive — unrolled, it is an infinite sum over paths of every length: direct light, one bounce, two bounces, and so on. Every visual effect a renderer might want — soft shadows, colour bleeding, caustics — is already contained in this single integral. The entire challenge of path tracing is estimating it efficiently.
Estimating an integral by rolling dice
The hemisphere integral has no closed form for an arbitrary scene, so it is approximated with Monte Carlo integration: draw N random directions, evaluate the integrand at each, and average.
∫ f(x) dx ≈ (1/N) Σ f(xᵢ) / p(xᵢ) xᵢ drawn from probability density p Unbiased: E[estimate] = the true integral, for any N Standard deviation ∝ 1/√N → 4x more samples only halves the visible noise
That 1/√N convergence is the defining constraint of the whole field: it is why a freshly started path-traced render looks grainy and only slowly cleans up, and why so much research effort goes into either reducing variance per sample (importance sampling, next event estimation) or denoising the result rather than brute-forcing more samples.
The BRDF: how a surface actually scatters light
The f_r term — the BRDF — describes a material. A perfectly matte (Lambertian) surface has the simplest possible BRDF, f_r = albedo/π, scattering incoming light equally in every direction. Real materials — brushed metal, plastic, skin — are usually modelled with a microfacet BRDF such as GGX, which treats the surface as a statistical field of tiny mirrors and combines a normal distribution function, a geometric shadow-masking term and Fresnel reflectance into one physically-grounded reflection lobe ranging continuously from a sharp mirror to a soft, glossy highlight.
Tracing one path from the camera
The core loop fires a ray from the camera, bounces it off whatever it hits according to the surface's BRDF, and accumulates emitted light along the way, weighted by a running throughput term that tracks how much of the original light survives each bounce:
tracePath(ray, maxDepth = 8):
throughput = 1; radiance = 0
for depth in 0..maxDepth:
hit = scene.intersect(ray); if !hit: break
radiance += throughput * hit.material.emission
(wi, pdf, brdfVal) = hit.material.sample(hit.normal, ray.dir)
throughput *= brdfVal * cos(hit.normal, wi) / pdf // BRDF * cosine / pdf
ray = { origin: hit.point, dir: wi }
return radiance
Choosing wi uniformly over the hemisphere would waste most samples on directions that contribute little. Importance sampling instead draws wi from a distribution shaped like the integrand — for a Lambertian surface, cosine-weighted hemisphere sampling picks directions proportional to cos θ, which conveniently cancels the cosine term in the throughput update entirely.
Stopping without bias: Russian roulette
Capping path length at a fixed depth is simple but introduces systematic bias — long light paths (through glass into a bright light, say) are cut off every time and their contribution is lost entirely. Russian roulette terminates a path randomly instead, with survival probability q — often the luminance of the current throughput — and rescales any surviving path by 1/q. Because E[(throughput/q) · q] = throughput, the estimator stays unbiased even though most individual paths are cut short, which lets a renderer bound its worst-case cost without systematically darkening the image.
Frequently asked questions
Why does path tracing produce noisy images at low sample counts?
Path tracing estimates the rendering equation's integral with random Monte Carlo samples, and the standard deviation of that estimate falls off proportional to 1/√N. Quadrupling the number of samples per pixel only halves the noise, which is why a converged image typically needs hundreds to thousands of samples unless denoising or smarter sampling is used.
What is Russian roulette and why doesn't it bias the result?
Russian roulette randomly terminates a light path with probability 1−q, and rescales the surviving path's contribution by 1/q. Because the expected value of (contribution/q) × q equals the original contribution, the estimator stays unbiased on average even though individual paths are cut short — which lets a renderer cap path length without systematically darkening the image.
Why does path tracing get global illumination and soft shadows for free?
Rasterization approximates lighting with separate hacks — shadow maps, ambient occlusion, baked light probes — bolted on individually. Path tracing instead directly evaluates the rendering equation by following randomly sampled light paths through the scene, so indirect bounces, colour bleeding and soft area-light shadows emerge automatically from the same physical integral, with no special-case code for each effect.
Try it live
Watch Path Tracing accumulate one Monte Carlo sample per frame on a classic Cornell box scene, and see soft shadows, colour bleeding between the walls, and caustics gradually resolve out of the noise as the sample count climbs.
▶ Open Path Tracing simulation