A dispersive caustic separating into a spectrum

A real-time ray-tracing framework, built from scratch · light transport research

RadianceLab

For the light paths that sampling only finds by accident.

Per pass, analytic caustic
7.05→1.17 ms
kernel-free on the sheet, no neural network, consistent as a pair; 0.76 ms of it is estimation · 217×140 film, good to about 10 % within a session
Strategy slots in the picker
31
3–24 and 26–30 are the family; 21 of the slots are analytic swept manifolds
Worst-pixel outlier
6291→1
removed by mixing three primitives
Equal-pass caustic error
2.1×
lower than the stochastic estimator
Catalogued features
549
493 work; 116 have never been deliberately rendered

The ten-second answer

Can you compute a caustic instead of gathering one?

RadianceLab may be the most feature-rich open real-time ray-tracing framework on GitHub. I have not found another that ships bidirectional path tracing inside its real-time kernel, interactive Metropolis, VCM, photon mapping and a full-reservoir ReSTIR next to fourteen material models, 157 image operators and a node graph, in one binary. The one open repository with comparable transport breadth (yuphin/Lumen) lists no participating media and no analytic estimators; the one with comparable material and volume breadth (LuxCore) is offline.

The research question underneath all of it is the one above. A refractive caustic in fog is the light path standard sampling cannot reach: next-event estimation cannot connect through a glass sphere, because the refraction is a delta, and path sampling finds it only by chance. The usual answer is to stop connecting and gather instead, with a kernel, and accept the blur. RadianceLab asks the other question. Sample some dimensions of a light path, sweep the rest into a geometric surface, intersect the camera ray with that surface in closed form, and divide by the co-area Jacobian of the sweep. No kernel, no radius, no blur. The answer is a family of twenty-one analytic swept manifolds — fix one of the photon’s three coordinates and sweep the other two, and fix two and sweep one for the three members that close the lattice — one of which carries a photon through two refractions and lands a volumetric caustic exactly; paired with the stochastic beam it converges to 1.0000 against a traced reference. The family is not a better way to sample what is already easy, and the site says so: where a connection exists, the camera walk's next-event estimate beats every swept member. It exists for the paths that have no connection to make — the caustic through glass, the emitter too sharp to sample, the light with more dimensions than a point sample covers — and there it lands what path sampling finds only by chance and kernels can only blur.

Sweeping a photon's free dimensions into an analytic manifold is an older idea than this project — Bitterli and Jarosz's photon planes, Deng et al.'s photon surfaces — and it was proven at offline cost: minutes to over an hour per image on sixteen Xeon threads for the planes, and no wall-clock published at all for the surfaces. This project does not implement either paper's primitive. It shows what the idea does when the implementation is fast: 1.17 ms per pass, no neural network anywhere, and a caustic waist nine times narrower than the kernel estimator's at equal passes. That is the true power of the idea, and the recording on the estimator page is built to show it. Every number on this site carries the measurement that settled it, and where a measurement contradicted something written earlier, the correction is on the page rather than quietly removed.

Shape of the thing

One scene, two render paths, one measured film

A scene is built procedurally from analytic shapes. It feeds two independent renderers that share nothing but the scene and the film format: a GPU megakernel that renders in real time, with bidirectional path tracing inside it, and a CPU integrator bank — Metropolis, VCM, UPBP, the photon primitives — that runs interactively. Sharing nothing is exactly what makes them useful as checks on each other. Agreement between two independent estimators is the strongest kind of evidence here, and where a claim rests on something weaker — a numpy port of the estimator, or a single run with no partner — the page that makes it says so.

Scene analytic shapes only no meshes GPU megakernel one dispatch · recompiles itself to the live feature mask 14 materials · 7 outputs CPU integrator bank 10 transport modes 31 photon-primitive slots 25 closed-form · 6 kernel Linear film 32-bit, untonemapped one dump per run Oracle ratio, energy, byte identity THE RESEARCH LIVES HERE
Two paths, one filmThe two renderers share the scene and the film format and nothing else. That independence is what lets one be used as evidence about the other, and it is why the strongest numbers here are ratios between two estimators rather than single numbers that look plausible.

The sixty-second answer

Eleven threads

The first ten are built and running. The first of those is a research result with a measured reference behind every member; the next nine are the framework it runs in, and several of them would be a project on their own. What differs is how much of each has been photographed, and the second tag on every card says so. The eleventh is the complement: everything the other ten passed over, including the modules that do not work yet.

01 · Estimators

Caustics without a kernel

Twenty-one analytic swept manifolds — a lattice that now closes — and a volumetric caustic through glass solved in closed form on its sheet: kernel-free there, consistent as a pair, no neural network, 1.17 ms a pass.

Analytic volumetric caustic beneath a glass sphere in fog
1.0000converged ratio against the stochastic reference
page written12 measured

02 · Visualisation

Where the fireflies come from

Tools that trace the sampling geometry itself and colour it by its own singularity.

shared p: pdfSel·pdfArea cancels → conditional-given-p measure w_i = p_i(s|p) / Σ_j p_j(s|p) · each p_i → 0 on its own tangency locus medium axis θ 9 cone · φ,t 7 sphere · θ,φ 8 disk · cosθ,t camera p ab ab ab x₇ x₉ x₈ 13 · sphere + disk + cone under one measure · 6291 → 1
6291 → 1worst pixel, before and after mixing
page written9 tool figures

03 · Evidence

How I know the pictures are right

A variance theory, a film format, a Python oracle, and a document that records where I was wrong.

f = ρ/π · cos_s · L_e · cos_y / r² diffuse wall · ρ emissive quad · L_e camera camera ray r n_y cos_y n_s cos_s no continuation y x_s surface NEE · one Lambertian connection · the L D E reference denominator
15variance rules, each cited to a run
page written3 measured films

04 · Engine

Ten integrators, one scene

A real-time GPU megakernel that recompiles itself to the render state, fourteen material models, seven ways to make fog.

Three glass spheres and two aimed mirrors casting caustics through fog
276shaders across the render stack
page writtenevery number source-anchored

05 · Imaging

Seeing light arrive

A holography bench that walks the inverse problem in fourteen displayable stages, scores itself in wraps against ground truth, and names ten setup faults. Never once photographed.

medium eye R θ φ splat x x′ a b film ω_L light y J = |ω_cam·ω_L| = dual of 07's |ω·d| R = cam free flight · 1/R² stays (in We) 2 roots → 2 pixels, each splatted 17 · camera sphere · the dual: splat to film · swept: θ, φ about the eye
0figures shot; the page is the shooting list
page writtenunphotographed

06 · Transport

Every way to connect a path

Real-time BDPT with every (s,t) family and a clickable strategy pyramid; interactive Metropolis, VCM and UPBP; and a ReSTIR that is a real reservoir implementation — RIS, temporal and spatial reuse — not a filter. Plus a precisely scoped claim about what the estimator family does and does not contain.

F20b · hourglass flavours sampled swept kernel member ψ φ r sheet 20 hourglass in engine revolution about a ior→1 ⇒ cone (9) 20-φ not built meridian sheet (ruled) ior→1 ⇒ disk (8) 20-r not built r shell at fixed r ior→1 ⇒ sphere (7) V2 fixed-apex Python only θᵢ+xₛ nxₛ cone about n at xₛ one interface, pdf_A·p_c 18 beam in engine tube: ray + 1-D kernel stochastic reference 19 point in engine point + 3-D kernel coverage, Θ(rad²) not drawn: mixed-L (traced light leg, sweep φ,r) · mixed-C (camera-seeded, sweep ψ,φ) — sketches only, see docs/hourglass/FLAVOURS.md the hourglass as a system: sample one of {ψ, φ, r}, sweep the other two
10transport algorithms, each graded by what it really implements
page written4 found inert

07 · Authoring

The instrument panel

The node editor, the comparison report and the ray tree — the apparatus that decided which picture to take.

57node types; 32 controls that do nothing, published
page writtenunphotographed

08 · Image operators

One hundred and fifty-seven ways to ruin a render

Ricci flow, Hodge decomposition and walk-on-spheres share a combo box with halftone. Nineteen of them read the renderer, not the image.

0 of 157operators ever deliberately photographed
page writtenunphotographed

09 · Simulation

Things that move on their own

Five solvers, reaction–diffusion, and seventeen shaders that carry state between frames. Three of the solvers reach the traced image.

3 of 5solvers whose state reaches the traced image
page writtenunphotographed

10 · The camera

The camera is not a viewpoint

An aperture with a measure: it adds a strategy to the BDPT mixture, five rejection-sampled bokeh shapes, and a second complete lens model that has never rendered a pixel.

0lens samples ever drawn by the C++ camera
page writtenunphotographed

11 · The residue

Everything that did not get a page

The complement of the other ten: thirteen half-built modules named without softening, the second projector's baking mode, the fields declared and never read, and the headless vocabulary every figure was made with.

the ten threads are a curated selection · the tag says which covers each box scene one description GPU megakernel one dispatch, real time CPU integrators eight, interactive estimator family transport bank imaging bench node graph image operators physics camera control surface 31 strategy slots 10 integrators projector, ToF, holography 57 node types 157 toggleable passes 3 of 5 reach the film two lens models panels, menus, hotkeys 01–03 04 06 05 07 08 09 10 11 the residue the WIP tab · the absences · the four dead tools · the inert selectors 67 headless keys · each of them crosses every box above
0 of 18controls in the WIP tab that reach a backend
page writtenunphotographed

Every claim, graded

The honest ledger

A renderer portfolio usually shows the good renders and stays quiet about the rest. This one publishes the whole catalogue: 549 features, of which 494 work and 116 have never been deliberately rendered — nearly a quarter of the working surface, which is what a framework this size looks like when it is counted honestly rather than curated. The table below is the part worth a reader’s time; the exhaustive version, every row with a source anchor, is in the repository. What backs each row is shown here. The most interesting grade is not the top one. Built but never looked at is where the surprises live, and three of the rows below were validated against a reference that nobody has yet seen an image of.

measured   a number, with the run behind it  ·  seen   looked at, not quantified  ·  never viewed   shipped, never rendered on purpose  ·  design only   specified, not built

FeatureWhat it doesEvidenceGrade
Photon-primitive rosterAnalytic caustics by sweeping a light pathper-row median 1.000measured
Three-plane mixtureRemoves the grazing singularityworst pixel 259 → 1measured
Direction-sweep trioSphere, disk and cone mixedworst pixel 6291 → 1measured
Cross-family mixtureThe three planes and the direction trio under one light-area measure, six closed-form densitieslowest per-pass variance at every emitter size; energy agrees with 12 and 13 to 0.1 %measured
Light-size seriesOne glass ball, five emitter sizes from 2° to 77°, every estimator timedboth written crossovers falsified inside the rangemeasured
Surface caustic photon mapThe photons the beam walk already traces, deposited at the first diffuse hit behind glass and gathered by the camera walk; the floor under every glass ball lights upenergy within 2 % of a first-principles trace on a receiver 62 % hidden by the ball; 21 pinned films byte-identical with it offmeasured
Direction-preserving surfacesNot only the estimator's probes: every integrator in the mode menu now sees through a passthrough wall, so the camera stands outside the fog box on all of thempreset 63 renders the Reliquary from outside on modes 2 through 8 — means 0.1988 and 3.47 to 8.89, against exactly 0.000000 through the same wall left opaque on every one of them whose control finished; 21 pinned films byte-identicalmeasured
Continuous-orientation planesClosed-form envelope over the sweep anglemedian 0.999measured
Photon hourglassCaustic through a dielectric spheremedian 0.976measured
Hourglass and beam mixedSpectral, relay-aware pairing1.0000 against the beam at the same radius; 2.1× lower error; consistent, not unbiased — its energy reads +0.9 % at r = 0.15 against its own r = 0.0375 rungmeasured
Vertex hourglass, entry-seededSeeds on the glass, not the lightenergy 1.007 / 1.020measured
Exit refraction coneTwo cones apex-to-apex at the vertex1.0001 against the hourglassmeasured
Reflection coneFirst analytic reflection caustic here0.998 against the beammeasured
Stochastic photon beamThe kernel reference the rest is judged against1.048 against next-eventmeasured
Surface next-event referenceOne Lambertian connection at a hit1.0005 against the GPUmeasured
Variance studyWhy each estimator fails, and where15 rules, each citedmeasured
Byte-identical regressionA feature off must change nothing8 films, two resolutionsmeasured
Behaviour triage documentSymptom, expected, bug — with corrections6 corrections in one daymeasured
The in-engine gate stackFour logs behind one config key: an analytic airlight gate, an exact-Le camera gate, a relL2 against a GPU film, and two brute-force oracles at five named probe pixels that cross-check each other at one bounceGate C caught the 1/cos θL over-brightness on preset 34 that the analytic gate structurally could not see — the source says so at the line it fixedmeasured
Mirror relaysCaustics that arrive by reflection+0.16% of frame energymeasured
Photon ring-altThe family's first glossy vertex: the reflection cone taken off the delta, five free dimensions instead of three, the BRDF evaluated at every crossing1.0005 ± 0.0088 against the camera walk isolated to the same class at roughness 0.16; 2.2–3.1× lower variance per pass and 6.6–8.3× on equal time, with no crossover in rangemeasured
The one-dimensional groupThe user’s photon ring and photon arc, and their mixture with the tracer beam. Fix one of (θ,φ,t) and sweep two and you get the kernel-free cone, disk and sphere; fix two and sweep one and you get these — the lattice closes, and the beam’s familiar 1/sin θ turns out to be the same expression at unit speedworst pixel 545.7 → 5.0, lower than the beam’s own, and 2.7× the best single member per pass at r = 0.15, 2.5× at r = 0.05, at three draws per pass against that member's one; bias ladder over five kernel radii, every rung with its own bar; consistent, not unbiased — 0.33 % high at r = 0.30 where the ring alone is 33 % highmeasured
The float32 cliff it foundA lobe-sampled cosine pins one coefficient to 1 and the cone quadric's leading term cancels in single precision81.5× / 108.8× / 8.7× invented energy at roughness 0.05 / 0.09 / 0.16 before the shared solver was moved to doublemeasured
The refraction chainThe swept sheet pushed through a chain of glass balls instead of one — exact where the chain is collinear, and refused by name where it is not60.4×/116× lower variance per pass than the stochastic beam and 35.5×/48.5× on equal time; energy on the caustic mask 1.0023 ± 0.0061; the off-axis pair renders exactly zero with one refusal linemeasured
Post-ball planar mirrorLight → ball → fog → mirror → eye: a reflection is an isometry, so the camera ray is reflected and the same quadric is solved against it0.9979 ± 0.0017 against the same scene shot from the eye's mirror image; 14.5–28.3× the beam per pass on that viewmeasured
BSDF-general surface shadingThe estimator's camera walk shades every non-delta surface, not only Lambertian ones — Metal, Anisotropic, CoatedDiffuse and the Airy film, with the delta rule taken from the megakernel's own sourceMetal 1.0133 / 1.0096 / 1.0111 against the GPU at roughness 0.10 / 0.15 / 0.30, inside the band two Lambertian controls set at 1.0008 and 1.0141 in the same frame; reciprocity and white-furnace tables computed by the rendering binary itself; 21 pinned films identical with it off and onmeasured
The gate familyOne lamp, one gobo plane, four apertures: a slot, an annulus, a grille and a dispersive slitthe annulus matches the slot's aperture area to 1.4 % (2.98 cone² against 2.94) and still drives the shaft's core-to-limb ratio from 0.94 down to 0.40; the open variant's film mean falls 5.9× while its shaft-to-surround contrast rises 4.8× → 10.1×measured
GPU megakernel tracerOne dispatch for the whole transport, in real timeseen; fallback ladder in sourceseen
Real-time bidirectional path tracingBDPT inside the megakernel — eye and light subpaths, interior connections, all six (s,t) cells, MIS method B — with a clickable strategy pyramidseen; each cell isolable from the pyramid overlayseen
ReSTIR direct lightingResampled importance sampling into per-pixel reservoirs, temporal reuse against the previous frame's reservoirs, then spatial reuse from neighbours — the full construction, not a filter, replacing the tracer's direct term907 lines of GLSL across two compute passes; seenseen
Interactive MetropolisKelemen primary-sample-space MLT with lazy per-coordinate mutation, in the CPU integrator bankseen; interactive, not real-timeseen
Self-recompiling kernelSpecialises to the live render stateseen; measured instruction ceilingseen
Material models14 types, Lambertian through hair, subsurface and dispersive glassall 14 reachable from a preset; 56 covers the last fourseen
Spectral dispersionPer-object wavelength-dependent indexseen in the prism presetseen
Participating mediaHomogeneous and noise-modulatedseen; inert in the CPU integratorseen
Solid-angle sphere samplingNo wasted samples near the silhouetteseen; variance-only comparisonseen
The prism is two solidsPreset 30's dispersive prism is a 45° box to the path tracer and a 45° mesh to the rasteriser — and the tracer's half-extents were pre-fattened to the rotated bounding box before the GPU learned to rotate the ray, so the fattening is applied on top of the rotation it was compensating for0.5 → 0.7071 in X and Z: √2 wider, twice the volume. Read from source, not measuredseen
Node-graph material bakeTen channels of a graph baked per entity into three RGBA8 layers the megakernel samples — and a twelfth slot booked twice: the dispersive gate reads the same alpha as the dispersion spread it gatesthe layer comment says anisotropy angle, the write stores dispersionSpread / 10, and three of the eleven seeded BSDF inputs have no mask bit at allseen
VCM “Fix Bias”The checkbox reaches VCMConfig::fixBias and the field is read nowhere in the integrator — its own declaration says “always enabled; kept for UI compat”. The greyed MIS-mode selector beside it names this control as the live onezero references to fixBias in VCMIntegrator.cpp; the catalogue grades the row seen and describes it as toggling the weightsseen
Time-of-flight, three systemsModulation carried along the traced path, four-phase continuous wave off the depth buffer, and a geometric-path post-process with five light shapesimplemented, unphotographed; the path-length gate is a fourth, separate thingnever viewed
Off-axis holographyWith a live error readoutimplemented, unphotographednever viewed
Structured lightPattern projection; what gets decoded is phase, never pattern indeximplemented, unphotographed; no pattern decoder existsnever viewed
Two emission profilesCone and ring emittersno preset uses themnever viewed
Photon Primitive ValidatorPick a camera pixel and a photon on a light, budget the two legs against one total, sweep any vertex, and ask a zero pairing why it is zeroeleven rows in the inventory and not one of them has been on a screennever viewed
Time-of-flight noise modelsAn ambient term and a “shot noise” term on both synthetic time-of-flight laddersread from source: ambient is added to all four shots and cancels exactly in both differences the recovery is built from, and the noise term is uniform, signal-independent and frame-frozen. One capture would settle it, and the imaging page carries the recipenever viewed
Bilateral-grid previewA live-parametrised diagram of the bilateral grid drawn from the filter's own sliders, with the support ellipse at the cursorread from source: it splats and draws; the blur and the slice its own footer names are not performednever viewed
Complement modeA primary estimator plus a backup for the transport it does not own, partitioned by path class and never mixedbuilt; the backup walk reads 1.0008 on the diffuse half and 0.9949 on the glass against the GPU, preset 0measured
Correlated primitivesOne batch of primitives, every camera ray12 films byte-identical off; converges to the uncorrelated energymeasured
Gaussian-process surfacesMicrofacet and medium from one objectboth blocking gates passeddesign only
Camera-side primitivesSweep the camera path instead of the light, and splat the result onto the filmPP strategy 17; 0.992 against the camera walk on preset 48, both legs shadow-tested since 2026-09-05measured
Node-based material and medium authoring57 node types, live 2-D and orbiting 3-D previews, evaluated per entity by the GPU tracer. Its medium half is a different machine from its surface half, and neither reaches any CPU integratorfourteen of the nineteen node-editor rows in the catalogue are graded never viewed, the canvas among them; the five that are not describe what the graph reaches rather than the editor being looked at. Reachability is not viewing, and this row said otherwise until 2026-09-06never viewed
Structured-light projectorFour delta geometries with four falloff laws, seven patterns, a beam angle derived from a quad you size; a real next-event light, and on two of the four shapes a real emission disk, so it appears in mirrorsimplemented, unphotographed — this row read seen until 2026-09-06 on the strength of the ToF work, which uses the projector as a carrier and never as a lampnever viewed
The projector's top-down setup viewA draggable plan of the whole rig — scene, camera frustum, image and focal planes, the effective quad, the emitter, and the time-of-flight wavefront as an arc for a point source and a straight line for a panelimplemented, unphotographed; the difference it draws is the one the imaging page currently asserts in prosenever viewed
Photograph-to-lightA captured pose paints a photo into the tracer’s albedo array, where it then receives correct global illuminationimplemented, unphotographednever viewed
157 stackable image operatorsOver 21 non-photorealistic modes — Ricci flow and persistent homology share a combo with halftonegrouped, mostly unphotographedseen
Integrator comparison reportRenders each configuration, writes PNGs and a browsable HTML report, round-trips the run as JSONthe only working image export in the program — and nobody has generated a report and looked at it. This row read measured until 2026-09-06; the catalogue grades it never viewed and the catalogue is right, because writing a file is not a measurementnever viewed
Unit test suite61 assertions over the shape classifier and the evaluator; exit code 1 on failurealready a CI gatemeasured
Comp-mode class countersHunt double counts and holes that the mean, the median and a seam shift all misswired to the guard that can actually firemeasured
Three holographic reconstructionsOff-axis synthesis, GPU FFT sideband crop, and a WLS-PCG Poisson phase unwrapperself-scored in wraps against ground truthnever viewed
Scene-setup advisorTen rows naming each way your interference fringes fail — nine two-state, one warn-onlynothing else here diagnoses its own setupnever viewed
Non-standard-state alarmThe renderer notices it is in a diagnostic configuration and offers a one-click way outseenseen
Interactive ray treeWalk a single path’s branching structure in the viewportimplemented, unphotographednever viewed
Cone sphere and volume emitter samplingSolid-angle sampling with its legacy alternative kept beside it for comparisona variance-only A/Bseen
Five physics solversCoupled to a path-traced medium — though only the Euler solver feeds the gridseenseen
Classical vision bankGPU Hough with compute non-maximum suppression, epipolar geometry, stereo, four-mode reconstructionimplemented, unphotographednever viewed
A finite aperture as a transport objectOpening the pinhole adds a light-tracer connection strategy to the bidirectional MIS count, so a camera control changes which paths existthe strategy counter reads the aperture radius directly; the panel warns in red when it is zeroseen
Shaped-aperture bokehCircle, polygon, star, cross and ellipse, rejection-sampled in the shader, with blades, tilt, inner radius, arm width and aspectseen; GPU only — every CPU integrator rebuilds a bare pinholeseen
Two aperture implementationsA complete C++ lens model beside the megakernel's own, with a different shape setevery call site passes a zero lens sample, so the C++ aperture sampler has never produced a pixel and its shape enum has no writer outside its own directoryunreachable
The projection self-checkThe rasterised gather establishes that the camera it was handed really is the inverse of the projection it is about to use15 probes at 0.05 px; on failure it refuses and names the camera, and the tracing gather runs insteadmeasured
Goldstein branch-cut unwrappingA second phase-unwrapping algorithmthe function body is empty and its selector has no writerdesign only

Stated, not discovered

What this is not

There is no mesh loading, by decision. Analytic primitives give exact ray intersections, and exactness is the whole point: the hourglass estimator depends on rotational symmetry about the axis from the light to the sphere's centre, and a tessellated sphere destroys it. Spheres, planes, boxes, fog and light is the entire palette, and the scenes are composed for it.

There is no acceleration structure on the GPU. Every ray tests every primitive. With scenes of a few dozen analytic shapes that is the right trade, and the tree code that exists is used by the photon mapper rather than the tracer.

There is no dedicated denoiser, no image export from the viewport, and no temporal accumulation across camera moves. No OIDN, no OptiX denoiser, no A‑SVGF: those three names occur in this repository only in the comment that says they are absent, and the two blurs in the post panel are blurs — the panel prints “Blurs, not a denoiser. There is no denoiser here.” in its own header. The one route a picture takes out of this program is the comparison report, which writes PNGs. A denoiser in particular would undermine the argument: this project is about where the noise comes from, and every comparison on this site is readable because nothing hides it.

There is no caustic on a surface in the analytic family. It deposits only at scattering vertices in the medium, so the bright pool a real glass ball throws on a floor is absent by construction. It is the largest hole in the family and the next one to fill, and it is named here rather than cropped out of the renders.