The Render Equation
- The single most important equation in computer graphics.
- Introduced by James T. Kajiya in 1986 — “The Rendering Equation”.
- Parent: PathTracer Learning
The Full Equation
L_o(x, ω_o) = L_e(x, ω_o) + ∫_Ω f_r(x, ω_i, ω_o) L_i(x, ω_i) (N · ω_i) dω_i
- This is a Fredholm integral equation of the second kind
- The solution is the radiance value that your eye (or camera) receives — the brightness and color of each pixel
Breaking Down Every Term
L_o(x, ω_o) — Outgoing Radiance
- The result we want to compute
x — the surface point being shaded
ω_o — the outgoing direction (toward the camera or previous bounce)
- Units: W/m²/sr (watts per square meter per steradian)
- This is the “color” of the point as seen from direction
ω_o
L_e(x, ω_o) — Emitted Radiance
- Light emitted directly by the surface (if it’s a light source)
- For most surfaces:
L_e = 0 (they don’t glow)
- For an area light:
L_e = power / (area * π) (Lambertian emitter)
ω_o dependence: some lights emit differently in different directions
∫_Ω ... dω_i — Hemisphere Integral
- Integrate over all possible incoming directions
ω_i in the upper hemisphere Ω
- The upper hemisphere = all directions above the surface normal
N
dω_i — differential solid angle (infinitesimal cone of directions)
∫_Ω dω_i = 2π steradians for the full hemisphere
- In practice: we can’t solve this analytically for most scenes → Monte Carlo
f_r(x, ω_i, ω_o) — The BRDF
L_i(x, ω_i) — Incoming Radiance
- The radiance arriving at point
x from direction ω_i
- THIS is why the equation is recursive / hard
L_i(x, ω_i) = L_o(hit(x, ω_i), -ω_i) — it’s the outgoing radiance at the next surface hit
hit(x, ω_i) — cast a ray from x in direction ω_i, find the first surface hit
L_i depends on L_o, which depends on L_i… infinite recursion
(N · ω_i) — The Cosine Term
- Lambert’s cosine law
N — surface normal at point x
ω_i — incoming light direction
cos(θ) where θ is the angle between the normal and the light direction
- When light hits at a grazing angle (
θ ≈ 90°), cos(θ) ≈ 0 — almost no energy per unit area
- When light hits head-on (
θ = 0°), cos(θ) = 1 — maximum energy per unit area
- This is a geometric fact, NOT a material property
- Implementation:
max(0.0, dot(N, omega_i)) — clamp to zero (no light from below surface)
The Recursive Nature
- The rendering equation is self-referential:
L_o(x, ω_o) = L_e + ∫ f_r * L_o(hit(x,ω_i), -ω_i) * cos(θ) dω_i
- To know the color of a point, you need the color of all other points it can see
- To know those, you need the color of all points they can see
- This is infinite recursion — direct solution is impossible for complex scenes
- Solutions:
- Path tracing — Monte Carlo: sample one random direction, recurse with finite depth + Russian Roulette
- Radiosity — only diffuse surfaces, finite element method
- Photon Mapping — trace photons from lights, then gather
- VPL (Virtual Point Lights) — represent global illumination as many tiny point lights
How Path Tracing Solves It
- Monte Carlo estimator for the rendering equation:
L_o(x, ω_o) ≈ L_e(x, ω_o) + (1/N) Σ [ f_r(x, ω_i^n, ω_o) * L_i(x, ω_i^n) * cos(θ^n) / p(ω_i^n) ]
- For
N=1 (one sample per bounce):
- Sample one random direction
ω_i from a probability distribution p(ω_i)
- Trace a ray in that direction → hit a surface → recursively evaluate
- Divide by
p(ω_i) to correct for the sampling distribution (importance sampling)
- Repeat many times per pixel and average → converges to the true integral
- Convergence rate:
O(1/√N) — slow but unbiased
- See PathTracer Learning Monte Carlo Integration
The Importance of Sampling Direction
- Naive sampling: pick
ω_i uniformly from hemisphere
- PDF:
p(ω) = 1/(2π)
- Most samples hit dark areas → high variance → noisy
- Cosine-weighted sampling: weight by
cos(θ)
- PDF:
p(ω) = cos(θ)/π
- The
cos(θ)/π cancels the (N·ω_i)/p term → estimate = f_r * L_i
- Better for diffuse — still suboptimal for specular
- BRDF importance sampling: sample proportional to
f_r * cos(θ)
With Emissive Medium (Volumetric)
- The Light Transport Equation (LTE) extends the rendering equation to participating media
- Adds a scattering integral through the volume along the ray
- Used for fog, smoke, clouds, skin subsurface scattering
L(x→y) = L_e(x→y) + ∫ f_s(x, ω_i, ω_o) L(x'→x) G(x,x') V(x,x') dA
G(x,x') — geometry term: cos(θ_x) * cos(θ_y) / |x-y|²
V(x,x') — visibility: 1 if unoccluded, 0 if shadow
- Replace
L with L_λ(λ) — spectral radiance per wavelength
- BRDF also becomes wavelength-dependent:
f_r(ω_i, ω_o, λ)
- Required for accurate dispersion (prisms), fluorescence, interference effects
- Path trace by sampling one wavelength per ray (Hero Wavelength Sampling)
Energy Balance Check
- For any point
x and any outgoing direction ω_o:
∫_Ω f_r(x, ω_i, ω_o) cos(θ_i) dω_i ≤ 1
= 1 — perfectly reflective surface (mirror), no absorption
< 1 — some light is absorbed (every real surface)
> 1 — physically impossible! Check your BRDF implementation
- Common cause of energy gain bugs: wrong
1/π factor in Lambertian BRDF
The Rendering Equation in Code
vec3 traceRay(Ray ray, int depth) {
if (depth <= 0) return vec3(0.0);
HitInfo hit = findClosestIntersection(ray);
// No hit → environment / sky
if (!hit.valid) return sampleEnvMap(ray.direction);
// L_e term — emission
vec3 L_emitted = hit.material.emissive ? hit.material.emission : vec3(0.0);
// ∫ f_r * L_i * cos(θ) dω_i (Monte Carlo, 1 sample)
vec3 omega_i = sampleHemisphere(hit.normal); // sample ω_i
float pdf = hemispherePDF(hit.normal, omega_i); // p(ω_i)
vec3 f_r = evalBRDF(hit.material, ray.direction, omega_i, hit.normal);
float cosTheta = max(0.0, dot(hit.normal, omega_i));
Ray scattered = Ray(hit.point + hit.normal * 1e-4, omega_i);
vec3 L_i = traceRay(scattered, depth - 1); // recursive call
vec3 L_indirect = f_r * L_i * cosTheta / pdf; // Monte Carlo estimator
return L_emitted + L_indirect;
}
Historical Context
- 1980 — Whitted introduces recursive ray tracing (specular only)
- 1984 — Cook et al. introduce distributed ray tracing (stochastic sampling)
- 1986 — Kajiya publishes “The Rendering Equation” — the unified framework
- 1997 — Jensen introduces photon mapping
- 2002 — Jensen introduces path tracing to production
- 2018 — Real-time path tracing via RTX / DXR / Vulkan RT
Checklist
- TODO Can write the rendering equation from memory
- TODO Understand what each term represents physically
- TODO Can explain why L_i makes it recursive
- TODO Know why the cos(θ) term appears and what it means geometrically
- TODO Understand why uniform sampling is suboptimal
- TODO Can implement the rendering equation as a path tracer in pseudocode