A real-time ray-tracing framework, built from scratch · light transport research
For the light paths that sampling only finds by accident.
The ten-second answer
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
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.
The sixty-second answer
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
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.
02 · Visualisation
Tools that trace the sampling geometry itself and colour it by its own singularity.
03 · Evidence
A variance theory, a film format, a Python oracle, and a document that records where I was wrong.
04 · Engine
A real-time GPU megakernel that recompiles itself to the render state, fourteen material models, seven ways to make fog.
05 · Imaging
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.
06 · Transport
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.
07 · Authoring
The node editor, the comparison report and the ray tree — the apparatus that decided which picture to take.
08 · Image operators
Ricci flow, Hodge decomposition and walk-on-spheres share a combo box with halftone. Nineteen of them read the renderer, not the image.
09 · Simulation
Five solvers, reaction–diffusion, and seventeen shaders that carry state between frames. Three of the solvers reach the traced image.
10 · The camera
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.
11 · The residue
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.
Every claim, graded
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
| Feature | What it does | Evidence | Grade |
|---|---|---|---|
| Photon-primitive roster | Analytic caustics by sweeping a light path | per-row median 1.000 | measured |
| Three-plane mixture | Removes the grazing singularity | worst pixel 259 → 1 | measured |
| Direction-sweep trio | Sphere, disk and cone mixed | worst pixel 6291 → 1 | measured |
| Cross-family mixture | The three planes and the direction trio under one light-area measure, six closed-form densities | lowest per-pass variance at every emitter size; energy agrees with 12 and 13 to 0.1 % | measured |
| Light-size series | One glass ball, five emitter sizes from 2° to 77°, every estimator timed | both written crossovers falsified inside the range | measured |
| Surface caustic photon map | The 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 up | energy within 2 % of a first-principles trace on a receiver 62 % hidden by the ball; 21 pinned films byte-identical with it off | measured |
| Direction-preserving surfaces | Not 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 them | preset 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-identical | measured |
| Continuous-orientation planes | Closed-form envelope over the sweep angle | median 0.999 | measured |
| Photon hourglass | Caustic through a dielectric sphere | median 0.976 | measured |
| Hourglass and beam mixed | Spectral, relay-aware pairing | 1.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 rung | measured |
| Vertex hourglass, entry-seeded | Seeds on the glass, not the light | energy 1.007 / 1.020 | measured |
| Exit refraction cone | Two cones apex-to-apex at the vertex | 1.0001 against the hourglass | measured |
| Reflection cone | First analytic reflection caustic here | 0.998 against the beam | measured |
| Stochastic photon beam | The kernel reference the rest is judged against | 1.048 against next-event | measured |
| Surface next-event reference | One Lambertian connection at a hit | 1.0005 against the GPU | measured |
| Variance study | Why each estimator fails, and where | 15 rules, each cited | measured |
| Byte-identical regression | A feature off must change nothing | 8 films, two resolutions | measured |
| Behaviour triage document | Symptom, expected, bug — with corrections | 6 corrections in one day | measured |
| The in-engine gate stack | Four 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 bounce | Gate 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 fixed | measured |
| Mirror relays | Caustics that arrive by reflection | +0.16% of frame energy | measured |
| Photon ring-alt | The family's first glossy vertex: the reflection cone taken off the delta, five free dimensions instead of three, the BRDF evaluated at every crossing | 1.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 range | measured |
| The one-dimensional group | The 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 speed | worst 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 % high | measured |
| The float32 cliff it found | A lobe-sampled cosine pins one coefficient to 1 and the cone quadric's leading term cancels in single precision | 81.5× / 108.8× / 8.7× invented energy at roughness 0.05 / 0.09 / 0.16 before the shared solver was moved to double | measured |
| The refraction chain | The 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 not | 60.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 line | measured |
| Post-ball planar mirror | Light → ball → fog → mirror → eye: a reflection is an isometry, so the camera ray is reflected and the same quadric is solved against it | 0.9979 ± 0.0017 against the same scene shot from the eye's mirror image; 14.5–28.3× the beam per pass on that view | measured |
| BSDF-general surface shading | The 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 source | Metal 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 on | measured |
| The gate family | One lamp, one gobo plane, four apertures: a slot, an annulus, a grille and a dispersive slit | the 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 tracer | One dispatch for the whole transport, in real time | seen; fallback ladder in source | seen |
| Real-time bidirectional path tracing | BDPT inside the megakernel — eye and light subpaths, interior connections, all six (s,t) cells, MIS method B — with a clickable strategy pyramid | seen; each cell isolable from the pyramid overlay | seen |
| ReSTIR direct lighting | Resampled 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 term | 907 lines of GLSL across two compute passes; seen | seen |
| Interactive Metropolis | Kelemen primary-sample-space MLT with lazy per-coordinate mutation, in the CPU integrator bank | seen; interactive, not real-time | seen |
| Self-recompiling kernel | Specialises to the live render state | seen; measured instruction ceiling | seen |
| Material models | 14 types, Lambertian through hair, subsurface and dispersive glass | all 14 reachable from a preset; 56 covers the last four | seen |
| Spectral dispersion | Per-object wavelength-dependent index | seen in the prism preset | seen |
| Participating media | Homogeneous and noise-modulated | seen; inert in the CPU integrator | seen |
| Solid-angle sphere sampling | No wasted samples near the silhouette | seen; variance-only comparison | seen |
| The prism is two solids | Preset 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 for | 0.5 → 0.7071 in X and Z: √2 wider, twice the volume. Read from source, not measured | seen |
| Node-graph material bake | Ten 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 gates | the layer comment says anisotropy angle, the write stores dispersionSpread / 10, and three of the eleven seeded BSDF inputs have no mask bit at all | seen |
| 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 one | zero references to fixBias in VCMIntegrator.cpp; the catalogue grades the row seen and describes it as toggling the weights | seen |
| Time-of-flight, three systems | Modulation carried along the traced path, four-phase continuous wave off the depth buffer, and a geometric-path post-process with five light shapes | implemented, unphotographed; the path-length gate is a fourth, separate thing | never viewed |
| Off-axis holography | With a live error readout | implemented, unphotographed | never viewed |
| Structured light | Pattern projection; what gets decoded is phase, never pattern index | implemented, unphotographed; no pattern decoder exists | never viewed |
| Two emission profiles | Cone and ring emitters | no preset uses them | never viewed |
| Photon Primitive Validator | Pick 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 zero | eleven rows in the inventory and not one of them has been on a screen | never viewed |
| Time-of-flight noise models | An ambient term and a “shot noise” term on both synthetic time-of-flight ladders | read 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 recipe | never viewed |
| Bilateral-grid preview | A live-parametrised diagram of the bilateral grid drawn from the filter's own sliders, with the support ellipse at the cursor | read from source: it splats and draws; the blur and the slice its own footer names are not performed | never viewed |
| Complement mode | A primary estimator plus a backup for the transport it does not own, partitioned by path class and never mixed | built; the backup walk reads 1.0008 on the diffuse half and 0.9949 on the glass against the GPU, preset 0 | measured |
| Correlated primitives | One batch of primitives, every camera ray | 12 films byte-identical off; converges to the uncorrelated energy | measured |
| Gaussian-process surfaces | Microfacet and medium from one object | both blocking gates passed | design only |
| Camera-side primitives | Sweep the camera path instead of the light, and splat the result onto the film | PP strategy 17; 0.992 against the camera walk on preset 48, both legs shadow-tested since 2026-09-05 | measured |
| Node-based material and medium authoring | 57 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 integrator | fourteen 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-06 | never viewed |
| Structured-light projector | Four 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 mirrors | implemented, 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 lamp | never viewed |
| The projector's top-down setup view | A 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 panel | implemented, unphotographed; the difference it draws is the one the imaging page currently asserts in prose | never viewed |
| Photograph-to-light | A captured pose paints a photo into the tracer’s albedo array, where it then receives correct global illumination | implemented, unphotographed | never viewed |
| 157 stackable image operators | Over 21 non-photorealistic modes — Ricci flow and persistent homology share a combo with halftone | grouped, mostly unphotographed | seen |
| Integrator comparison report | Renders each configuration, writes PNGs and a browsable HTML report, round-trips the run as JSON | the 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 measurement | never viewed |
| Unit test suite | 61 assertions over the shape classifier and the evaluator; exit code 1 on failure | already a CI gate | measured |
| Comp-mode class counters | Hunt double counts and holes that the mean, the median and a seam shift all miss | wired to the guard that can actually fire | measured |
| Three holographic reconstructions | Off-axis synthesis, GPU FFT sideband crop, and a WLS-PCG Poisson phase unwrapper | self-scored in wraps against ground truth | never viewed |
| Scene-setup advisor | Ten rows naming each way your interference fringes fail — nine two-state, one warn-only | nothing else here diagnoses its own setup | never viewed |
| Non-standard-state alarm | The renderer notices it is in a diagnostic configuration and offers a one-click way out | seen | seen |
| Interactive ray tree | Walk a single path’s branching structure in the viewport | implemented, unphotographed | never viewed |
| Cone sphere and volume emitter sampling | Solid-angle sampling with its legacy alternative kept beside it for comparison | a variance-only A/B | seen |
| Five physics solvers | Coupled to a path-traced medium — though only the Euler solver feeds the grid | seen | seen |
| Classical vision bank | GPU Hough with compute non-maximum suppression, epipolar geometry, stereo, four-mode reconstruction | implemented, unphotographed | never viewed |
| A finite aperture as a transport object | Opening the pinhole adds a light-tracer connection strategy to the bidirectional MIS count, so a camera control changes which paths exist | the strategy counter reads the aperture radius directly; the panel warns in red when it is zero | seen |
| Shaped-aperture bokeh | Circle, polygon, star, cross and ellipse, rejection-sampled in the shader, with blades, tilt, inner radius, arm width and aspect | seen; GPU only — every CPU integrator rebuilds a bare pinhole | seen |
| Two aperture implementations | A complete C++ lens model beside the megakernel's own, with a different shape set | every 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 directory | unreachable |
| The projection self-check | The rasterised gather establishes that the camera it was handed really is the inverse of the projection it is about to use | 15 probes at 0.05 px; on failure it refuses and names the camera, and the tracing gather runs instead | measured |
| Goldstein branch-cut unwrapping | A second phase-unwrapping algorithm | the function body is empty and its selector has no writer | design only |
Stated, not discovered
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.