The residue · RadianceLab
Ten threads were chosen. This is what was left over — graded in the same vocabulary, including the rows that say the thing does not work.
A curated portfolio can always be accused of having picked its ten best subjects. The reply is to publish the eleventh page: the half-built modules, the controls that do nothing, the fields that were declared and never read, and the headless vocabulary nobody has typed. Nothing here is promoted; everything here is named.
01 — What a curated site leaves out
Every other page here argues something. This one does not: it is an inventory, and its only claim is that the inventory is complete enough to be worth publishing. The ten threads were chosen because each had a mechanism or a number behind it. Choosing has a cost, and the cost is that a reader cannot tell what was passed over from what does not exist.
So the rule this page applies to itself is stated up front, and it is a rule about refusal more than about coverage:
A feature earns prose only where the site can say something about it that its own name does not already say — a number, a mechanism, or a defect. Everything else earns a table row: name, one clause, grade. An absence earns a row and never a paragraph, unless the absence corrects something the site has already claimed. And a control that does nothing earns a line, because saying so is the editorial position rather than an omission.
Applied honestly that rule mostly says no. The photon-primitive family, bidirectional transport and the camera all pass it, and all three already have pages; nothing here is going to out-argue them. What is left below is a projector mode with a real mechanism, a spectral pipeline with one specific hole in it, thirteen half-built modules, and several hundred rows that reduce to a clause each.
Reading the map. One scene description feeds two render paths that share nothing else, and eight subsystems hang off them. Seven of the eight have one of the first ten threads. The eighth — the control surface, which is what every screenshot on this site is taken through — is drawn dashed because its only thread is this one; thread 07 describes the parts of it that its own argument needed, and §07 below is the rest. The dashed band underneath is the reason the page exists at all: the work-in-progress register, the absences, the dead tools and the headless keys each cut across every box above, so none of them could be filed under a single thread.
The exhaustive version is not here and should not be. docs/FEATURE-CATALOGUE.md carries all 555 rows with a source anchor on each — 253 seen, 116 never viewed, 97 measured, 31 inert, 24 absent, 18 superseded, 16 scene-dependent. This page is the part of that document a reader would actually finish.
02 — The work-in-progress register
Thirteen modules in this program are partly built. The unusual thing about them is not that they exist — every large renderer has them — but that most of them are labelled by the code that hosts them. Three panels print [No backend] on their own faces. A menu greys out the four tools that are headers and nothing else. An integrator that never runs a Newton iteration reports that its step count is always zero. A gather mode is named “Final Gather (not built - renders kNN)” in the combo box you select it from.
The register below is the honest version. Each row says what runs and what does not, in that order, and no row is softened. Where a module builds data that nothing reads, the row says exactly that.
| Module | What runs | What does not | Grade |
|---|---|---|---|
| Advanced ▸ WIP tab | The panel draws, and labels itself: a button reading UI ONLY — backends not yet wired, then three collapsing headers each printing [No backend] | All of it. Eighteen controls across Temporal Techniques, Differential Rendering and Inverse Rendering. Twelve are function-local statics, so nothing outside the panel can read them; the other six are diffRender* fields declared on the UI struct, and every reference to all six is either that declaration or a line inside this same panel. Inverse Rendering's own line says it depends on Differential Rendering, which has no backend either | inert |
| Physics, two of five solvers | Five solvers step. Three reach the traced image — thread 09 is the census | The panel prints [WIP] Euler Fluid (Grid-based) and [WIP] Soft Body (Mass-Spring) in yellow above its own controls | seen |
| UPBP beams | Beams are built, stored and drawn | Nothing gathers from them. The integrator's own header records that enableBeamMerge is assigned from the UI and read nowhere, and that beamMaxPerPath is declared, assigned nowhere and read nowhere | inert |
| VCM visualisation | The integrator runs | vcmVizMode has no reader outside the UI. The combo is greyed and titled Viz Mode [inert] — and it is offered twice, in two different panels | inert |
| Manifold Exploration | The integrator renders | There is no Newton solve. The reported step count is assigned the literal zero, and both Newton sliders reach config fields the integrator never reads — which its own greyed heading, Newton Solver (not built), says | inert |
| Photon mapper, third gather | Two gather modes work | The third is named in its own combo box: Final Gather (not built - renders kNN). Max Iter sits behind a Confirm button so a slider drag cannot launch fifty passes | absent, labelled |
| Goldstein branch-cut unwrap | The weighted-least-squares unwrapper beside it works and scores itself | runGoldstein is an empty function body, and so is runPostAndDepth next to it. The two textures the algorithm would write are allocated on every run and never touched | absent |
| Off-axis holography, second implementation | The working bench lives in the VFX source and renders | src/spectral/Holography.h declares OffAxisHolography and CWToFSensor in full — configs, method signatures, 72 lines — with no implementation file, and is included by nothing | design only |
| Mesh pipeline | Analytic primitives only, and that is a decision thread 04 defends | tinyobjloader is vendored and linked with zero call sites. A 153-line marching-cubes implementation compiles into the binary and its header is included by no other file | absent |
| Texture pipeline | Node-graph textures reach the tracer; the projector bakes into an albedo array | The material struct's three texture indices — albedoTexture, normalTexture, roughnessTexture — have exactly one reference each: their own declaration | absent |
| The Tools menu | Nothing. All four entries are greyed, and a tooltip says why | Flatland Slicer, Sampling Visualizer, Field Visualizer and NeRF Training are named in the menu and nowhere else. Their four headers are part of a larger set: fourteen headers, 1,410 lines, that no file includes and no .cpp implements | absent, labelled |
| Scene input and output | Fifty-odd presets load from the menu, and the comparison report writes PNGs | Open Scene, Save Scene, Export Image and Take Snapshot (F12) are four stubs that draw a not-implemented border and return | absent, labelled |
| Backend selection | One backend exists: the GL compute shader | The Compute / CUDA / OptiX radio group is disabled, and its variable is a function-local static read nowhere. No CUDA or OptiX code ships here | inert, labelled |
Preset 9 belongs in this list and is one line long. The scene menu entry once read Outdoor (WIP) and built a plain Cornell Box under that name. It now reads Outdoor (not built) and is greyed — the correction is the interesting part, not the preset.
What the register is not is a confession. Nine of the thirteen rows are visible to a user who never opens the source, because the running program says so itself — in a greyed label, a tooltip, an [inert] suffix or a combo entry that names its own gap. That is a deliberate convention in this codebase, and it is the same convention that produced the ledger on the landing page: a control that lies about what it reaches is a worse defect than a control that does nothing. The other four — the Goldstein branch, the second holography declaration, the mesh pipeline and the three texture indices — are silent, and were found by reading the source rather than by driving the program. They are in this table for that reason.
03 — Six modes, four destinations
There are two projectors in this renderer and they are unrelated. The first is the structured-light lamp inside the megakernel — four delta geometries, seven patterns, a time-of-flight carrier — and thread 05 is about it. The second lives in the node graph and has six modes. Thread 05 names three of them in a paragraph and thread 07 has an unshot clip of the bake; what no page has said is where each mode lands, and that is the fact the mode combo hides.
Wire a Camera node, which freezes the live view-projection at one button press, and a Texture node into a DataMosh Projector, and a photograph is cast into the scene from that frozen pose. The combo box beside it offers six modes, and they land in four different places — which is the whole reason the mode matters more than any slider under it.
| Mode | Where it lands | What that means |
|---|---|---|
| 0 · Reproject | Screen-space post-pass | Applied to the display texture after tonemap, only when an enabled projector is fully wired. The float film never sees it |
| 1 · Project | Rasteriser and path tracer | Repaints albedo at the hit. A sub-option decides whether the photo is shown unlit — an exact colour match, absorbed on contact — or becomes a surface colour that the scene then lights |
| 2 · Forward | Rasteriser only | The one mode the traced image never sees |
| 3 · Light cone | Path tracer, as a luminaire | A perspective cone from the captured apex. Image colour × intensity × N·L is added at each hit — no shadow ray, no distance falloff, no MIS, and skipped entirely for the two pure light-tracing strategies and for light-sample-only |
| 4 · Light plane | Path tracer, as a luminaire | The same addition with an orthographic beam of a world size you set, so N·L is taken against the fixed projector forward rather than the direction to it |
| 5 · Bake to UV | A per-mesh UV texture the tracer then reads | The baking mode. Covered below |
Mode 5 is the only one that leaves something behind. The rasteriser renders the projection into texture space: for every mesh with a layer in the tracer's baked-albedo array, it binds that layer as a framebuffer attachment, draws the mesh through a UV-space vertex shader at the chosen resolution, and writes the projected photograph into the texels the mesh covers. There is deliberately no clear — texels the projection misses keep the tracer's own material fill, so a bake paints onto a surface rather than replacing it.
Three controls belong to that mode alone. Bake res runs 64 to 4096 and, because a high-resolution bake is slow, it does not re-bake while you drag: a separate Re-bake button commits it. Bake side is a three-way choice — the side facing the projector, the side facing away, or both — which is the question a decal system has to answer and usually answers silently. The result is not a decal or an overlay: those layers become entries in the array the path tracer samples for albedo, so the photograph is lit, shadowed and reflected like any other surface colour.
Two limits worth stating in the same breath. No CPU integrator sees any of the six modes — every projector uniform is written by the megakernel's uploader and the rasteriser, and by nothing else, so the entire projector surface is GPU-only in a renderer whose research half is on the CPU. And one function that looks like the CPU's way in, sampleProjectorBake, has zero callers anywhere, although the header comment above it describes the CPU material bakes calling it per texel.
04 — A curve for reflectance, none for emission
Spectral rendering here is whole, and all of it but one piece runs on the GPU. A hero wavelength is drawn per path, uniformly across the visible band, carried through the bidirectional connection and drawn again for the light-tracer path; conversion home is the CIE 1931 curves. Reflectance is up to eight Gaussian lobes per material, edited on a rainbow canvas that replaces the ordinary colour picker, packed into the material buffer and read back in the shading module. Spectral mode compiles its own kernel variant. Thread 04 covers that machinery, and it is the part of it that works.
What has no spectrum is emission. The scene material carries a field named spectralEmission with a full spectral-curve type behind it, and that field has exactly one reference in the entire program: its own declaration. The consequence is visible rather than theoretical — in spectral mode an emitter falls back to its albedo curve, or to plain luminance when it has none. A spectral light source in this renderer borrows the spectrum it reflects with. A second field, spectralSPP, was declared for wavelength samples per pixel and has the same one reference.
One piece of the spectral work is not GPU-only, and it is the piece the research half needed: dispersion. The CPU estimator evaluates the identical Cauchy expression per colour channel, anchored at the sodium D line, written deliberately verbatim from the shader so the two cannot drift — which is why the dispersive caustic on thread 01 can be compared against the megakernel at all.
05 — The absences, one clause each
A field with one reference is a promise the program has not kept. These are cheap to find and cheap to state, and stating them is the only reason a reader can trust the rest of the site: a portfolio that lists only what works has not told you how it counted.
Every row below is a name that exists in the source and reaches nothing. None of them earns a paragraph.
| Name | What it was for | Grade |
|---|---|---|
| spectralEmission | An emission spectrum per material. One reference; a spectral emitter uses its albedo curve instead (§04) | absent |
| spectralSPP | Wavelength samples per pixel per frame. One reference | absent |
| albedoTexture / normalTexture / roughnessTexture | Three texture indices on the material struct. One reference each — textures reach the tracer through the node graph and the projector bake instead | absent |
| envMapIndex | An HDR environment map on the scene. One reference. The sky is procedural | absent |
| hasSSS / sssRadius | A per-material subsurface flag and mean free path. The subsurface material works; these two fields are not how it is reached | absent |
| LightType Directional / Environment | Two of the four light kinds. The rasteriser and the light panel handle both; the megakernel's uploader takes only Point lights from scene.lights[], so neither reaches the traced image by that route | absent in the tracer |
| aoIgnoreSurfaceType | “Treat all surfaces the same” for ambient occlusion. One reference | absent |
| pmPrimitiveType | Point / beam / surface selection for the photon mapper. One reference — the photon primitives live in the CPU estimator instead, where they are thread 01 | absent |
| allowNegativeEmission | Not absent, and worth the exception: it gates a signed emission slider, and it turns itself on when a material is found already carrying negative emission | seen |
| Fourteen orphan headers | 1,410 lines. No file includes any of them and none has an implementation file: SDF.h, Timeline.h, Pipeline.h, NDScene.h, NeRF.h, Medium.h, DiffRenderer.h, Holography.h, DebugDraw.h, SamplingVis.h, FieldVis.h, FlatlandView.h, Registry.h, DipoleApprox.h. Nine of them, 637 lines, are the ones the rest of this page names for other reasons — four Tools entries, the Advanced tab, the holography bench, a subsurface approximation; the whole list came out of a scan of every header in the tree for an includer | absent |
| Marching cubes | 153 lines that compile into the binary. Its header is included by no other file, and a second, independent declaration of the same algorithm sits inside one of the orphans above | absent |
| tinyobjloader | Vendored and linked, zero call sites. Not one triangle reaches the GPU — and thread 04 argues that this is a decision, not an omission | absent |
06 — Reachable, and does nothing
Thirty-two catalogue rows are graded inert: reachable, and reaching nothing. That is a large number and it is published rather than hidden, because the alternative — a slider that appears to work — is the failure mode that costs a reader a day. Most of them are greyed at the source with a tooltip that says why, and a handful carry the date the correction was made.
The register in §02 has the ones that belong to a half-built module. These are the strays — and the last two rows are here for contrast, because a tab that only signposts and a tab that is entirely live are the two things an inert-control audit keeps mistaking for each other.
| Control | Where | What it reaches |
|---|---|---|
| Compute / CUDA / OptiX | Tracer tab | A function-local static. Disabled, with a tooltip saying the GL compute shader is the only backend that exists and that this group is read nowhere |
| Viz Mode | VCM, twice | Nothing, and titled [inert] in both places |
| Beam-beam merge | UPBP | Assigned from the UI, read nowhere — recorded as such in the integrator's own header |
| MSAA | Rasteriser tab | Nothing. The GL context is created with one sample, so the raster view is aliased however the knob is set |
| Debug tab | Render Settings | Three lines of signposting and no control at all: it says its contents moved to the tabs that own them, and names two of the destinations. Empty by design, which is not the same as inert |
| Misc tab | Render Settings | Two toggles, both live: select in tracer mode, select in FPS mode with a crosshair. The smallest tab in the program, and it is in this table only to be the row that is fine |
07 — What every screenshot is taken through
Thread 07 covers the authoring apparatus — the node editor, the picker, the gizmo, the alarms, and the hotkey collision it found by reading both handlers. What it does not cover is the surface the camera work runs through, and that surface is not incidental to the research: a hotkey that switches the view mode without touching the accumulation buffer, or a clean-view key that hides every overlay, is the difference between a usable frame and a re-run. Everything below has been seen in the running program — that is what the grade means — and none of it has ever appeared in a figure. These are one line each, and the line is the point.
| Surface | What it is | Grade |
|---|---|---|
| F1 – F8 | Eight view modes — shaded, wireframe, normals, UV, depth, tangent, material ID, checker — each logging its own name as it switches | seen |
| E / R / Q, in FPS capture | Projector on, wind on, dolly zoom on. The dolly key stores the camera's original position, look-at and focal length, and puts them back on release if the dolly moved the camera. The projector half is the one piece here the catalogue still grades never viewed | seen |
| Ctrl+1 / 2 / 3 | Pinhole, thin lens, tilt-shift on the C++ camera — which the megakernel does not read (thread 10) | seen |
| Window title | Frame rate, active renderer, camera type and frame number, rewritten every frame. A screenshot of the title bar dates and identifies the run | seen |
| UI Test panel | A gallery of the custom widget set — eight knob styles, eight toggle styles, meters, LEDs and progress bars, the parts the rest of the panels are built from — kept as a live reference under View | seen |
| Style Editor | Global and per-panel header style overrides, small-widget vertical alignment in absolute pixels and as a factor, and a box-label mode, in its own window | seen |
| Scene Hierarchy | The entity tree, docked left, and the route into the node editor's focus-on-node request | seen |
| Background depth prepass | The rasteriser renders a linear-depth buffer every frame whichever integrator is active, before the image-operator chain runs. That is why a depth-driven operator and the exact selection overlay both work while a CPU integrator with no depth buffer of its own is the one drawing the picture | seen |
| Ambient Occlusion | A separate GPU render mode, not a post-process. The megakernel replaces its whole shading path with a cosine-weighted hemisphere visibility test — radius, sample count and intensity — and returns white where nothing is hit. This page is the first place on the site that names it | seen |
08 — The vocabulary nobody has typed
Every number on thread 01 came out of a headless run, and a headless run is a config file. The estimator's configuration surface is 67 distinct keys under the pp. prefix, read in one block when the config selects the estimator mode. Most of the concepts are described elsewhere on this site; the keys are the part that would let somebody else reproduce a figure, and until this table they had not been printed.
They are worth one table because the shape of the list is itself the argument: a research estimator needs a switch for every claim it makes, including the switches that exist only to turn a claim off and prove the film does not change.
| Key | What it selects |
|---|---|
| pp.strategy | Which of the 31 slots runs. 0–2 are the baselines, 25 is the rasterised gather, the rest are the primitive family |
| pp.misMask | A bitmask choosing the subset of primitives that enter the mixture, so any sub-combination is one integer |
| pp.misBalance | Balance heuristic on or off, which is how a mixture is compared against its own members |
| pp.surfaceNEE / pp.surfaceBSDF / pp.surfaceSpec / pp.specNEE | The camera walk's shading: one Lambertian connection, the BSDF-general version, specular chains, and next-event through them |
| pp.surfPhotons / pp.surfPhotonCount / pp.surfPhotonRadius / pp.surfPhotonKMask | The surface caustic photon map, its budget, its fixed radius, and the bounce-class mask that isolates one contribution for validation |
| pp.hgChain / pp.hgChainTol / pp.hgPostMirror / pp.hgRelay / pp.hgGuard / pp.hgJMin | The refraction chain and its collinearity tolerance, the post-ball mirror, relays, and the two guards that decide when the estimator refuses rather than draws |
| pp.correlated / pp.primCount / pp.freezePool / pp.corrRecords / pp.corrViz | Correlated mode: one batch of primitives shared across camera rays, its size, a batch frozen across passes so the sheets stop flickering and can be studied (its film then stops converging, and says so), the record-versus-rebuild switch, and the visualiser |
| pp.compBackup / pp.compSeam / pp.compClassLog | Complement mode — the backup estimator that owns the transport the primary does not, the seam policy, and the class counters |
| pp.b… (ten keys) | The B side of the strategy, the comp mode, the surface shading and the photon map, for split A/B rendering: two estimator configurations in one file |
| pp.raster / pp.rasterFamily | The rasterised gather and which primitive family it draws |
| pp.emitLambertian / pp.emitterCos | How many cosines an area light contributes in next-event — the area-to-solid-angle Jacobian is always one of them, the emitter's own directional term is the choice — and, on the photon side, whether the emitter is Lambertian. Two estimators cannot be compared until they agree what the lamp is, and this is where that agreement is set |
| pp.passesPerFrame | Accumulation passes per rendered frame. 0 is auto; 1 is the historical behaviour, where every pass paid a full frame of vsync, GL and UI — 7.0 ms against 0.7 ms of actual estimation. Byte-identical by construction at any value |
| pp.camPos / pp.camLookAt / pp.camFarCap | A headless camera override, so a figure is a config rather than a remembered mouse drag |
| pp.dumpCpuAt / pp.dumpGpuRefAt / pp.dumpPpRefAt / pp.compareMode | Write the film at a named pass, from the estimator, from the GPU reference, or both — which is where every ratio on this site comes from |
| pp.vizPrimitive / pp.vizSize / pp.vizGridN | Draw the swept manifolds themselves. Thread 02 is made of these |
One key outside the prefix, for the same reason. window.vsync exists because a per-pass timing measurement taken with vsync on measures the display, not the estimator.
A standalone crossing harness is a single translation unit that links nothing from the renderer and re-implements the general refraction chain's per-pixel crossing exactly as its specification describes it, so the cost of a design can be measured before it is built. It was written because the specification had withdrawn an earlier cost estimate whose two factors were both assumed, and then forbade replacing it with another guess. Its verdict on the off-axis pair, at the same resolution the rest of the family is measured at, is that the general chain would cost 14 to 42 times the one-ball estimator per pass — so that tier is a no-go, and the harness is the reason it was never started.
Two presets carry a correction in their own menu label rather than in a document. Jellyfish Giant (FBM, not SmoothLife) renames itself after the thing it actually builds. Caustic Room (enclosed, roam inside) says in its own entry that it must be viewed from within, which is the answer to the question its earlier name provoked every time somebody rendered it from outside and got a black frame.
Two more of the same kind sit in the scene layer rather than the estimator: vclGateGeom and vclGoboSlab, the shared aperture vocabulary the gate presets are built from — four apertures across five scenes: a slot, an annulus, a grille and a dispersive slit, all derived from one light position, one ball and one gobo height rather than typed in as coordinates. That is why the annulus can be said to match the slot's aperture area to 1.4 %: the two were computed from the same geometry, not eyeballed into agreement.
What this page does not do. It does not reprint the catalogue, and it does not re-grade anything: wherever docs/FEATURE-CATALOGUE.md carries a row, the grade shown here is that row's. Two entries have no catalogue row and are graded here for the first time — the second holography declaration, and the projector's mode-by-mode destinations. The counts are a different matter and were re-derived rather than copied, which is how the orphan-header figure moved from the catalogue's ten to fourteen. It does not claim the residue is complete: 551 is a floor, and the catalogue says so itself, because the 157 image operators are filed as 22 grouped rows and the 31 estimator slots as a far smaller number of grouped ones. And it does not photograph anything — the two schematics above were drawn, and the other five slots are four screenshot recipes and a clip that will stay recipes until somebody runs the program and follows them.