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 16

Every glowing thing is a light

A lamp placed with the scene builder is a light the renderer knows about: a rectangle or a disc, its size and place, aimed at directly (chapter 14). But any shape can glow: give a sphere, a twisted tube or a whole wall a glowing material, and it lights the scene too. To aim at those, the renderer first has to find them, without being told where they are or what shape they have.

1Found by the paths that hit them

Paths bounce through this room as in chapter 13. A coarse grid lies over the scene (the dotted lines, 16 × 16 here; 16 × 16 × 16 in ShadyTracer). When a path hits something that glows, it notes in which cell. Each cell keeps two things: its box, the tightest rectangle round every hit it has had (drawn solid, a few pixels out), and the brightest glow seen there. Cells with hits are shaded. After one sample (one path from each of the 48 pixels), 28 paths have hit the ball in plain view, and 2 have hit the bar behind the wall, by bouncing over it. The faint lines are the latest paths that found a glow: blue while they bounce, orange on the piece that hits it. The short dashes along the bar are how these figures draw a glowing line (the key on the contents page).

Nothing aims at these cells yet. Every second sample the renderer turns the cells with hits into its list of targets (), and this is the first.

After 8 samples the list has been made four times, and both glows are on it. Each listed cell gets a circle round its box (dashed): from now on the renderer aims at it as at a lamp, picking a cell (brighter and larger ones more often) and then a direction into its circle. A cell of a single hit has a box of no size, so its circle is only the small margin added to every one: a small solid ring. Drag the ball behind the wall, or into a corner, and see how many samples it takes to be found.

After 64 samples, every part of both glows that paths reach has a cell.

And before a glow is found? It is lit only by the paths that happen to hit it, exactly as in chapter 13, and that is still right. Chapter 15 weighs each way of reaching a light by how likely each way was to pick the direction it came by. For one direction, pplate is the bounce's chance of picking it and plamp is aiming's, each as a density: a chance per unit of direction (section 4 says how directions are measured). Take a bounce whose direction meets the bar before the bar is listed. Under MIS its weight is pplate² / (pplate² + plamp²), and plamp is 0, since nothing aims at the bar yet: the weight is 1, and the hit counts in full. Once the bar is listed, the same direction gets a weight between 0 and 1, and the aiming brings the rest. Either way the light is counted once: the picture is unbiased from the first sample, only noisier until the glow is found. The code says it in one line ().

A room with a glowing ball in view of the eye and a glowing bar behind a wall, both drawn in the glow's colour. Dotted grid: the cells; shaded: the cells hit so far, each with the tightest box round its hits (solid) and, once the cells are listed, the dashed circle the renderer aims into. Faint: the latest paths that found a glow. Choose how many samples, and drag the ball (its circled cross) somewhere the eye cannot see it.

2Random lines

Section 1 traced from the eye and waited to hit a glow. Light tracing (chapters 18 and 19) goes the other way: it starts light at a glowing surface and follows it out, from random points of it, in random directions, each carrying the brightness of the part it left. That needs a random point on the surface, and for a shape known only by its distance formula (chapter 2: how far is the nearest surface, nothing more) there is no list of its surface to pick from. Finding where a line crosses a shape is easy, though: it is what the renderer does all day, marching along a ray. So ShadyTracer uses a fact of geometry. Draw random lines through a circle round the shapes (a random direction, a random offset from the middle); in ShadyTracer that circle is each found box's sphere from section 1. Where a line crosses a glowing surface, start a photon there, leaving the surface along the line. (Which way along it, and past what, the code's comment says: the line is followed from its far end back, through anything that does not glow, and the first glowing surface met from outside starts the photon, so a lens round an LED is passed through, not taken for the light: .)

Count the crossings and each shape is crossed in proportion to the length of its outline: with 40,000 lines through the dashed circle (radius 2), the disc (radius 0.45) is crossed 17,972 times, and its length alone predicts 18,000 (only the first 60 lines are drawn; the counts are of all of them). And the angles the lines cross at (the columns of the chart) are spread exactly as a matte glowing surface sends out its light: most straight out along its normal (the direction straight out of the surface), fewer at a slant, in proportion to the cosine of the angle from the normal (the dots; this is Lambert's law for a matte surface). So the crossings are points spread evenly over every surface, with directions spread the right way, for a shape the method knows nothing about. A brighter part sends out more light not by more photons but by brighter ones: each carries the glow where it started. In 3D it is the same with lines through a sphere and the area of the surface: the Cauchy–Crofton formula.

The mathsLines through a circle

A line is picked by two numbers: an angle θ in [0, π) and an offset p in [−R, R], both uniformly. θ stops at π because a line has no front: turned by half a turn it is the same line. Draw every choice as a point of a rectangle π wide (the angle) and 2R tall (the offset): all the lines through the circle fill that rectangle, of area π × 2R = 2πR, and the chance that a random line is in some set of lines is the share of the rectangle that set covers.

Now a short, nearly straight piece of curve, of length s. Lines at angle φ from its normal see it as a width s |cos φ| across them, its shadow in their direction: of the lines at that angle, those whose offset falls in that width cross it. So at each angle the piece covers a height s |cos φ| of the rectangle. Across the rectangle's width π, |cos φ| goes from 1 (lines straight at the piece) to 0 (lines along it) and back; its average over a half turn is 2/π, about 0.64 (throw random angles and average |cos φ|, chapter 12's way, and you get 0.637). So the piece covers an area π × s × 2/π = 2s, out of 2πR. A longer curve is many short pieces, so a curve of length ℓ is crossed, on average,

expected crossings of a curve of length ℓ = 2ℓ2πR = ℓπR

times a line. (A crossing is counted each time: a line through the disc crosses its outline twice.) And because at each angle the share of lines crossing is in proportion to |cos φ|, the angles at the crossings come out spread in proportion to cos φ, which is the flat world's matte emission: the chart.

For later, in the notation of chapter 5 and the textbooks: in 3D a line through a sphere of radius r crosses a small patch of surface, of area dA, going in a small bundle of directions dω round ω, with chance |n·ω| dA dω / (4π²r²), where n·ω is the cosine of the angle from the normal: the matte emission's own measure.

Random lines through the dashed circle (at most 60 of them drawn), their crossings of a disc, a box and a rod drawn as dots. Right: the angle of every crossing from the surface's normal (columns) against a matte surface's cosine (dots). Choose how many lines, or draw new ones; point at a column to read it.

3How bright, in 32 bits

The grid keeps the brightest glow each cell has seen, in one 32-bit whole number a cell. Thousands of paths write to it at once, each worked on by its own thread, one of the many small processors of the GPU running side by side (chapter 21). Two that read the old value at the same moment and each write their own would lose one of the two, so the grid is kept with an operation the GPU does in one step for every thread safely, "keep the larger" (atomicMax), which works on whole numbers. So the brightness has to become a whole number whose order is the brightness's order.

It used to be the brightness, capped at 100,000, times 16: fine while glowing materials were "5 per channel" (80 codes), useless once they were in real units, candela per square metre (chapter 5), where a bright panel is 20,000 and the sun seen in a mirror about 1.5 billion (the code's comments say 1.6; chapter 5 works it out), and everything above the cap ranked equal. Below 1/16 it was 0, the same as never seen. On the figure's scale, where each step is ten thousand times the last, that old code hugs zero, climbs over the last steps before 100,000 and is flat after.

The log code counts doublings instead. The logarithm of base 2, log₂ x, is how many times 1 must be doubled to reach x: log₂ 8 = 3, log₂ 1 = 0, log₂ ½ = −1, and it works between whole numbers too (log₂ 20,000 ≈ 14.3). Code a brightness by its log₂ and every doubling of brightness gets the same number of codes, 209,715 (a doubling is what photographers call a stop). 80 stops, from 2⁻⁴⁰ to 2⁴⁰, fill 2²⁴ codes, in order. And multiplying a brightness by a number adds the same count of codes whatever the brightness: doubling adds one stop's codes, halving takes them away, and multiplying by 0.9 takes away 0.15 of a stop. The comment does not say why 2²⁴ and not all 32 bits; a likely reason (inferred) is that the code is worked out in a 32-bit floating-point number, the GPU's usual kind, which counts whole numbers exactly only up to 2²⁴ (chapter 12).

The whole number kept for a brightness, as a share of the largest: the old code (rising, then flat from 100,000) against the log code (a straight line across the whole range). The dashed lines mark everyday brightnesses. The slider picks a brightness: the readout gives both codes for it and what each reads back as. Point at the curves to read them.

4How ShadyTracer does it

The deposit, from every path that hits a glowing surface:


    

    

A number in the comment, checked. Until version 0.67.3 the comment gave the log code's step as "2e-5 of a stop". From its own constants it is 80 stops over 2²⁴ codes, 4.8 × 10⁻⁶ of a stop: finer than it said. This chapter found it, and the comment now gives 4.8e-6.

Between samples, each cell with hits becomes a box with a sphere round it (the figure's circles), scored by its brightest glow times its size squared (roughly its power), and the scores become the chances of picking each. Aiming (chapter 14's NEE) then picks a box and a direction uniformly within the cone its sphere fills as seen from the point. Both halves of MIS ask for the density of that choice: its chance per unit of direction, chapter 15's plamp. Directions in 3D are measured by solid angle, the area a bundle of them covers on a sphere of radius 1 (the whole sphere is 4π); a cone of half-angle α, the angle from its middle to its edge, covers 2π(1 − cos α).


    

And the lines for light tracing, Crofton's density, which needs nothing about the shape but the spheres. A photon stands for all the light leaving its patch of surface along its line; lines come through that patch at a rate given by this density, so each photon's brightness is divided by it, chapter 12's rule again: what is picked often counts for little each time.


  

5Tricks and trade-offs

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

Aiming at found glow pays. On the showcase scene lit only by a glowing sphere, aiming at the discovered glow cost 2.3 times as much per sample and still reached a given noise 1.4 times sooner than not aiming; light tracing cost 47% more per sample and bought nothing there (roadmap/PLAN.md, "Emissive surfaces as lights", second run). The document names no graphics card for that run; the run before it was on the user's NVIDIA Ampere (chapter 0 says who "the user" is).

Too many glows is noisier, never wrong. At most 64 boxes are listed. A glow past that is not aimed at, and is lit by the paths that hit it, as before it was found (the comment above the collect kernel).

The list only changes between samples. If it moved during a sample, the density a shadow ray was drawn with would differ from the one a later bounce is weighed by, and MIS's weights would no longer sum to one.

6What went wrong before

  • A code that saturated: the linear brightness code of section 3.
  • Glow counted twice. Light from a glowing surface that reaches the camera through glass (a caustic) can be found two ways: by camera paths, and by photons from the surface (chapters 18 and 19). The renderer splits the work: camera paths are marked to leave that light to the photons. In the first run on hardware the marking was made only while the sun's photons existed, so with the sun off both counted it, and the picture read 0.6% too bright. Small, because light counted twice adds its own amount again: that light was only about 0.6% of the picture (the second run notes this glow throws little of it into view). Wrong all the same.
  • Two estimates of two things: aiming worked out how much light a surface passes on from its own simplified copy of the materials, while bounces used the real ones. MIS adds the two ways' shares of one amount, and these were shares of two different amounts, so the picture's brightness moved with how often shadow rays were aimed at the glow: 1.2% darker than the default with none aimed, 0.5% brighter with 9 in 10 (chapter 15 tells it whole).
  • A sum that overflowed. The cell keeps the brightest glow, not the sum: a glow filling the screen is a million hits a sample, and their sum would wrap round a 32-bit number within one (the comment in emitDeposit).

7Try it

  • In the first figure, which glow gets a cell first, and why does the bar behind the wall get fewer hits?
  • With 40 lines, how far from the prediction are the counts? With 40,000? (Chapter 12 says how far to expect.)
  • In ShadyTracer, give a shape in a dark scene a glowing material and watch the first few samples: the noise falls quickly once the glow is found.

Words from this chapter