Form Factors – The Light Already Knows: An Elegant Idea That Failed
Hey Look, Free Disco Ball
Today’s episode, like so many, begins with a Cornell box. This one holds three glass spheres, rendered by a radiosity solver. Something is wrong with this retro radiosity render, and the culprit is the most fundamental part of a radiosity solve:
form fac·tor/ˈfôrm ˌfak·tər/ · noun, radiometry 1. the computed fraction of one surface’s light that reaches another. 2.(reciprocal) the same fraction, measured from the light’s side.
The starting point: 2×2 receiver hemicubes — twelve form-factor samples per patch, direct light included — 20 s. The patch-to-patch blotch is the artifact this article is about.
Great soft blotches of light and shadow sparkle across surfaces that should be smoothly lit and shaded. It’s a dance party! Or some kind of terrible bug? But the geometry is fine, the materials are ok, and the light is set up correctly. This artifact is sampling noise, aliasing, from the single most important number in a radiosity solve: the form factor.
Twelve samples for the most important form factor
A radiosity solver works by computing, for every pair of surface patches in the scene, what fraction of the light leaving one arrives at the other. That number is the form factor. If you build a full matrix of form factors for every combination of two patches you have the whole simulation. Once you have it, light bounces around the room by repeated matrix application until it converges.
The classical way to measure a form factor – Cohen and Greenberg’s hemicube, 1985 – is beautifully direct: put a camera at the patch, render the scene onto a little half-cube of pixels, and count. Whatever fraction of those pixels a given surface covers, weighted by a precomputed cosine table, is the form factor to that surface. It is solving the harder rendering problem (global illumination) by using a simpler rendering technique (scanline render), which has always made the graphics person in me happy.
The catch is resolution. The image above was rendered with a hemicube two pixels on a side; a top face of 2×2 and four side faces of 2×1, for twelve form factor samples total.
For indirect bounce light, twelve form factor samples may be survivable; the energy is diffuse, low-contrast, and errors average out. But the patch-to-light coupling is the highest-energy and highest-frequency term in the entire solve. Quantizing that through only twelve pixels is what gets the party going. Some patches catch the lamp in a pixel and blaze; their neighbours miss it entirely and fall dark. The blotch is not noise in the path tracing sense, but it is in the radiosity solver’s sense.
Our hemicube inspector can highlight, in the 3D view, the surface each hemicube cell actually landed on. Here are two neighbouring patches highlighted in a white outline – a few centimetres apart on the same surface – with all twelve of their cells mapped:
White marks the patch being inspected; cyan marks where its twelve form factors land. On the left, patch 2311 samples the room twelve times and every single cell comes back with a form factor pointing to a wall, the floor, or a sphere. It never finds the lamp. Its neighbour on the right, patch 2715, casts the same twelve cells into the same room – and exactly one of the form factors (yellow) lands on the light.
And here are the two hemicubes themselves, straight from the inspector –
each patch’s complete view of the world, all twelve cells of it:
Every coloured cell is one form factor sample. This is the entire dataset from which each patch must estimate its illumination – the readout names the difference: one cell of patch 2715’s grid on emitter 5131; none in patch 2311’s twelve.
That one form factor is worth 6.4× the total energy: 1.489 against 0.232. Two patches you could cover with a thumbnail, differing by a factor of six, because a single form factor out of twelve happened to find the ceiling. Scatter that coin-flip across every patch in the room and you have the blemish we started with.
The obvious fix, and its price
The obvious fix is to stop being cheap. Here is the same scene with the receiver hemicube raised to 32×32 – 3,072 form factor samples per patch instead of twelve.
The same scene, receiver hemicubes raised to 32×32 (3,072 form factor samples per patch) — 33 s.
The blemish is gone – but look along the top of the room. Fine horizontal stripes have taken its place. The lamp is now a handful of pixels in every receiver’s view instead of a rounding error, but those pixels still round, patch by patch, and the rounding marches across the wall as a moiré. The party didn’t end, it just changed into more formal clothes.
Here’s 64×64 – 12,288 form factor samples per patch, five times the render time of the picture that had the blemish:
64×64 receiver hemicubes (12,288 form factor samples per patch) — 1 m 47 s.
Better. Also still striped. Here is the top of that frame, magnified, with local contrast amplified so print does it justice – on a good monitor you can even see these whorls in the render directly:
The top of the 64×64 frame, magnified, local contrast amplified: the moiré whorls the stripes make.
One more rung. 128×128 – 49,152 form factor samples per patch:
128×128 receiver hemicubes (49,152 form factor samples per patch) — 9 m 43 s.
Clean – at viewing contrast. (Under the same amplification that exposed the 64×64 whorls, a ghost of the pattern still answers.) And now the full bill is on the table. On this scene – twenty thousand patches, meshed the way the later benchmarks use it – the ladder runs from 20 seconds to 583: twenty-nine times the blemished frame, for one artifact. The bill also scales the wrong way: it is paid per patch, and a scene with more surfaces pays it more times. The 128×128 frame serves as the article’s ground truth reference from here on.
So: can I get the 128×128 hemicube picture, or better, at the 2×2 hemicube picture’s price?
The Light’s Side of the Forms
Here is the thing that started this work. Our solver already renders the
scene from the light’s point of view, at high resolution, every single time.
It does this because the light is a patch like any other, and every patch gets its hemicube and calculates form factors. But the solve is a gathering solve: a receiver i reads only row i of the form factor matrix. The light’s own row – the record of exactly which surfaces the lamp can see and how much of its output each one receives – is consulted only when gathering light onto the lamp, which is pretty pointless.
And that data is precisely what we are missing. A receiver’s twelve-sample hemicube is a bad instrument for finding a small bright lamp. But the lamp’s own view of the room is a rendered image of every surface it illuminates, and it is already sitting in memory. Also, light hemicubes get rendered with more form factor resolution by setting a multiplier. There are only a few of them, so the difference on the GPU is actually so small it’s hard to measure.
What each side actually shoots
It is worth pausing to draw the two techniques, because the costs in the
table below fall straight out of the pictures. The seeded path asks the
question from the receiver’s side: every patch fires a fan of shadow rays at
stratified cells of the lamp.
Sixteen rays is a modest fan – but there are thousands of these,
one per patch, and the bill grows with every patch the mesh gains.
The reciprocal path asks the same question from the light’s side using form factors: the lamp is cut into sixteen sub-squares, and each one renders the scene through a small hemicube – a 16×16 raster through the top face, 8×16 through each of the four side faces. One rasterized view of the room, per face, occlusion included.
And that is the whole trick: 16 of these – total, for the entire scene, no matter how many patches the mesh grows.
Here is that view – the lamp’s own hemicube 64×64, unfolded, from the same scene, shows the form factors:
Where the receiver had twelve cells, the light has over twelve thousand.
Every coloured cell is a surface patch the lamp measured, occlusion
included – you can see the three glass spheres sitting in its field of
view. This image is computed on every solve, at real expense, and then
consulted by nothing.
The bridge between them is two hundred and sixty years old. Lambert’s
reciprocity relation says that form factors between two patches are
symmetric once you weight by area:
F_j→L · A_j = F_L→j · A_L
Which rearranges to something almost embarrassingly convenient:
F_j→L = F_L→j · A_L / A_j
Every receiver’s coupling to the light can be read off the light’s own form factor row, inverted through areas, with no new rendering at all. The expensive measurement is already paid for. I simply have to use it rather than discard it.
There is a further elegance to it. A hemicube rendered from the light has
occlusion built in for free: a shadow is exactly the set of pixels the lamp
cannot see. Where a receiver must ask “can I see the light?”, the light has
already answered “here is everyone I can see.”
It works
First I need to admit that the hemicube needs to be at least 8×8 just to capture the indirect and other effects in this scene. But even at 8×8 the direct light is still problematic — still having a party.
Fixed 20k-patch mesh, 8×8 receiver hemicubes, no direct seeding — 26 s. Indirect light and the glass are captured, but each patch’s direct light still rides its own few samples: the blotch is back.
And finally here’s the result with the form factors from the Light’s Side used for direct illumination.
The same mesh and the same 8×8 hemicubes, with direct light taken from the light’s own high-resolution form factor row instead — 24 s. Two seconds faster, and the blotch is gone.
receiver hemicube
direct light from
render time
what you see
128×128 (reference)
receiver hemicube
9 m 43 s
clean
2×2
receiver hemicube
20 s
the blotch
8×8
receiver hemicube
26 s
blotch on the direct light
8×8
the light’s own row
24 s
clean
In the original 2×2 measurement run, that swap came out eighty-seven times faster than buying the same quality by refining the receiver.
This is the point in the article where, if I were selling something, I
would stop.
The question we should ask before celebrating
The honest question is not “is this better than the artifact?” It is “is this
better than what the renderer would have done anyway?”
Because our solver already has a mechanism for this problem, and it is on by default. Rather than transporting the lamp’s light through form factors at all, it shades each patch directly using importance sampling – fire shadow rays at the light source, compute the illumination analytically, and seed the solve with the answer. Form-factor transport then handles only the bounces, where twelve samples is possibly enough.
So here is the production answer: the same 8×8 receiver, with seeded direct lighting doing what it does every day.
The production answer: the same receivers with seeded direct lighting — shadow rays fired at the lamp — also smooth.
version
direct light from
time
error vs reference
reciprocity
the light’s own row
24 s
0.0058
seeded direct
shadow rays
23 s
0.0042
Same cost. Slightly better. And it was already there.
I then went looking for the conditions where reciprocity would pull ahead. I did not find them.
Sample budget. Seeded direct’s quality is bought with shadow rays, so I
swept the budget from 400 rays per patch down to one:
shadow rays per patch
400
100
25
9
4
1
reciprocity
error vs reference
0.0291
0.0290
0.0289
0.0295
0.0290
0.0297
0.0327
render time
5 s
6 s
5 s
5 s
5 s
5 s
5 s
A single shadow ray per patch beats the reciprocal form factor approach, and the cost curve is flat – shadow rays are so cheap against the cost of the form-factor solve that four hundred of them per patch does not move the wall clock.
Scene complexity. I subdivided the room, reasoning that the per-patch
ray cost should eventually dominate:
patches per wall
1,024
4,096
16,384
reciprocity
3 s
38 s
over budget
seeded direct
2 s
34 s
over budget
It doesn’t. Both are dominated by the shared cost of rendering tens of
thousands of receiver hemicubes; the direct-lighting method is noise against
it. At the finest subdivision neither finished inside a fifty-minute budget,
which tells you where the real cost lives.
Heavy occluders. A Stanford bunny between the light and the walls, to
make each shadow ray expensive: reciprocity 14 s, seeded direct 12 s.
Where the error lives. Perhaps the whole-image number was hiding a
regional win – reciprocity ought to shine in penumbrae, where a rendered view
of the light beats point sampling. It doesn’t, here:
region
reciprocity
seeded direct @ 1 ray
shadow / penumbra
0.67
0.59
lit surfaces
10.09
9.02
Why the elegant idea loses
The reasoning was sound and the implementation does what it claims. The premise was wrong, and the premise was this: that measuring direct light using shadow rays is more expensive.
For a hemicube-based gathering solve, it genuinely is – that is the artifact we opened with. But the renderer is not restricted to hemicubes. It is a ray tracer, and asking “can this point see that lamp?” is the single cheapest, most optimised query in the entire system. Reciprocity is an elegant way to recover an expensive measurement. Ray tracing is an inelegant way to avoid needing it, and it wins because the thing it avoids was never more expensive in the first place.
There is a general lesson in that, and it is why this article exists rather than a triumphant one. An optimisation must be benchmarked againstthe best solution we already have, not against the problem it was designed to solve. The artifact is real and the technique genuinely fixes it – every number in the first half of this article is true. But that artifact is not what our renderer produces, because the renderer has had importance sampling of area lights for years. Had I never rendered that production image, I would have shipped a correct, well-tested, thoroughly verified solution to a problem the renderer had already solved in a better way, and I would have believed I had improved it eighty-seven fold.
What survives
Two things, and one open question.
The analytic machinery built along the way is independently useful.
Implementing this required an exact closed-form solution for the form factor
between a point and a polygon – Lambert’s contour integral, 1760 – which now
lives in the renderer with a fifteen-case test suite and no dependence on
rasterization at all. Any future work needing ground-truth form factors, or a
check on the hemicube, now has it.
The diagnostic is real. The reciprocal path reports exactly how many
receiver-light couplings the hemicube missed entirely – 56,601 out of 82,160
in the original 2×2 run. That is a direct, per-scene measure of whether a given
hemicube resolution is adequate, and we did not have one before.
And the open question: every scene here has a small, compact lamp, which is precisely the case a single shadow ray handles almost exactly. The original argument for this technique concerned a large emitter, partially occluded and subdivided as a way of stratifying the sampling – something that benefits both shadow rays and reciprocal form factors. A broad softbox behind a complex silhouette, subdivided into nxn smaller lights. That one is still on the bench.
Epilogue: the idea comes back
This article sat in drafts for weeks, and in that time the story evolved in a way we did not see coming.
The verdict above is honest for the scenes it measured: compact lamp, diffuse room, easy geometry. But I kept sweeping scene classes, and on mirror-heavy scenes the tables turned. Light that arrives via a mirror is caustic transport and belongs to a different pipeline entirely; but that is not what changed. What changed is the wall behind the mirror: specifically the grazing slivers along mirror frames and edges. The receiver’s hemicube (the red wall below) sits behind the mirror. Hemicube rays are fired from the patch center and they get blocked by the back of the mirror, speaking for the whole patch. The light’s side, though, finds the edges exposed and measures them cleanly. I put the estimators in front of a Monte-Carlo referee (hundreds of area samples per pair, visibility included) on the eleven hardest disagreement light-patch pairs: the light’s view came out closest to ground truth on eight of the eleven. Not just more efficient, but more accurate!
Those two renders differ by an RMSE of 0.0036 – visually identical – but the amplified difference is not noise: it clings to the mirror frame’s edges.
I tuned both techniques to their own honest best and measured again, on an intentionally heavier version of the scene – 22,000 patches. The seeded path required twenty shadow-ray samples per patch to smooth the noise, a value I found out by experimentation. The reciprocal path, on the other hand, wanted the light split into sixteen sub-patches so it could sample from more positions. And a higher-resolution hemicube. The table below is the two cost curves.
tuned technique
stage
+ its light atlas
total
seeded direct (20 samples)
3,539 ms
3 ms
~3.5 s
reciprocity (subdiv 2, 32× view)
20 ms
248 ms
~0.27 s
Thirteen times cheaper, just on the direct light evaluation. The seeding bill grows with every patch and every sample; the reciprocal pass reads a measurement the light already made. A curious footnote from the tuning: doubling the light’s view resolution as a safety margin improved the results noticeably at a cost of 190 milliseconds of GPU rasterisation. In a 224-second frame – increasing resolution on the light’s side is very nearly free, which is itself a small architectural lesson.
It also found its limit, and a second referee session named the price with the same bluntness. On densely self-occluding geometry – a mesh bunny hugging its own crevices – the technique’s analytic repair path over-injected energy, because a single lucky confirmation ray was granting fully un-occluded credit to mostly-blocked couplings. The fix was to make the confirmation visibility-weighted: stratified shadow rays across the lamp’s area now scale the analytic coupling by the fraction that actually gets through. Re-adjudicated against the same Monte-Carlo truth, per coupling:
rescued coupling
before (vs MC truth)
after v-weighting
worst
3.67×
1.84×
2nd
2.89×
1.40×
3rd
2.55×
1.24×
4th
2.31×
1.14×
median of the flagged band
~2.2×
~1.2×
The clean rematch
One confound remained in every timing we had published: the mesh itself. Under adaptive subdivision the two techniques do not even solve on the same patches – the reciprocal field is raster-exact at the mirror edges, so it shows the subdivider sharper gradients and refines earlier, spending seconds on a finer intermediate mesh that the seeded run never builds. Fixing that meant removing adaptivity from the experiment entirely: the same scene, uniformly meshed to an identical 20,496 patches for both sides, each technique at its tuned best on the same mesh yielded these results:
Same mesh, same solver, same picture: reciprocal 20 seconds, seeded
23. Three seconds – thirteen percent of the whole frame – and this time
it is not a stage-level curiosity but wall-clock you can feel, on exactly the
kind of patch count that real scenes grow into.
So the lesson from the middle of this article was correct, but incomplete. An optimisation must be benchmarked against what the system already does – per scene class. The elegant idea lost in the first scenes we tested, but now we see it wins in many others. Enough to earn a place in future testing as we continue development.
I set out to optimise away a three-and-a-half-second stage inside a three-minute-forty-seven-second frame. On one scene the frame barely noticed – about a second and a half of every hundred. On the twenty-thousand-patch rematch above, three seconds in twenty-three is a more significant percentage. What actually survives the arithmetic is worth more than the seconds though: a direct-transport estimator that is closer to ground truth on the geometry where sampling struggles, at a cost of milliseconds; the discovery that resolution on the light’s side costs almost nothing; and a method whose bill doesn’t grow as much with the patch count – which matters, because patch counts only ever grow. Sometimes optimisation reduces the render time, but I have to remember that being closer to ground truth can be even more valuable.
The conclusion is that there is no conclusion. At least not yet. I’ve declared victory and defeat multiple times just in this article, and I expect the pendulum to keep swinging as development continues. What I do know is that DeluxeRender now has another unique tool in its arsenal, and it has already helped me understand the shape of the problem better.