Image operators · RadianceLab

One hundred and fifty-seven
ways to ruin a render

Sitting on top of a physically-based path tracer is a bank of stackable image operators that is much stranger than a post-process stack. Ricci flow, Hodge decomposition, persistent homology and walk-on-spheres share a combo box with halftone, ASCII art and Kuwahara. The strangeness is the point.

This page expands a single row on the landing-page ledger into 157 operators, three families, and an audit of every theorem-named shader against its own source. It is also the least photographed territory in the project — almost every figure below is a capture instruction, not a capture — and the shot list is a deliverable in its own right: eighteen captures nobody has taken — fifteen stills and three clips — of operators that all exist and are all one click away.

Dispatch sites in the stackable chain
157
counted in EngineRender.cpp; the CV filter slot immediately after is a 158th
Operator toggles in the panel
164
FancyKnob("##…_toggle") widgets across two bands
Fragment shaders behind them
200+27
.frag and .comp in shaders/; 8 files are orphaned
Ever deliberately photographed
0
no operator in the bank appears in any image in this repository

01 — What is actually there

Not a filter pack. A second renderer that happens to run on the first one's output

The landing page carries one row for all of this: "157 stackable image operators — over 21 non-photorealistic modes — Ricci flow and persistent homology share a combo with halftone." That row is the only place on this site the bank exists. Everything below is what is behind it.

The claim is not "we have effects". Every renderer has effects. What is unusual here is the company these operators keep: a lattice U(1) gauge field with Wilson-loop holonomy is nineteen rows below Risograph misregistration, in the same scrolling panel, and both are one click. And what is unusual in the other direction is that fifty-five of them read the path tracer's G-buffer, so they are not image filters at all — run them on a photograph and they do nothing.

The bank is genuinely wired. That was the first thing to check, because a list of 157 named toggles is exactly the sort of thing that turns out to be a menu with no engine behind it. It is not.

Dispatch sites displayTex = apply…() in the stackable chain157
— inside EngineRender.cpp:2710–3036, up to and including the CV / ToF / holography sub-block146
— inside :3825–3849 (stat-physics, research-grade math, tail)11
— then applyCVFilter at :3854, one line after the chain's block closesthe 158th
apply* function definitions across EngineVFX.cpp / EngineVFX2.cpp / EngineNPR.cpp183
C++ lines in those three files8,136
bool …Enabled flags in UIManager.h with no reader outside src/ui/3 of 178

Every operator toggle in the bank reaches a dispatch site and a shader, and that is the claim worth making. An earlier draft of this paragraph tried to settle it with a stronger one — that of the 178 *Enabled flags the UI declares, exactly one has no reader outside src/ui/. That is wrong: three do. diffRenderEnabled is the one that draft had in mind; envEnabled is defensible, because UIManager.h:214 says it zeroes envStrength at the use sites; and the third is a good joke at this page’s expense. ltEnabled has exactly one reference in the entire tree, its own declaration, and it is the light tracer’s enable flag — sitting a few lines from the comment that this project’s whole reachability audit exists because of. The bank is genuinely wired; the sentence advertised as proving it was not.

On the number 157. The catalogue's figure is 157 and the landing page repeats it. The chain itself holds exactly 157 dispatch sites — 146 in EngineRender.cpp:2710–3036 and 11 in the tail at :3825–3849 — and applyCVFilter, one line after the block closes, is a 158th. An earlier version of this read-out stopped the chain at :3010 and reached 157 by adding the CV filter to a short count of 156; it landed on the right total by cancelling two errors, and the group table in §08 (which sums to 157 without the CV filter) was right all along. Separately there are 164 _toggle knobs in UIManagerRender.cpp, and the gap to 157 is not the single subtraction an earlier version of this note claimed. Eight of the 164 drive something other than a bank operator — spectralMode, volLightSampling, bdptPyramid, enableMedium, ppDOFEnabled, ppGlowEnabled, cvFilterEnabled and dataMoshEnabled — leaving 156; the 157th operator, tofRecoveryEnabled, has no _toggle at all but an ImGui::Checkbox in a different file (UIManagerPanels.cpp:1034). 164 − 8 + 1 = 157. Four of the 157 are mutually exclusive (see the conformal note in §07), so the largest number that can be simultaneously on is 154.

ONE FRAME, LEFT TO RIGHT — ORDER IS FIXED IN SOURCE, NOT USER-ORDERED raster or tracer NPR one of 21 the stackable chain — 157 operators 22 dispatch groups, applied in a fixed order spatial first → artistic → field-based → neural last CV filter · DOF · photo capture · mosh projector · glow · temporal blur · overlay modes 1–5 also run inside both renderers, per sample, before the accumulator the in-engine photo tap sits HERE: post-VFX, pre-mosh. Whatever the bank did to the frame is what a Texture node captures. Anchors — NPR dispatch EngineRender.cpp:2602–2694 · chain block :2702–3851, sites :2710–3036 and :3825–3849 · CV filter :3854 · tail :3856–3935 · capture :3869
The pipelineTwo things this diagram settles. First, the order is fixed in source — you cannot reorder the stack, you can only choose which of the 157 are on, and they always compose in the same sequence. Second, the NPR combo is not part of the bank: it is a one-of-21 that runs before it, so an NPR mode and any number of bank operators compose, and the two Kuwaharas in this program (NPR mode 11 and the bank's "Kuwahara Multi-Scale") can both be on at once.

Three families, not one list

Presented as a flat list of 157 shaders this reads as sprawl. It is range. Three quite different projects share one panel — second-pass shading that reads the renderer's own geometry, a bank of operators named after theorems, and a set of output-device emulators — and each carries a claim the other two cannot make. The rest of the page is three contact sheets, one per family.

How to read the marks. This page uses the catalogue's grades. measured a number exists and is checkable. seen looked at, unquantified — or, on this page, "dispatched and read in source but with a caveat stated". never viewed reachable, apparently never deliberately rendered. inert / orphan a reachable control that reaches nothing, or a file with no caller. The overwhelming majority of this territory is never viewed, and that is the most interesting thing about it: the shot list below is a list of images nobody has ever seen, all of which exist, all of which are one click away.

02 — Contact sheet I

Fifty-five of them cannot be run on a photograph, and the panel says which

Buried in the Advanced VFX panel is a grading system nobody documented. Every operator row draws two glyphs beside its name: a star (gold, silver, amber or none) and a renderer badge. The badge is a tiny drawing of a raster frustum, a ray fan, or both — and the code that draws it takes three values.

BadgeMeansVFX bandCV bandDrawn as
0tracer only154ray fan alone
1works in both renderers752frustum + ray fan, side by side
2both, but better with the tracer297dimmed frustum + bright ray fan
—no badge drawn231—

What the two columns add to. The VFX band draws 119 badges over 142 toggles (drawBadge, UIManagerRender.cpp:1800); the CV band draws 13 over 14 (drawBadgeCV), the unbadged one being holoV2_toggle at :4503. 142 + 14 = 156, which is every operator with a _toggle; the 157th is the checkbox named in §01. An earlier version of this table put 0 in the CV column's last cell and so summed to 155.

So the author already sorted the bank by how much renderer it needs, in the UI, and then never wrote it down. Nineteen operators are marked tracer-only. Those nineteen are the contact sheet:

Surface Flow PaintingBrush strokes follow principal curvature directions computed from the normal field — not from an image gradient.
3D Surface VoronoiVoronoi cells seeded in world space via the position/material buffer, so the cells stick to surfaces under camera motion.
Curvature StipplingStipple density from discrete curvature of the depth surface.
Light SculptingRelights the frame from the G-buffer alone.
SS Subsurface ScatteringScreen-space SSS. Note: unrelated to Material::hasSSS, which is a dead field.
SS CausticsFake caustics from normals and an IOR slider — sitting inside a renderer whose whole thesis is real analytic caustics.
Curvature EngravingEngraved line direction from curvature sign.
Normal Field WatercolourPigment pooling steered by the normal buffer.
Holographic IridescenceThin-film interference, view-angle dependent, using actual 3-D normals. Thickness 0.1–0.8, IOR 1.2–2.0.
Gauss Map VisualisationMaps each normal to its point on S² and colours by it — the one operator here whose name is exactly what it does.
Willmore EnergyIntegrated squared mean curvature of the depth surface, as heatmap, ink density or glow.
MatCap ShadingMaterial-capture lookup indexed by view-space normal.
Anime Shadow ControlTerminator placement driven by the normal buffer rather than by luminance.
Screen-Space ReflectionsA screen-space reflection pass, inside a path tracer that computes real ones.
Light ConeA volumetric cone marched in screen space from a position-buffer anchor.
Epipolar GeometryDraws the epipolar constraint between two camera poses. CV band.
Stereo CorrespondenceBlock-matching disparity in three layouts: side-by-side, anaglyph, disparity. CV band.
Geometry ReconstructionRebuilds 3-D structure from depth and normals in four modes, with an orbitable 3-D mini-window. CV band.
Tracer TOFGeometric path-length time-of-flight. Covered in depth on the imaging page; listed here only for completeness of the tracer-only set.

The limitation, in the same breath

The G-buffer is only bound when the tracer is active. The dispatch block opens with two lines that decide the whole family's behaviour:

normalTex = (activeRenderer == 1) ? gbufNrmRgh() : 0
and the same for posMatTex. Under the rasteriser both are texture zero. EngineRender.cpp:2704–2705
Operator shaders that declare uNormalTex58
— of those, carrying an explicit uHasGBuffer fallback20
— sampling the normal buffer with no declared guard38
Shaders mentioning uHasGBuffer at all — the other two guard depth and position instead22

Twenty of the fifty-eight branch on uHasGBuffer and fall back to a luminance-derived surrogate; linocut.frag:89 is a clean example. (Twenty-two shaders mention uHasGBuffer; the two extra — parallaxLayers.frag and volLightCone.frag — guard the depth and position buffers and never declare uNormalTex at all. Counting all 22 as guarded normals is the error this read-out used to make.) The other thirty-eight sample the normal buffer unconditionally. With texture zero bound that read returns black, and a normalize(vec3(0)) is not defined — so the predicted behaviour under the rasteriser is somewhere between "does nothing" and "NaN". I have not run it. That is the first shot on the list and it is a bug hunt as much as a photograph.

Marked unverified. The fallback claim is read from source, not observed. Nothing in this repository records what any of these thirty-eight operators looks like under the rasteriser.

Screenshot — Rasteriser fallback probe. Preset "Material Showcase". Press 2 for the tracer, let it settle 5 s. Render Settings ▸ Effects ▸ Advanced VFX [Experimental] ▸ enable ONLY "Gauss Map Visualization". Screenshot. Now press 1 for the rasteriser WITHOUT touching anything else and screenshot again from the identical camera. Two frames, side by side. What to look for: whether the second frame is black, unchanged, or full of NaN speckle — all three are informative and none is currently recorded anywhere.
Screenshot — The G-buffer family at work. Preset "RICH: The Apothecary". GPU tracer, let it converge ~200 passes, Clean View (G) on. Four frames from ONE unmoved camera: (a) clean, no operators; (b) Surface Flow Painting only, Brush 12, Length 2.0, Layers 3, Jitter 0.35; (c) 3D Surface Voronoi only, defaults; (d) Curvature Stippling only, defaults. What to look for: in (b) and (c) the pattern should be locked to the surfaces, not to the screen — that is the whole claim of this family and a still cannot prove it, which is why the clip below exists.
video · Painted on the object, not on the glass
Preset "RICH: The Apothecary", GPU tracer, Clean View. Enable Surface Flow Painting alone (Brush 12, Layers 3). Let it settle. Now orbit the camera slowly through about 50 degrees over 12 s and hold 3 s. Repeat the identical orbit with Surface Flow Painting off and "Painterly Strokes" on instead — that one is image-space. Cut the two orbits together.
Watch for whether the strokes swim across the surfaces or stay glued to them. The G-buffer version should read as paint applied to the objects; the image-space version should read as paint applied to the screen. This single comparison is the argument for the whole family and no recording of it exists.

Two operators in that list deserve a note rather than a shot, because they are funny. SS Caustics and Screen-Space Reflections are screen-space approximations of exactly the two phenomena this renderer computes correctly and at length elsewhere on this site. They are not redundancy by accident — they are the cheap version, kept, in the same program as the expensive version, and there is currently no picture comparing the two. That comparison is a shot on the list below and it is arguably the most useful thing on this page.

Screenshot — The cheap caustic against the real one. Preset "Hourglass Validation (1 glass ball)". Two frames, identical camera: (a) GPU tracer + Photon Primitives strategy 21, converged — the real analytic caustic; (b) GPU tracer with NO photon primitives, plus Advanced VFX ▸ SS Caustics enabled, Light Dir aimed at the ball, IOR 1.5, Intensity 2.0, Scale 1.5. What to look for: where the fake caustic puts its light. The screen-space version has no knowledge of the medium and cannot produce the volumetric fold at all — it should land on surfaces only, which is precisely the hole the analytic family exists to fill.

03 — Contact sheet II

A reader who knows the mathematics is exactly the reader who will check

The most striking thing in the panel is the row of names: Ricci Flow, Heat Method Geodesics, Hodge Decomposition, Walk on Spheres PDE, Persistent Homology, Gauge Theory Viz, Parallel Transport, Spinor Field, Optimal Transport Color, Morse-Smale Topology, Reeb Graph Contours, Laplacian Eigenfunctions, Willmore Energy, Cotangent Laplacian Flow. Several carry a citation in their tooltip. It would be easy to write a page that just listed those names.

So I read the shaders instead — every one below, end to end, against its own tooltip. The table gives each name, what the shader actually computes, and whether the name is earned. Three are earned outright. One is a plain mislabel. The rest are in between, and the table says exactly where, because a precise "in between" is a stronger result than a rounded verdict in either direction.

What I checked. The shader source for each row, end to end, plus the operator's UI tooltip. Not the rendered output — none of these has ever been rendered on purpose. Verdicts are about what the code computes, which is a question source can answer; nothing here is a claim about how the image looks.

OperatorWhat the shader computesNameAnchor
Gauge Theory VizAssigns a U(1) link phase to each edge between neighbouring pixels from the difference of structure-tensor angles, wraps it to (−π,π] with atan(sinΔ,cosΔ), and sums link phases around a closed rectangular plaquette of side 1–5 texels. That sum is the lattice field strength F = ∮A·dℓ.earnedgaugeTheory.frag
Optimal Transport ColorSliced Wasserstein: projects colours onto 4–16 random 1-D directions, matches quantiles against a loaded reference image, accumulates the displacement. Structurally the Bonneel construction its tooltip cites.earnedoptimalTransportColor.frag
Gauss Map VisualisationNormal → point on S² → colour. That is the Gauss map.earnedgaussMap.frag
Hodge DecompositionBuilds a structure-tensor tangent field, takes its divergence and curl, and runs a genuine 8-iteration Jacobi Poisson solve for both the potential φ and the stream function ψ, then forms ∇φ + curlψ + remainder. The decomposition is real. But the solve is on a 5-point stencil with the outer ring pinned to zero — a Dirichlet-zero boundary one texel away — so φ and ψ are near-local and the "harmonic" remainder is nearly the whole input field.structure right, support one pixelhodgeDecomposition.frag:89–115
Walk on Spheres PDE4–32 independent random walks per pixel, 4–16 steps each, averaging the scene colour at the walk endpoint — a real Monte-Carlo Laplace solver in shape. Two deviations from Sawhney & Crane: the step is radius · bDist · (0.3 + 0.7·ξ), a random fraction of the largest empty ball rather than a jump to its boundary, which is the step the mean-value property makes unbiased; and the walk terminates on a fixed step count rather than an ε-shell.right estimator, biasedwalkOnSpheres.frag:84–96
Persistent HomologyMode 0 is genuine discrete Morse theory: steepest 8-connected descent from each pixel to its basin minimum, hashed to a colour — that recovers connected components of a sublevel set. But there is no filtration sweep and no persistence pairing; "birth/death" (mode 1) is per-pixel critical-point classification at one threshold. The shader's own header says "approximating persistence diagrams".homology yes, persistent nopersistentHomology.frag:60–90
Ricci FlowSee below — this one needs a paragraph.half earnedricciFlow.frag
Kuwahara Multi-ScaleIts own first source line reads "Single-scale anisotropic Kuwahara filter". The slider labelled Scales (1–5) sets nSamples = 8 + (uScales−1)×4 — samples per sector, not scales. The tooltip cites the multi-scale paper.mislabelled controlkuwaharaMultiScale.frag:12,55–56
Heat Method GeodesicsTooltip: "Brief heat diffusion, then Poisson solve gives geodesic distances." The shader diffuses heat (as a closed-form Gaussian, correct on a flat unbounded domain), computes the normalised gradient X = −∇u/|∇u| — and then never uses X again. The distance is a 16-tap luminance line integral along the straight segment from source to pixel, scaled by |uv − src|. There is no Poisson solve. The output is a luminance-weighted Euclidean radius.not earnedheatMethod.frag:72–91

Ricci flow, in full, because it is the one people will check first

The tooltip reads: "Flow the metric by −2·Ric(g). (Perelman's tool for Poincaré conjecture.) Curvature-weighted smoothing." Here is what is right and what is not, in order.

Right: the shader's own header states the correct reduction — in two dimensions a conformal metric g = e2uδ turns the flow into an equation on the single scalar u. And the curvature overlay really is the conformal-metric Gauss curvature rather than an edge detector wearing its name. It really does run explicit Euler on a 5-point Laplacian for 4–20 iterations, not a blur pretending to be one.

∂u/∂t = e−2u Δu    ← 2-D Ricci flow
∂u/∂t = Δu         ← what ricciFlow.frag integrates
K = − e−2u Δu   ← Gauss curvature of g = e2uδ
K = − Δu / ( 2 e2u )   ← ricciFlow.frag:96 — half of it
The dropped factor in the flow is the whole nonlinearity. What runs is the linear heat equation on image luminance.

A correction to an earlier version of this section, which printed the shader's curvature line and called it exact. It is not: it is off by a factor of two, and the reason is more interesting than the size. The same identity has two standard forms. Writing the metric as g = e2uδ gives K = −e−2uΔu; writing it as g = ρδ gives K = −(1/2ρ)Δlogρ. They agree, because logρ = 2u and the 2 cancels. ricciFlow.frag takes the 1/(2ρ) prefactor from the second form and the Δu from the first, so the 2 never cancels and the overlay shows K/2. Nothing visible depends on it — the value is immediately normalised by clamp(K·8.0, −1, 1), so the factor is absorbed into a display gain — but this was the one claim the page made in the operator's favour, on a page whose whole thesis is that a name has to be checked against the code. It did not survive its own standard.

Not right, in three parts. First, the missing e−2u: real 2-D Ricci flow is a nonlinear fast-diffusion equation, and the shader integrates its linearisation. Second, u is image luminance, not a metric on any surface — there is no manifold here, and no depth or normal is read, so this operator is running on the picture, not on the geometry the renderer knows. Third, the iteration does not close: the comment concedes that the ring neighbours are "updated with their own simpler one-step estimates", so past the first step the stencil is inconsistent and the scheme is not the explicit Euler iterate it claims to be.

And one line in the shader is worth quoting because it is the exact species of argument this page exists to catch: "Cap iterations to keep worst-case cost reasonable; mathematically Ricci flow converges in 2D regardless of specific step count." That is a true statement about the continuous flow being used to license a truncation of a discrete scheme, which it does not license. The truncation is probably fine — with dt ≤ 0.05 in texel units the explicit scheme is comfortably inside its stability bound — but it is fine for a different reason than the one given.

The fair summary. "Ricci Flow" here is curvature-coloured heat-equation smoothing of luminance with a gradient warp. That is a real and slightly unusual image operator, and the overlay genuinely is the conformal-metric Gauss curvature, up to the stray factor of two above. It is not Perelman's tool, and nothing in this program flows a metric. The name would be earned by a rename — "Conformal Curvature Flow" — and by keeping the e−2u, which is one multiply.

Screenshot — The curvature overlay is the honest part. Preset "Cornell Box (default)", GPU tracer, converged, Clean View. Render Settings ▸ Effects ▸ Advanced VFX ▸ Ricci Flow. Three frames, camera unmoved: Overlay 0.0 (warp only), Overlay 1.0 with Iterations 4, Overlay 1.0 with Iterations 20 — all at Timestep 0.02. What to look for: red where the luminance field is locally concave and blue where convex, and the red/blue structure visibly coarsening between 4 and 20 iterations as the heat flow eats the fine detail. That coarsening IS the flow, and it is the one claim in the operator's name that the picture can support.
Screenshot — Hodge, all four components. Preset "RICH: The Reliquary", GPU tracer, converged. Advanced VFX ▸ Hodge Decomposition, Blend 0.0 (field only, no scene). Four frames from one camera, cycling the Component combo: full / curl-free / divergence-free / harmonic. What to look for: whether curl-free and divergence-free are nearly black while harmonic is nearly identical to "full". If so, that is the one-texel Poisson support showing up in the picture exactly as the source predicts, and it is the cleanest possible confirmation of the caveat in the table above.
Screenshot — Walk-on-spheres, variance visible. Preset "Cornell Box + Volume (static)", GPU tracer, converged, Clean View. Advanced VFX ▸ Walk on Spheres PDE, Mode = harmonic smooth, Radius 0.08. Three frames: Walks 4, Walks 12, Walks 32, all with Steps 8. What to look for: Monte-Carlo noise in the OPERATOR, on top of a converged render — the estimator's own variance, falling as 1/sqrt(walks), painted over an image whose sampling noise is already gone. That double-noise structure is unique to this operator in the whole bank and has never been photographed.
Screenshot — Gauge theory — the one that is exactly what it says. Preset "Prism (Rainbow)" or "Material Showcase", GPU tracer, converged. Advanced VFX ▸ Gauge Theory Viz, Colorize on, Overlay 0.6. Three frames at Loop Size 1, 3, 5 with Strength 1.5. What to look for: vortex structures pinned to image discontinuities, coarsening as the plaquette grows. The Wilson loop around a plaquette containing an edge picks up nonzero holonomy; around a smooth region it should cancel to near zero. A picture where the vortices sit on the silhouettes and nowhere else is a picture of a correct implementation.

The rest of the mathematics bank, in one table

These I read enough of to classify but did not audit line by line. Where I could not tell from the source what an operator computes, the row says so rather than guessing.

OperatorReadingGrade
Morse-Smale TopologyCritical points and separatrices from the Hessian determinant of the luminance height field. Correct construction for a height function; "complex" in the full sense (a cell decomposition) is not built.never viewed
Reeb Graph ContoursLevel sets of luminance plus merge/split detection at critical points. Its own header calls the nodes "critical points, edges = persistence", which is the Reeb graph's structure honestly described.never viewed
Cotangent Laplacian FlowCotangent weights derived from triangles formed by depth samples — so this one does use the geometry, unlike Ricci flow. Strength −2 to +2 gives sharpen or smooth.never viewed
Willmore Energy∫H² over the depth surface, three display modes. Geometry-derived. Tracer-only badge.never viewed
Parallel TransportTransports a tangent vector from image centre to each pixel, accumulating Christoffel contributions from the luminance metric gradient. Its own uniform block admits uPathLength is unused — the path is always centre→pixel.one dead uniform
Spinor FieldHues cycling with a 4π period to expose the SU(2) double cover. A visualisation of a fact, not a computation on the scene — and honest about it.never viewed
Navier-StokesSelf-contained: luminance as density, structure tensor as initial velocity, then advection + viscous diffusion + vorticity confinement for a few internal iterations, then the colour is advected. Its own header says "Navier-Stokes inspired".honest in its own header
Schrödinger EvolutionLuminance as potential V, an analytic Gaussian wave packet evolved by a split-operator-style update, |ψ|² as intensity and argψ as hue.never viewed
Laplacian EigenfunctionsNamed for eigenmodes; the header describes multi-scale disc sampling with bilateral-style spectral smoothing and a "modes" count of 1–8. I could not establish from the source that any eigenproblem is solved. Marked unresolved rather than guessed at.unresolved — needs a closer read
Geodesic DistanceDistinct operator from Heat Method Geodesics, and more honest: its header says "single-pass approximation" outright, with three seed modes and a height-scale slider.never viewed
Hyperbolic Tiling, Mandelbulb Overlay, Domain Colouring, Julia Overlay, Quaternion Colour, Clifford Attractor, Fractal DimensionOverlays of known mathematical objects rather than analyses of the frame. Nothing to overclaim; the names describe the pictures.never viewed
Percolation (pc=0.593 button), Ising (Tc=2.269 button), Markov Chain WalkStatistical-physics automata with one-click critical-parameter buttons. All three carry the "better with tracer" badge, which is hard to justify — they are image-domain and read no G-buffer.badge looks wrong

04 — Contact sheet III

Emulating an output medium is the same problem as emulating a lens, run backwards

The renderer spends enormous effort producing a linear HDR film: unbiased estimators, byte-identity contracts, energy ratios to four digits. The third family exists to throw that away in a specific, historically-motivated manner. A linocut takes a float32 linear radiance field and returns one bit per pixel.

The reason this belongs on a rendering site rather than in a footnote is that it is the same class of modelling as everything else here. A thin-lens camera is a model of an input device. A Risograph is a model of an output device: two ink drums, halftone screens at different angles, misregistration in pixels, grain, overprint. Both are physical-process emulation; only one of them is usually treated as serious.

OperatorThe physical process being modelledControlsReads G-buffer
RisographTwo-drum stencil duplication: two ink colours, each halftoned, printed in separate passes that do not register perfectly2 ink colours · Dot Size 3–12 · Misregister 0–3 px · Grain · Overprintno
Woodblock PrintUkiyo-e multi-plate relief printing: a limited palette carved as separate plates, each with its own registration error, on grained woodPalette 3–6 plates · registration offset · wood grain · carved outlinesno
Linocut / ScratchboardRelief carving: white lines cut into black, thickness carrying tone, cross-hatching in the mid-tones. Binary output.Spacing 3–15 · Width 0.3–2.0 · Contrast 1–3 · Flow 0–1yes — lines follow the 3-D surface flow, with a uHasGBuffer fallback
Sumi-e Ink WashInk on absorbent paper: wet-edge bleeding, anisotropic brush strokes, paper absorption, atmospheric fade with distancedepth-aware fadeyes
Screen ToneBen-Day dots: adhesive manga tone sheets, denser dots for darker regions. Three pattern modes.dots / lines / crosshatchno
Advanced HalftoneProcess halftone screening, multi-shape and multi-colour-mode, with the screen angle steered by surface normalshape · colour modeyes
Advanced DitheringBayer, blue noise, normal-aware and spatially-consistent modes — the ordered and error-diffused ends of the same problem4 modesyes
Pointillist (Seurat)Neo-impressionist optical mixing: a jittered grid of complementary-paired dots that mix in the eye rather than on the canvascolour-theory pairingno
ASCII ArtCharacter-cell reproduction: luminance per cell selects a glyph from a procedural 4×5 bitmask table. The oldest output medium in the list.Cell Size 4–16 pxno
Pixel SortingNot a print process — a codec artefact, deliberately induced. Sorts runs of pixels by brightness within luminance-bounded segments, capped at 64 taps.direction · 3 sort keys · two thresholds · Max Segment 16–512no
WFC MosaicProcedural tiles chosen by luminance and gradient with adjacency constraints. Header says "WFC-inspired" — no constraint propagation happens, which a fragment shader could not do anyway.cell size · tile setno

Four of these read the G-buffer, which is the unusual part. A linocut whose carving direction follows the surface flow of a 3-D normal field rather than an image gradient is not something a photo-editing filter can do; it is a print emulator that knows the geometry it is printing. That is the intersection of family I and family III, and it is where I would start if the goal were one image that justifies the entire bank.

Screenshot — One frame, four presses. Preset "RICH: The Apothecary" (rich materials, strong tonal range). GPU tracer, converged ~300 passes, Clean View. Five frames, camera absolutely unmoved: (a) clean; (b) Risograph — Dot Size 6, Misregister 2.0 px, Grain 0.4, Overprint 0.6; (c) Woodblock — Palette 4; (d) Linocut — Spacing 7, Width 1.0, Contrast 2.0, Flow 1.0; (e) Sumi-e — defaults. Present as a strip. What to look for in (d) specifically: the carved lines should follow the curvature of each object, not run in one screen direction — that is the geometry-aware claim, visible in a single still.
Screenshot — Linocut with and without geometry. Same preset, same camera as above. Two frames: Linocut with Flow 1.0, then Linocut with Flow 0.0. What to look for: whether the line direction collapses to a fixed screen angle when Flow goes to zero. This is the cheapest possible demonstration that the G-buffer is being consulted, and it needs no second renderer.
Screenshot — ASCII at three cell sizes. Preset "Cornell Box (default)", GPU tracer, converged. Advanced VFX ▸ ASCII Art at Cell Size 4, 8, 16. What to look for: the point at which the glyph table stops resolving the box and the image becomes texture. Also worth checking whether the glyph ramp is monotone in luminance — a non-monotone ramp shows as tonal inversion in the gradients on the back wall, and nothing in the repo records whether this table is monotone.
video · Misregistration
Preset "RICH: The Reliquary", GPU tracer, converged, Clean View. Enable Risograph alone, Dot Size 6, Grain 0.3. Hold at Misregister 0.0 for 3 s, then drag the Misregister slider slowly 0.0 → 3.0 over 8 s, hold 3 s at the top. Do not move the camera.
Watch for the moment the two ink drums separate. At 0.0 this is a duotone; by 2 px it is unmistakably a printed object with a registration fault. The whole aesthetic of the operator lives in one slider and it is the clearest single demonstration of the "modelling an output device" thesis on this page.

What this family costs. Every operator here discards the property the rest of the site is about. There is no tonemapping choice to make — the megakernel's Reinhard is hardwired at pathTrace.comp:4064 and the Tonemapping combo above it is inert — so the image the press family receives has already been through a fixed curve and a gamma. Nothing downstream of the bank is linear, nothing is measured, and no claim on any other page of this site survives contact with these operators. That is the correct trade for what they are for, and it is worth saying out loud on the page that shows them.

05 — State

Seventeen of them are not functions of the frame. They are functions of every frame so far

The default assumption about a post-process is that it is a pure function: same input, same output. Seventeen shaders in this territory break that. They sample a previous-frame texture, which makes them simulations running on the display buffer — and it means a still photograph of them is not a description of them.

Sampled, not declared — and this page got that wrong. An earlier version of this table counted the shaders that declare a previous-frame sampler and put three innocent operators in the stateful column. Two of them tell you so in the declaration: reactionFlow.frag:10 reads uniform sampler2D uPrevTex;  // unused but kept for C++ binding compat, and morphogenesis.frag:9 is commented // unused. Neither ever samples it. The third, temporalNoise.frag, declares no previous-frame sampler at all — it varies frame to frame because it is seeded from uTime and uFrame, which is a different thing from memory. The simulation page's §07 states this correctly and always did; this page is now the one that changed.

Stateful shaderWhat accumulates
waveEquation.fragA second-order wave field; needs two previous states to step, so it genuinely propagates
rdStylize.fragReaction-diffusion concentrations, ping-ponged. Turing patterns emerge over frames, not within one. The only genuinely stateful RD operator in the bank — it reads uPrevTex at five sites (:64, :94, :100, :138, :139)
smoothlife.fragA continuous-domain cellular automaton with its own reset button; this is the only real SmoothLife in the program — the two scene presets named SmoothLife are legacy FBM noise
ibfv.fragImage-based flow visualisation: noise advected along the field and blended with the previous frame, which is the whole IBFV method
nbodyAttractor.frag, curlNoise.fragParticle-like positions carried forward through an advection field
anisotropicDiffusion.frag, heatEquation.frag, totalVariation.frag, mumfordShah.frag, poissonGradient.fragIterative solvers that use the frame loop as their iteration loop — the honest way to do what §03 shows Ricci flow and the heat method trying to do inside one invocation
progressiveDraw.fragStrokes deposited over time; the image is a drawing being made
fractalFeedback.frag, phaseAmplify.fragFeedback, and Eulerian phase amplification — the Gabor response of the previous frame is what the current one is measured against
dataMosh.fragA frozen frame and its depth buffer, held until you unfreeze. See below
ballpointB.frag, temporalBlur.fragThe two outside the bank: pass B of NPR mode 7 ping-pongs its own stroke buffer, and the tail's temporal blur is a straight mix(current, prev, blend). Both are in this territory and neither is an Advanced VFX toggle
Looks stateful, is notWhat the source says
reactionFlow.frag:10 — uniform sampler2D uPrevTex;  // unused but kept for C++ binding compat. One occurrence in the file, the declaration. A per-frame filter with a reaction-diffusion look
morphogenesis.frag:9 — uPrevTex, commented // unused. Same: declared for the C++ binding, never sampled
temporalNoise.fragNo previous-frame sampler of any kind. It changes every frame because it hashes uTime and uFrame — unphotographable in a still, but stateless

Note the row of five solvers. Anisotropic diffusion, the heat equation, total variation, Mumford-Shah and Poisson gradient editing solve their PDE the correct way, by iterating across frames. The mathematics family in §03 does not, and compresses the iteration into a single fragment invocation on a local stencil instead. Both approaches are in the same panel. The difference between them is exactly the difference between the earned names and the unearned ones, and it is a difference of engineering, not of ambition.

Data moshing, which is not the WIP menu item

The menu bar carries Tools ▸ Data Moshing (WIP), unwired. The feature is fully implemented one panel over, at Effects ▸ Post Process, and it is the most interesting stateful operator in the program because one of its three modes is not an image effect at all.

ModeWhat it doesGrade
ReprojectFreeze a frame and its depth buffer. As the camera moves, re-project the frozen pixels through the frozen depth into the new view. The moshed image sticks to 3-D geometry. This is a depth-correct temporal artefact, not a 2-D smear.never viewed
Screen-LockThe 2-D version: the frozen frame stays pinned to the screen. The conventional data-mosh look.never viewed
DifferenceCurrent minus frozen. Incidentally a usable debugging tool: freeze, change one setting, see exactly which pixels moved.never viewed
Menu routeTools ▸ Data Moshing (WIP) — unwired menu item advertising a finished feature as unfinishedmislabel
video · Moshing through the depth buffer
Preset "HERO: The Gate (shaft + glass ball)". GPU tracer, converged, Clean View. Effects ▸ Post Process ▸ Data Moshing, Mode = Reproject, Blend 0.9, Block Glitch 0.2. Press freeze. Then orbit the camera slowly through 40 degrees over 10 s. Hold 2 s. Now reset, switch Mode to Screen-Lock and repeat the IDENTICAL orbit. Cut together.
Watch for in Reproject the frozen pixels should slide across the screen at a rate that depends on their depth — near geometry moving fast, the far wall barely at all — and should tear where the frozen depth has no answer for the new view. In Screen-Lock nothing moves at all. The difference between the two clips is a picture of a depth buffer being used, and it is why this operator belongs on a rendering site rather than in a glitch-art gallery.
Screenshot — The frame loop as an iteration loop. Preset "Cornell Box + Glowing Volume". GPU tracer, converged first, then Advanced VFX ▸ Reaction-Diffusion Stylize, defaults. Four frames of the SAME unmoved camera at roughly 1, 30, 120 and 600 frames after enabling. What to look for: Turing structures nucleating out of the render and coarsening. A single still of this operator is meaningless; the four-frame sequence is the operator.
Screenshot — Temporal noise, which cannot be photographed. Preset "Cornell Box (default)". Effects ▸ Post Process ▸ Temporal Noise, any mode, Density 0.5, Speed 1.0. Take two consecutive single frames from an unmoved camera. What to look for: they should differ everywhere. Present them as a two-up with the difference image if the Data Moshing "Difference" mode can be stacked on top — that would make one still that carries the operator's whole behaviour, which is otherwise unphotographable.

06 — The layer above

Twenty-one one-of-N modes, five of which are not post-processes at all

Above the bank sits a single combo box with 22 entries — "Off" plus 21 modes — and it behaves differently from everything below it in two ways. It is exclusive, so exactly one can be active. And five of its modes are not applied to the finished frame; they are compiled into both renderers and applied to radiance before the frame exists.

#ModeWhere it runsGrade
1–5Toon · Outline · Toon+Outline · Pencil Sketch · Oil (simple)Inside both renderers. basic.frag:896–910 per fragment; pathTrace.comp:2293–2350 per sample. The panel's "Affects both renderers" label is literal.seen
6Oil Painting [Advanced]2-pass post with relief lighting and a canvas freezeseen
7Ballpoint Pen3-pass postseen
8Ink Spiral2-pass post, with a Variation switch to a Hilbert curve family whose density is modulated by scene luminance and optionally by estimated depthnever viewed
9–12Halftone · Cross-Hatching · Kuwahara · WatercolorPost. Mode 11 is a second, separate Kuwahara from the bank's "Kuwahara Multi-Scale"; both can be on at onceseen
13SmoothLifeA continuous cellular automaton run as a ping-pong post-process with a reset button. Evolves over frames. The only real SmoothLife in the programnever viewed
14–17Pencil Crystal · Noise Contour · Pixel Art · Stained GlassPostnever viewed (Noise Contour)
18–21Depth Fog · Depth Edges · Tilt-Shift Miniature · Depth Warp/SpongeDepth-driven post, marked [D] in the combonever viewed

The prediction worth photographing

Modes 1–5 in the path tracer are applied to radiance at line 2293, then firefly-clamped at 2372, and only then accumulated into the running mean at 2443. So the toon quantisation happens per sample, before averaging. Averaging N independently quantised samples of a noisy estimate reconstructs a continuous ramp. The prediction from source is therefore:

Toon in the rasteriserhard bands, stable
Toon in the tracer, 1 passhard bands, noisy
Toon in the tracer, 500 passes, noisy scenebands wash out — the quantiser is being averaged away
Toon in the tracer, 500 passes, near-deterministic direct lightingbands should survive
Extra gate in the tracer onlylength(firstNrm) > 0.5 — NPR is skipped where the primary ray missed, so the sky and background are never toon-shaded

Marked unverified, deliberately. Every line of that read-out is derived from the source and none of it has been observed. It is on this page as a prediction with an address, so that the shot below either confirms it or falsifies it. If the bands do survive at 500 passes on a noisy scene, the reading above is wrong and I would want to know why.

Screenshot — Does toon shading survive convergence? Preset "Cornell Box (default)". Effects ▸ NPR ▸ mode 3 (Toon + Outline). Four frames, camera absolutely unmoved: (a) rasteriser, press 1; (b) tracer at 1 pass — press 2 and shoot immediately; (c) tracer at ~50 passes; (d) tracer at 500+ passes. What to look for: whether the four quantisation bands visible in (a) and (b) are still four bands in (d) or have become a smooth ramp. Also check the sky/background: it should be untouched in every tracer frame and toon-shaded in the raster frame, because only the tracer gates NPR on a valid first-hit normal.
Screenshot — The two Kuwaharas, stacked. Preset "Material Showcase", GPU tracer, converged. Three frames from one camera: (a) NPR ▸ Kuwahara only; (b) NPR Off + Advanced VFX ▸ Kuwahara Multi-Scale only, Radius 8, Scales 3, Sharpness 8; (c) BOTH at once. What to look for in (c): whether the second pass has anything left to do. Two Kuwaharas composed should be close to idempotent, and if (c) differs sharply from (b) the two implementations disagree about what a flat region is.
Screenshot — Ink Spiral, both curve families. Preset "HERO: The Gate (shaft + glass ball)", GPU tracer, converged, Clean View. NPR ▸ Ink Spiral. Two frames: Variation = spiral, then Variation = Hilbert with depth modulation on. What to look for: whether the Hilbert curve visibly changes density with depth — that is a continuous space-filling curve carrying a depth signal, and it has never been photographed.

Three things about that Variation switch, because it is the only control in the combo that swaps a shader rather than a branch. The two variations run two different pass-A shaders, inkA.frag and inkA_hilbert.frag, and share pass B; the switch is guarded by m_inkAHilbertShader.isValid() (EngineNPR.cpp:428), so if that shader ever failed to compile the combo would still read "Hilbert Curve" and the spiral would run. The two variations also share one backing variable under two labels: inkSpiralSlope, 5–30, is presented as Spiral Slope in one mode and Curve Spacing in the other (UIManagerRender.cpp:1497, :1501), so a value set under one name follows you into the other.

And the Depth Density toggle reads no depth buffer. Its tooltip says "vary ink density by estimated depth"; the shader's own uniform comment says "vary density by estimated depth (luminance proxy)", and what it computes is local luminance variance, smoothstepped to a 0.4–1.5 density multiplier on the reasoning that detailed regions are near ones (inkA_hilbert.frag:16, 145–155). The shader is honest and the tooltip is the part that rounds up. The curve itself is real — a proper xy→d Hilbert mapping, layered at orders 6, 4 and 3 — and it is not the program's only one: the bank carries a separate Hilbert Line Stylization operator in dispatch group 13, different code, same curve. never viewed, both of them.

07 — The ledger for this territory

What in here is inert, orphaned or misnamed — including three the catalogue did not have

A page that lists 157 capabilities and no defects is a brochure. Here is everything in this territory that does not do what its label says, separated by kind of failure, because the three kinds have very different consequences for a user.

Orphaned files first. I re-derived this list independently of the catalogue by checking every one of the 227 .frag and .comp files in shaders/ for any mention anywhere under src/. Eight have none, and they fill the first five rows of the table below. The sixth row, fullscreen.frag, is a different animal and is here because its consequence is the only one that matters: it is mentioned — loaded, even — and still never runs.

Not a corroboration, and this page said it was. An earlier version of this paragraph claimed the eight matched docs/FEATURE-CATALOGUE.md exactly and called that "two people counting the same thing separately and agreeing." They were not counting the same thing. The catalogue's §5.3 is headed "Seven files" and lists nine filenames in six rows; my scan finds eight files with no mention plus fullscreen.frag, which the catalogue also lists. Eight, seven and nine are three numbers, and agreement between two of them was asserted rather than checked. The catalogue's heading has been corrected to nine; the count that is actually derivable from a rule is the eight-with-no-mention, and that rule is now stated above so the next person can re-run it.

FileWhy it has no callerIs a capability missing?Grade
conformalWarp.fragm_conformalWarpShader loads conformalGlitched.frag instead — the shader named after the function is not the one the function usesNo. Conformal Glitched renders.superseded
deepDreamCNN.comp
deepDreamGrad.comp
Never loaded. generateCNNWeights(int seed) sets a bool and returns.DeepDream [Advanced] still renders — as iterated gradient-ascent ping-pong. The image changes; the CNN does not exist.superseded
styleGram.compNever loaded; m_stAdvGramComputed is written once and never readNo — and this row is the opposite of what it looks like. Style Transfer [Advanced] is a real 2-pass structure-tensor plus statistics transfer against a loaded reference, despite its own header calling itself a "stub — Phase 3".superseded
houghLines.frag
houghCircles.frag
houghCirclesDraw.frag
v1 single-pass fragment implementations, replaced by compute accumulate + NMS + draw-peaksNo. Both Hough operators work, better.superseded
eulerFluid.compThe Euler solver is CPU, in PhysicsWorld.cpp:565–732. (An earlier anchor here said :521–554, inherited from the catalogue; that range is inside the two initialisers, not the stepper.)No.superseded
fullscreen.fragm_fullscreenShader is loaded at Engine.cpp:236 and has exactly two references in the repository — that load and its declaration. It is never .use()d.Yes, one. This file implements Reinhard and ACESFilm. Because it never runs, the Tonemapping combo's three options all render the megakernel's hardwired Reinhard. ACES is compiled into the binary and unreachable.orphan with a consequence

Rule 1 applies to every row above. Not one of them is dead. The question "is the feature reachable by any route" is yes in all six cases; the question "is this file live" is no. That pair is superseded, and each row says where the feature actually lives. The only row with real consequence is the last, and it is a consequence for the tonemapper, not for an effect.

Three findings this page adds

These came out of reading the shaders for §03 and §06 and are not in docs/FEATURE-CATALOGUE.md's mislabel table.

ControlWhat the label promisesWhat it doesAnchor
Kuwahara Multi-Scale ▸ ScalesA multi-scale filter, cited to Kyprianidis & Kang 2011, with a 1–5 scale countThe shader's own first line says "Single-scale anisotropic Kuwahara filter". uScales sets samples per sector: 8 + (uScales−1)×4. There is one scale.kuwaharaMultiScale.frag:2, 12, 55–56
Heat Method GeodesicsTooltip: "Brief heat diffusion, then Poisson solve gives geodesic distances."The normalised heat gradient X is computed and then never referenced again. No Poisson solve exists. The displayed distance is |uv−src| weighted by a 16-tap luminance line integral.heatMethod.frag:72–91
Parallel Transport ▸ Path LengthA slider, 0.05–0.5Its own uniform declaration carries the comment "(unused: path always uses centre→pixel)". An inert control that documents its own inertness in the shader but not in the UI.parallelTransport.frag:14

Inert knobs already on the record, both in this territory: stAdvIterations (Style Transfer [Advanced] ▸ Iterations, 1–8) and ddAdvChannels (DeepDream [Advanced] ▸ Channels, 8 or 16). Each has exactly two references in the repository — its declaration and its own widget. Dragging either changes nothing.

One structural surprise, not a defect. The four conformal-map operators — Conformal Fixed, Glitched, Glass, Correct — are dispatched as an if / else if / else if / else if chain. They are the only mutually exclusive group in an otherwise freely stackable bank, and the UI gives no sign of it: four independent-looking toggles, of which only the highest-priority enabled one runs. Turning on Conformal Glass while Conformal Fixed is already on does nothing and looks broken.

What I verified, and what I did not. Verified by reading source: every count in §01, the eight orphan files, the badge and star distributions, the G-buffer binding condition, the uNormalTex and uHasGBuffer counts, the NPR-inside-the-megakernel placement and its clamp/accumulate order, and every row of the two mislabel tables above. Not verified — nothing on this page has been rendered. No image of any operator in this bank exists in this repository. Every figure is a capture instruction. Where a claim depends on what the picture looks like rather than on what the code does, it is written as a prediction with an address, so it can be falsified by taking the shot.

08 — The two ends nobody described

A Hough transform that had to be taught not to vote for the brightest thing in the room

Two clusters sit at the edges of this territory and no section above has touched either. One is the computer-vision pair, which is the only place in the bank where an operator had to be redesigned because the input was a renderer's output rather than a photograph. The other is the fixed tail that the pipeline diagram in §01 names in eight words and nothing on the page unpacks. Both are tables; three rows in them carry a mechanism or a defect and those are written out.

Computer-vision rowWhat it isGrade
Shared edge pre-filterOne Sobel-family pass feeding both Hough transforms: four denoise modes (off, Gaussian, bilateral, median 3×3) × four edge methods (Sobel, difference-of-Gaussians, Canny-lite with non-maximum suppression, Scharr), a binary threshold, and a contrast boost applied before detection (EngineVFX2.cpp:4205-4231). Bypassable, in which case the raw gradient votesnever viewed
Edge pre-filter debug viewShows the pre-filter's output instead of the Hough output. Not a rendered visualisation — it hands you the intermediate texture itself, whose channels are R = the binary edge, G and B = the gradient direction, A = the soft magnitude (:4237-4240)never viewed
Hough line detection [GPU]Edge pixels vote atomically into a (ρ, θ) accumulator in a compute shader, spread over 3 θ bins × 3 ρ bins with Gaussian-like weights so that a diagonal line is not penalised against an axis-aligned one. 60–360 θ bins, 64–512 ρ bins, a peak cutoff, a bounded mode that draws segments only where real edges are, and a Hough-space viewnever viewed
Hough circle / ellipse [GPU + NMS]The same pre-pass into a three-dimensional (r, cy, cx) accumulator, then a compute-shader non-maximum-suppression pass extracting up to 256 peaks (EngineVFX2.cpp:4393). Radius is in min-dimension fractions, so a circle stays round at any viewport aspect; an aspect parameter with a one-click = 1 reset turns it into an ellipse detector; the Hough-space view is a max-over-radius projection with hue encoding the winning radius binnever viewed

The pre-filter is not boilerplate; it is the concession to running on a path tracer. Its own tooltip says why it exists: both transforms "vote ONLY from clean binary edges… this prevents a few bright HDR regions from dominating the accumulator". A Hough transform written for an 8-bit photograph can vote by gradient magnitude and be fine. Vote by gradient magnitude on a linear HDR film and a specular highlight three stops above everything else wins every bin it touches. So there is a tone-map multiplier ahead of the edge detector and a hard binary threshold after it, and the denoise combo exists because the tracer's own sampling noise is impulse noise — which is why median 3×3 is in a list that would otherwise be Gaussian and bilateral. That is a computer-vision operator adapted to a renderer, and it is the one thing in this pair worth a paragraph.

One defect, in the circle pass. The NMS shader's header says it finds peaks that are a "strict local max in 3x3x3", and the test it runs is if (n > v) isMax = false — which passes when a neighbour equals the centre. Ties are not suppressed. Every cell of a flat plateau in the accumulator is emitted as its own peak, and because the accumulator holds integer vote counts on a downscaled grid (the Accum Downscale slider goes to 16), plateaus are the normal case at low vote counts rather than a pathological one. The visible consequence would be several concentric or near-coincident rings drawn for one circle, and the 256-peak cap being reached by duplicates. I have not run it — this is read from houghCirclesNMS.comp:42-59, where the test itself is line 53, and the shot below is the test.

Screenshot — The Hough pair, and the tie question. Preset "Cornell Box (default)" — a box is the cheapest source of long straight edges — GPU tracer, converged ~200 passes, Clean View (G). Four frames, camera absolutely unmoved. (a) Effects ▸ CV ▸ Shared Edge Pre-filter ON, Denoise = Median 3x3, Edge Method = Canny-lite, Edge Threshold 0.15, Contrast Boost 3.0, and "Show Edge Result [Debug]" ON with Hough Lines enabled — this shows the pre-filter's own texture, so expect thin coloured edges on black, with hue varying by gradient direction. (b) Same, Show Edge Result OFF, Hough Lines ON, Bounded ON, Theta Bins 180, Rho Bins 256, Peak Cutoff 0.5 — expect segments lying exactly along the box's wall joins and the block silhouettes. (c) Same but Bounded OFF — expect the same lines extended across the whole frame. (d) Hough Lines OFF, Hough Circles ON, Aspect = 1, Min Radius 0.05, Max Radius 0.3, Radius Bins 16, Accum Downscale 8, Peak Cutoff 0.4, with a sphere in frame. What to look for in (d) specifically: whether ONE circle produces one ring or a tight family of near-identical rings. A family is the tie behaviour described above, and it would be the first observation of it.

The fixed tail, which the diagram in §01 names and nothing describes

After the 157 comes a short, unreorderable sequence that every frame passes through: applyCVFilter, then depth-of-field, then the in-engine photo tap, then data moshing, then the node projector's reproject mode, then glow, then the final temporal blur, then the selection overlay. Four of its rows have never been photographed and one of them is not a filter at all.

Tail rowWhat it doesGrade
Blur (temporal + spatial)An exponential moving average across frames plus a Gaussian across pixels, on the accumulated image. The panel header was renamed on 2026-09-04 and now carries the sentence "Blurs, not a denoiser. There is no denoiser here." in the UI itself (UIManagerRender.cpp:1419-1428) — a rare case of a program correcting its own overclaim in the widget rather than in a commitseen
Post ▸ Depth-of-Field blurDistinct from the tracer's real thin-lens depth of field, with per-renderer enables and a Decouple from Camera switch giving it its own aperture and focus distanceseen
Post ▸ Glow / BloomThreshold, intensity, radius. The knee is not a control: it is fixed at 0.1 in glow.frag:26, and the shader's comment gives the threshold's intended range as 0.5–2.0 where the slider offers 0–1 — so the bottom of the slider blooms everything above 0.1 luminance at full weightseen
Post ▸ CV / Image FilterAn always-available slot outside the bank: Sobel edge with an overlay-on-image option, unsharp-mask sharpen, or a bilateral filter with spatial sigma, range sigma and a 1–8 kernel radiusnever viewed
— live bilateral-grid previewA diagram, not a preview — see belownever viewed
Post ▸ Final temporal blurA second, independent EMA blending with the previously displayed frame, 0–0.95, with an early-out below 0.001 that also drops the history so the effect does not resume mid-fadenever viewed
Selection overlay / hover maskA screen-space masked overlay for the selected and hovered entity, suppressed entirely by Clean View. It runs after the final temporal blur — see belowseen
Background depth prepassA lightweight raster linear-depth pass kept alive whichever integrator is active, so the depth-driven operators and the photon-primitive overlay always have a fresh buffer even under a CPU integratorseen

Two sliders in this tab are both called Temporal Blur, and they are different things at different points in the pipeline. One is in Blur (temporal + spatial), ranges 0–1, and smooths the accumulator. The other is in Post Process, ranges 0–0.95, and blends the finished display texture with the last one shown (UIManagerRender.cpp:1424 and :1791). Both are on screen at once if both headers are open. The second is the one that ghosts a camera move; the first is the one that makes an animated volume converge faster and quietly biases it.

And the second one is not last. The catalogue calls it "a second, independent EMA at the very end of the chain", and the source disagrees: applySelectionOverlay runs after it. The consequence is small and specific — the selection outline is composited on top of the blur and is therefore the one element in the frame that does not ghost when you move the camera — and it is invisible under Clean View, which is where every shot on this page is taken. The correction is here rather than in the catalogue because the source is what settles it.

The bilateral-grid preview is a diagram of an algorithm, drawn from your slider values

Turn on the bilateral filter and a Show Grid Preview toggle appears. What it draws is not your image. It builds a hard-coded one-dimensional test signal — flat at 0.3, a linear ramp between x = 0.45 and 0.55, flat at 0.8 — and splats it into a grid whose two axes are spatial x and range, with the cell counts derived live from your two sigma sliders: the x cell count from the spatial sigma, the range cell count as 1/(σr + 0.01), both clamped. Occupied cells are tinted by their mean value, the signal curve is drawn over the top, and hovering the canvas draws the filter's support ellipse at the cursor with semi-axes proportional to σs and σr — so you can see, directly, that raising the range sigma is what lets the filter reach across the step (UIManagerRender.cpp:1694-1785).

That is a teaching figure for the bilateral grid, live-parametrised by the controls it explains, sitting inside a filter panel. It is the most unusual widget in this territory and it has never been photographed. Two things about it should be said plainly. Its own footer label reads "Splat → Blur 3D → Slice (bilateral grid)" and the widget performs the first of those three: the code splats and draws, and there is no blur pass over the accumulated cells and no slice back to the signal. The catalogue's row says it animates all three; the source says otherwise, and the source wins. The label is best read as naming the method the drawing belongs to rather than the stages the drawing shows. And the toggle is a function-local static bool, so unlike every other control in the panel it lives outside UIManager and is not saved with the rest of the UI state.

Screenshot — The widget that explains itself. No scene required — this is a panel crop, and it is the cheapest capture on the page. Render Settings ▸ Effects ▸ Post Process ▸ CV / Image Filter ON, Filter = Bilateral Filter, then Show Grid Preview ON. Three frames, cropped identically to the preview canvas plus the three sliders above it, with the mouse held INSIDE the canvas so the support ellipse draws. (a) Spatial Sigma 2.0, Range Sigma 0.40. (b) Spatial Sigma 2.0, Range Sigma 0.05. (c) Spatial Sigma 9.0, Range Sigma 0.05. What to look for, with the arithmetic written out so the frames can be checked rather than admired: the range axis is subdivided as 1/(sigma_r + 0.01) clamped to [3, 12], so (a) must show exactly 3 rows and (b) exactly 12, and the ellipse's vertical semi-axis should shrink roughly eightfold between them until it can no longer span the step in the yellow signal curve — which is the picture of an edge-preserving filter refusing to cross an edge. Between (b) and (c) the row count must not change at all while the grid LOSES columns and the ellipse widens, because the column count is derived from the spatial sigma alone. The caption must say the grid is drawn from a synthetic 1-D signal and never from the rendered frame.

09 — The deliverable

The capture protocol, and the full twenty-two-group accounting

Contact sheets need a shot list, and this page's real output is the twenty capture instructions above — seventeen stills and three clips — plus the protocol that makes them comparable. Every shot on this page obeys the same five rules, and the two added in §08 obey a sixth: where a shot can be checked against arithmetic rather than against taste, the expected numbers are written into the recipe.

And the accounting, so nothing in the bank is hidden by the fact that three curated sheets cannot hold 157 things. These are the source's own dispatch groups — twenty-two of them, each one a comment line in EngineRender.cpp — in the order the chain applies them. The n column sums to 157, which is the check the read-out in §01 failed and this table passed.

#Dispatch groupnSheetGrade
01Basic — style transfer, deep dream, neural edge glow3—never viewed
02Spatial / structural (applied first) — SSAO, depth contours, bilateral grid, feature volume4Inever viewed
03NPR / artistic — normal hatching, voxel grid, texel splatting, advanced dither, advanced halftone5I + IIInever viewed
04G-buffer 3-D-aware11Inever viewed
05Differential geometry + print media8II + IIInever viewed
06Histogram / painterly / progressive3—never viewed
07Anime / Live2D / manga8III (screen tone)never viewed
08Volumetric ray-marching — god rays, fog, aura, atmosphere, SSR, light cone6Inever viewed
09Topology / flow — Morse-Smale, level sets, gradient flow3IInever viewed
10Advanced techniques — walk-on-spheres, anisotropic diffusion, TV, Fourier spectral, heat equation, morphological, geodesic distance, power Voronoi8IInever viewed
11Mathematical / artistic — Laplacian eigenfunctions, OT colour, Poisson gradient, Mumford-Shah, XDoG, stroke-based, phase amplification, discrete curvature8IInever viewed
12Research-level — multi-scale Kuwahara, coherent line drawing, four conformal maps, contour families, transport and OT warps, and eighteen more29mixednever viewed
13Siggraph-inspired — guided filter, palette recolour, Voronoi stipple, ASCII, 2-D Gaussian splatting, RD stylize, layered abstraction, Hilbert stylization8III (ASCII)never viewed
14Glitch / generative — pixel sort, WFC mosaic, temporal noise3IIInever viewed
15Neural, applied last — style transfer [advanced], deep dream [advanced]2—2 inert knobs
16Pure mathematics — domain colouring, Julia, fractal dimension, quaternion colour, Clifford attractor5IInever viewed
17Field-based / animated — IBFV, curl noise, reaction flow, n-body, wave equation, morphogenesis, magnetic field, plasma, Schrödinger, Navier-Stokes, EM scattering, gradient-descent flow, Hodge, Helmholtz, heat method, Laplace eigenfunctions, Ricci flow, parallel transport, spinor, gauge theory20IInever viewed
18Computer vision — Hough lines, Hough circles, epipolar, stereo, geometry reconstruction5I + §08never viewed
19Depth extraction / holography7see the imaging pagemeasured
20Statistical physics — percolation, Ising, Markov chain3IInever viewed
21Research-grade math — hyperbolic tiling, persistent homology, mandelbulb, Reeb contours, kaleidoscope5IInever viewed
22Tail — chromatic hallucination, fractal feedback, depth abstraction3Inever viewed

One group in that table carries a grade the others do not. Group 19, depth extraction and holography, is measured — three time-of-flight systems and three holographic reconstructions with error figures scored against ground-truth depth. It sits in the same panel as ASCII art, and it is the only part of this territory with a measurement behind it — scored, though still never photographed. It has its own page, and it is the proof that this bank carries real work: the same dispatch chain that runs a halftone runs a self-scoring phase unwrapper.

Read next. The single ledger row this page expands is on the landing page; the full catalogue behind it is docs/FEATURE-CATALOGUE.md, where this territory is Group 5. The one part of it that has been measured is seeing light arrive. The audit method that separates "the feature is reachable" from "this file is live" — the rule that keeps every row in §07 out of the word dead — is docs/FEATURE-REACHABILITY.md.