This page runs from a web server, not from a file: in the repository run python3 -m http.server 8000 and open http://localhost:8000/tutorial/.

Part IV · Path tracing · Chapter 15

Two ways to the light

Chapter 14 found light by aiming at it: pick a point on a lamp and send a shadow ray. Chapter 13 found it by chance: bounce the way the surface scatters, and see whether the bounce meets a lamp. Each is good where the other is bad. Multiple importance sampling uses both at once and weighs every answer by how good its way was at finding it, so the picture gets the best of each.

1Each way fails somewhere

Four plates, from nearly a mirror (top row, σ 0.02) to quite rough (bottom row, σ 0.6), and four lamps over them, from tiny to long, each giving off the same total light (a short lamp is brighter for its size; the ticks under each lamp show the side it glows from). A glossy plate reflects light mostly near its mirror direction; σ is how far its reflection spreads around it, as an angle in radians (0.02 is about 1°, 0.6 about 34°). The camera sees each lamp reflected somewhere along each plate: the tiny lamp near the left end, the long one near the right. The plate is a line seen from the side, so each strip below is one plate, left to right, and each column of a strip is one point of it.

Every point's light is estimated three ways, from the same random numbers. The lamp strategy (left column) picks a point on a lamp and looks whether the plate reflects its light to the camera: this is chapter 14's next-event estimation, NEE. The plate strategy (middle) picks a direction the way the plate itself reflects, and looks whether it meets a lamp: chapter 13's bounce, called BSDF sampling after the BSDF, the formula for how a surface reflects. The right column uses both, weighed. All three are estimates of the same picture and settle on it in the end; they differ in how fast. After 8 samples the three differ a lot. Point at a strip to read that point's estimate, its error bar and the exact answer.

After 256 samples, look at where each is still streaked. The lamp strategy is noisy on the sharp plates, worst under the long lamp: a point picked anywhere on it is almost never in the narrow range of directions the plate reflects. The plate strategy is noisy on the rough plates, worst under the tiny lamp: a direction drawn from a wide spread almost never meets it. Each is smooth where the other is streaked. Both, weighed is smooth everywhere: the readout's error bars are 13% and 12% of the light for the two alone, 6% with MIS.

The mathsTwo estimates of one sum

The light a plate point sends to the camera adds up the light arriving from every direction, each weighed by how much of it the plate sends on to the camera. Pick one direction ω at random, and either strategy gives an estimate of that whole sum:

f(ω) cos θ Le(ω)plamp(ω)   or   f(ω) cos θ Le(ω)pplate(ω)

f(ω)
how much of the light from ω the plate reflects towards the camera (its BSDF)
cos θ
the slant the light arrives at, as in chapter 1
Le(ω)
the brightness of the lamp seen along ω (0 where no lamp is)
plamp
how likely the lamp strategy is to give direction ω, per radian
pplate
how likely the plate strategy is to give it

Dividing by p is chapter 12's rule: a direction picked often must count for little each time, one picked rarely for a lot, so that on average every direction counts once (a probability density). An estimate is noisy where its p is small but the light is not: a rare direction carrying a lot, divided by a tiny p, a bright streak in the picture.

Why weights that add up to 1 count each direction once: over many samples the lamp strategy brings back the whole of a direction's light, on average, and so does the plate strategy. Counted in full, both together would count it twice. Multiply the lamp strategy's samples by wlamp(ω) and the plate strategy's by wplate(ω), and on average they bring back (wlamp + wplate) times it: once, exactly, when the two add up to 1. Each sample is weighed at its own direction, with both densities there: a lamp sample at ω₁ by plamp(ω₁) and pplate(ω₁), a plate sample at ω₂ by the same two at ω₂.

Any weights that add up to 1 would be fair. The power heuristic squares the densities:

wlamp = plamp²plamp² + pplate²,   wplate = pplate²plamp² + pplate²

The squares lean harder towards the likelier strategy than the plain ratio would, so a direction only one strategy was likely to find counts almost entirely through it; Veach found the squares usually the least noisy of the recipes he tried (his chapter 9). With the second figure's numbers, 5.86 against 0.66: 5.86² = 34.3, 0.66² = 0.44, and 34.3 / 34.7 = 0.99, where the plain ratio would give 5.86 / 6.52 = 0.90.

That is also why the streaks go. A streak is a rare, lucky find by the weak strategy: a direction its p makes unlikely, divided by that small p. In that same direction the other strategy's p is large, so the lucky find is multiplied by a weight near 0, and its light comes in instead, steadily, through the strategy that finds it often.

Above: four lamps over a plate, and the camera seeing each one reflected (faint lines). Below: the plate's picture for four roughnesses (rows, sharp at the top, σ beside each) by three strategies (columns), as samples add up. Brighter is more light. Choose how many samples, or draw new random numbers; point at a strip to read it.

2Who is good at what

At one point of one plate, the top plot shows how likely each strategy is to pick each direction, on a log scale (each mark up is ten times the one below, so a density a thousand times smaller still shows). The lamps' density is a hump for each lamp seen from here, named above it, taller where a lamp looks smaller from here (a short lamp, or a far one seen edge-on); a lamp more than 80° away is off the plot. The plate's is a bell around its mirror direction. The bottom plot shows the weights MIS gives each. On the rough plate under the tiny lamp, the plate's bell is low and wide and the lamp's hump is tall: the tiny lamp's light counts almost entirely through the lamp strategy (weight 0.99, the narrow orange spike in the bottom plot). Elsewhere the plate's weight is 1, but there is no lamp there (shaded): no light arrives from those directions, so their weights count nothing. Only the stretches under a lamp matter.

On the sharp plate under the long lamp, the plate's bell is a narrow spike far above the long lamp's low hump, and the weights turn round: in the mirror direction, the plate's way counts entirely. Move the point along the plate and change the plate, and watch the weights follow whichever density is higher. Where no lamp lies and the sharp plate's bell has fallen to almost nothing, both weights read 0: neither strategy would ever pick that direction, so there is nothing to weigh (the code's guard against dividing 0 by 0 gives 0 there). Point at the curves to read them.

One point of a plate. Top: how likely the lamp strategy (in the lamps' orange) and the plate strategy (in blue) are to give each direction, on a log scale; the dashed line is the mirror direction. Bottom: MIS's weight for each. Choose the plate and the point.

3How ShadyTracer does it

The weight is one function:


    

The lamp side: when a shadow ray is aimed, the material is asked how much it reflects that way (lobesEval, the maths' f) and how likely its own sampling was to pick it (lobesPdf, the maths' pplate), and the shadow ray carries the weighted contribution:


    

The plate side's weight is worked out at the bounce, when the direction is chosen, but used later: whether the ray meets a light is only known when it arrives, maybe many rounds later. So the weight is computed here and carried on the ray. This part is for the sun and the sky; chapter 16 does the same for glowing surfaces, with the density kept as prevPdf:


  

4Tricks and trade-offs

Each is stamped with how it is known; point at a stamp to see what it means.

Only against a way that could have found it. A direction the lamp strategy could never pick (below the surface, or a perfect mirror's single direction, which aiming cannot hit) keeps weight 1: weighing it against a density of zero would be right. Weighing it against the lamp strategy's density where that should be zero but is not gives it a weight below 1, and the lamp strategy never makes up the rest: light lost (the glass ball, in section 5).

Both must estimate the same thing. MIS is unbiased only if the two strategies multiply in the same material. In the first run of the glowing-surface lights (on the user's NVIDIA Ampere card, roadmap/PLAN.md, "Emissive surfaces as lights"), the lamp strategy evaluated its own approximation of the material while the bounce sampled the real one. A setting decides what share of shadow rays go to glowing surfaces, and the picture's brightness moved with it: −1.2% with no shadow rays at all, −0.9% at a share of 0.2, 0 at 0.5 (the default, which the others are measured against), +0.5% at 0.9. The share is a factor of the lamp strategy's density (a direction is picked only if a shadow ray goes that way at all), so a bigger share means a bigger plamp and a bigger weight: when the two estimate different things, the mix of them moves with the share. Which row was right, the document does not say; with no shadow rays only the bounce counts, with the real material, so that row is the likelier one (inferred), and the default read about 1% too bright. With the lamp strategy asking the material the bounce samples, the second run's rows all agreed within 0.25%. The code now says so beside the call: "what the sampler samples".

5What went wrong before

  • A glass ball with a plain shadow. Under the sun a glass ball should cast a shadow with a bright spot in it, the sun focused through the glass (a caustic, chapter 18). The light that makes the spot comes the long way round: a path from the camera reaches the floor in the shadow, bounces up into the ball, and leaves it on the far side, refracted towards the sun. At that last point, on the ball's surface, "above the surface" means the side the ray came from, and the lamp strategy only ever aims there; the refracted ray goes through, to the other side. Yet it was weighed against the lamp strategy, whose density the sun's cone gave as not zero. And the plate side's own density was 0: clear glass refracts into one single direction, which a density spread over directions cannot describe, so the code counts it as 0. Its weight came out 0² / (0² + plamp²) = 0; the lamp strategy never produced it either; so the sun through glass was counted by neither, and a glass ball cast a plain dark shadow (the comment above prevPdf).
  • Glossy plastic lit by chance. A perfectly smooth coat used to be given a density of 10⁹, a stand-in for "a single direction". That number went into the plate side's density for the whole material, coat and matte base together, so a shadow ray to the sun from the base was weighed plamp² / (plamp² + (10⁹)²), which is 0: aiming counted nothing for the base, and a glossy plastic in sunlight was lit only by bounces that happened to find the sun (the comment on delta events).
  • Two estimates of two things: the brightness that moved with the shadow rays' share, above.

6Try it

  • In the second figure, find the plate and point where the two weights are equal in the mirror direction.
  • Which plate would you choose to show off the lamp strategy, and which the plate strategy?
  • In ShadyTracer, turn on Developer in the Render settings and switch Next-Event Estimation (the lamp strategy) off and on in a scene with a glossy floor under a large light. Where does the noise move?

Words from this chapter