Archived source from link
- Version 0: brute force recursive sampling
- Version 0.5: Russian Roulette
- Version 0.75: BRDF sampling
- Version 1.0: direct illumination
- Version 1.0m: direct by multiple importance
- Version 1.1: sharing the BRDF ray
Version 0: brute force recursive sampling
The idea of path tracing is to use Monte Carlo to compute the illumination integral for a surface point,
but to make a recursive call to get all radiance incident on the surface rather than just the direct radiance.
\[L_r(x,w)=\int_{H^2}f_r(x,w,w')L_i(x,w')d\mu(w')\]- integrand: $f_r(x,w,w’)L_i(x,w’)$, recursive here!
- probability: $d\mu(w’)$, uniform $\frac{1}{\pi}$
- estimator:
// estimator
rayRadianceEst(x, ω):
y = traceRay(x, ω)
return emittedRadiance(y, –ω) + reflectedRadianceEst(y, –ω) // recursive
// estimator
reflectedRadianceEst(x, ωr):
ωi = uniformRandomPSA(n(x))
return π * brdf(x, ωi, ωr) * rayRadianceEst(x, ωi)
Version 0.5: Russian Roulette
When we are evaluating the integral we:
- replace a fraction of the samples with zero (i.e. terminate some paths) and
- increase the weight of the remaining samples to preserve the mean.
proof:
$g(x)$ is an estimator for L,
\[g'(x) = \begin{cases} \frac{1}{q}g(x), \; with \; probability \; q \\0, \; with \; probability \; (1-q)\end{cases}\] \[E\{g'(x)\}=q \cdot (\frac{1}{q}g(x)) + (1-q) \cdot 0 = g(x)\]rayRadianceEst(x, ω):
y = traceRay(x, ω)
return emittedRadiance(y, –ω) + reflectedRadianceEst(y, –ω)
reflectedRadianceEst(x, ωr):
if random() < survivalProbability:
ωi = uniformRandomPSA(n(x))
return π * brdf(x, ωi, ωr) * rayRadianceEst(x, ωi) / survivalProbability
else
return 0
Version 0.75: BRDF sampling
We can improve things by doing importance sampling according to the BRDF rather than the uniform projected solid angle sampling.
Replace dividing $1/\pi$ with $1/pdf$.
rayRadianceEst(x, ω):
y = traceRay(x, ω)
return emittedRadiance(y, –ω) + reflectedRadianceEst(y, –ω)
reflectedRadianceEst(x, ωr):
if random() < survivalProbability:
ωi, pdf = brdfSample(x, n(x))
return brdf(x, ωi, ωr) * rayRadianceEst(x, ωi) / (pdf * survivalProbability)
else
return 0
Version 1.0: direct illumination
Separate the integral into direct and indirect and use two samples:
\[L_r(x,w)=\int_{H^2}f_r(x,w,w') \; [L_i^{0}(x,w'), L_i^{+}(x,w')] \; d\mu(w')\] \[=\int_{H^2}f_r(x,w,w') \; L_i^{0}(x,w')d\mu(w') + \int_{H^2}f_r(x,w,w') \; L_i^{+}(x,w') \; d\mu(w')\]- sample according to luminaires $P_L$
- sample according to BRDF $P_B$
This means we trace two rays,
- one by luminaire (L) sampling and
- one by BRDF (B) sampling.
The L ray goes toward a luminaire and its radiance value is the emitted light from its direction.
We don’t recurse on the L ray (called a shadow ray).
The B ray (the indirect ray) goes in some arbitrary direction (maybe toward a luminaire, maybe not) but in either case its radiance value is the reflected light (recursively estimated) of the surface it hits.
We don’t include emission in the B rays.
In the example code on the slide, this is done by having the caller trace the ray and then call reflectedRadianceEst
(rather than rayRadianceEst
, which would have included emitted light)
rayRadianceEst(x, ω):
y = traceRay(x, ω)
return emittedRadiance(y, –ω) + reflectedRadianceEst(y, –ω)
reflectedRadianceEst(x, ωr):
return directRadianceEst(x, ωr) + indirectRadianceEst(x, ωr)
// L
directRadianceEst(x, ωr):
ωi, pdf = luminaireSample(x, n(x))
y = traceRay(x, ωi)
return brdf(x, ωi, ωr) * emittedRadiance(y, –ωi) / pdf
// B
indirectRadianceEst(x, ωr):
if random() < survivalProbability:
ωi, pdf = brdfSample(x, n(x))
y = traceRay(x, ωi)
return brdf(x, ωi, ωr) * reflectedRadianceEst(y, –ωi) / (pdf * survivalProbability)
else:
return 0
Version 1.0m: direct by multiple importance
We got the best (or at least most robust) results for direct illumination by using multiple importance sampling to combine luminaire and BRDF sampling.
directRadianceEst(x, ωr):
ωl, pll = luminaireSample(x, n(x))
pbl = brdfPDF(ωl)
ωb, pbb = brdfSample(x, n(x))
plb = luminairePDF(ωb)
yl = traceRay(x, ωl)
yb = traceRay(x, ωb)
fl = brdf(x, ωl, ωr) * emittedRadiance(yl, –ωi)
fb = brdf(x, ωb, ωr) * emittedRadiance(yb, –ωi)
return fl / (pll + pbl) + fb / (plb + pbb)
Version 1.1: sharing the BRDF ray
For each reflection it generates three rays by the time it is done:
- an L and a B for direct, and then
- later another B for indirect
This is wasteful, because those two samples don’t need to be independent.
They are not samples of the same estimator; they are samples contributing to two estimators we are adding together.
So we can save work by tracing a single B ray and using it to sample both emitted and reflected light.
The weighting is important: the contribution of emitted light is weighted against the luminaire sample using Veach & Guibas’s balance heuristic, whereas the contribution of reflected light is just normalized as its own separate estimate and added in.
Doing this in the pseudocode results in a monolithic reflectedRadianceEst
function that is perhaps
harder to read, but performs well.
reflectedRadianceEst(x, ωr):
ωl, pll = luminaireSample(x, n(x))
pbl = brdfPDF(ωl)
ωb, pbb = brdfSample(x, n(x))
plb = luminairePDF(ωb)
yl = traceRay(x, ωl)
yb = traceRay(x, ωb)
fl = brdf(x, ωl, ωr) * emittedRadiance(yl, –ωl)
fb = brdf(x, ωb, ωr) * emittedRadiance(yb, –ωb)
reflRad = fl / (pll + pbl) + fb / (plb + pbb)
if random() < survivalProbability:
reflRad += brdf(x, ωb, ωr) / pbb * reflectedRadianceEst(yb, –ωb) / survivalProbability
return reflRad