Analytical Render · LTC · technical notes

Method and implementation

The fitted LTC garage, its matched direct-light comparison, and the scope of the numerical optical extensions.

Direct surface lighting with polygon integrals and fitted GGX linearly transformed cosines.

An area light illuminates a point from a continuum of directions. Sampling those directions is general, but it also introduces variance. This project investigates where the angular integral can instead be evaluated deterministically—and what must be approximated to extend that advantage from diffuse surfaces to materials and visibility.

The main WebGPU garage brings together published GGX LTC tables, procedural materials, a bounded luminous volume and selected optical extensions. The diffuse study exposes the exact projected-solid-angle identity that motivates the approach. Older material, geometric and numerical motion studies remain available as context, with their different models identified explicitly.

The contribution is an executable integration and inspection of these methods, not a new LTC derivation or a general closed-form rendering solution. The fitted LTC method is credited to Heitz, Dupuy, Hill and Neubelt, SIGGRAPH 2016.

Study structure

The garage opens in split comparison. Family tabs narrow the scene selector, retaining all seventeen original scene identifiers and URL entry points. The larger image is first, with a compact glass control instrument beside it and live mathematical inspection below. On shorter desktop windows the top spacing contracts to keep the image visible.

What the comparison establishes

In the WebGPU garage, Integral / Monte Carlo / Compare evaluate the same opaque direct-light model with two methods: fitted LTC versus uniform-emitter Monte Carlo. The comparison covers diffuse lighting, the isotropic GGX base, the blended metallic base, and an independent dielectric coat lobe. Glass and thin-film extensions, the environment and optional ambient fill remain shared. This is a more useful check of the fit than the earlier diffuse-only comparison, but it is not a full path-traced reference. With low roughness, uniform-emitter sampling can miss narrow highlights at low sample counts.

The hero scene uses unoccluded emitters in both estimators. The former planar blocker is archived in swap_out/garage-planar-blocker.json for a separate visibility study; its restricted diffuse clipping code remains dormant. Normal, roughness and blend-mask diagnostics display shared inputs rather than two lighting estimates. The earlier scene kernels still compare diffuse light while sharing their other material terms. For an isolated diffuse comparison, use Open diffuse comparison, which selects studio, freezes motion, disables fill and enables clipping.

Only the WebGPU garage executes the fitted LTC and specialized glass/volume kernel. CPU/WASM provide diffuse and selected mirror/film reference paths; they are not feature-equivalent garage implementations. The panel identifies this scope when a backend changes. Switching to JS/WASM opens a compatible diffuse study with a smaller image budget; returning to GPU restores the saved scene and settings. The modeless Learn more panel stays open until explicitly closed. Backend support and measured cost distinguish absent features from execution speed using actual equal-workload CPU frames.

One coordinated material-and-volume treatment

Extra fancy, enabled by default in the GPU garage, adds a clearly bounded organic volume cube, a fine metal edge cage, raised-edge hollow-ring shading normals and a flowing floor inlay. One switch restores the simpler material comparison. The organic network is procedural: it is not a Physarum, reaction-diffusion or SmoothLife simulation. Relief changes shading normals; it does not claim new displaced geometry or silhouette detail.

The cube has actual density, Beer extinction, numerical single scattering and teal/gold emission. Both image halves share that volume integration. LTC still evaluates the opaque direct surface lighting. The extra treatment notes explain this separation and why uniform light animation is compatible with LTC.

Expand Atmosphere & motion to pause the field, isolate its scattering and emission, or turn off the bronze light frame. With Extra fancy off, the original room-haze settings become editable: uniform, rolling-noise and filament density. Their values are preserved while the coordinated preset is active. The medium model records the numerical approximations, including omitted volume shadows from objects and secondary glass paths.

Demo Controls

Live LTC inspection

The lower instrument is a geometric floor probe, not a screen-picked shaded pixel. It uses point (0,0.002,0), upward geometric normal, the scene camera and first emitter. The garage floor slider supplies roughness; other scenes use their floor material.

The instrument decodes the same published 64² RGBA16F lookup data as the shader and uses the same bilinear coordinates: roughness horizontally and sqrt(1 − n·v) vertically. It displays the actual matrix, amplitude A/B, original spherical polygon, transformed polygon, and approximate edge/horizon factor. The F₀=.04 response is one unoccluded isotropic GGX lobe before texture normals, material blending, coat, visibility, radiance multiplication and tone mapping.

Geometry refreshes at about 30 Hz during animation; numerical labels refresh at about 10 Hz. Paused edits always refresh immediately, including the last light/camera event. This JavaScript instrument illustrates the live computation without synchronous GPU readback. The three-step explanation and expandable diffuse identity connect these intermediates to the lighting model.

Current Kernel

For each visible point, the renderer computes:

E = Le / 2 * sum_i atan2(|u_i x u_j|, u_i . u_j)
              * n . normalize(u_i x u_j)

where u_i and u_j are normalized directions from the shaded point to adjacent vertices of an oriented rectangular light. The spherical polygon is clipped to the receiver's upper hemisphere before evaluation.

This is analytical direct lighting, but not a symbolic image equation. The finite sum_i is the exact boundary evaluation for the polygon light, not a Monte Carlo sample loop. A renderer still has to evaluate rays/pixels unless the camera, geometry, visibility, tone mapping, and display reconstruction are also reduced to a closed-form image-space formula.

Arithmetic throughput probe

The collapsed panel runs a synthetic arithmetic probe, separate from the actual polygon integral and from renderer speedup evidence. It runs only when requested with Run Kernel, so scene and backend changes do not trigger extra benchmark work. Only the selected probe runs:

Its analytic/MC labels are historical kernel variants; the sampled probe is not an unbiased reference for the actual lighting kernel. CPU/WASM arithmetic timing and GPU submission/readback/reduction have different measurement boundaries. Neither these numbers nor the renderer's CPU submit times establish GPU elapsed time. The visible GPU backend is a full WGSL producer: it ray/intersects the small demo scene, evaluates the analytical quad-light formula or MC comparison, writes an rgba8unorm storage texture, and then the shared fullscreen WebGPU pass presents that same texture. The WASM backend now renders the same small scene into WASM linear memory with optimized C, then uploads that buffer into the shared WebGPU presentation texture. The JS backend keeps the scalar CPU reference path. The readout separates producer cost from present submit cost.

WebGPU And WASM

Browser WebGPU resources are owned by the browser API. WASM cannot directly grab the swapchain framebuffer pointer. The page still uses an HTML <canvas> element because that is the browser surface WebGPU presents into. When WebGPU is unavailable, a Canvas2D fallback presents the JS frame instead. The normal GPU presentation paths are:

To rebuild the visible WASM renderer:

emcc wasm\analytic_renderer.c -O3 -ffast-math -msimd128 --no-entry -s STANDALONE_WASM=1 -s INITIAL_MEMORY=8388608 -s ALLOW_MEMORY_GROWTH=0 -s ERROR_ON_UNDEFINED_SYMBOLS=0 -o wasm\analytic_renderer.wasm

Material Scope

Supported now:

Good next analytical targets:

Boundaries

The core comparison is direct lighting only. The optional bounce fill is a deterministic visual aid, not a closed-form global illumination solve. One parallel planar rectangular blocker has a retained, currently dormant analytical visibility path in the garage kernel; arbitrary blockers, interreflection, participating media, and caustics are not solved in closed form. Fitted LTC itself is a deterministic approximation to GGX, and the torus still uses numerical SDF marching. Those are where a practical analytical renderer becomes a hybrid: closed forms and differentiable equations where they fit, numerical root solves or quadrature where they are stable, and Monte Carlo only where the scene stops being tame.

Focused checks

Run node --test tests/*.test.cjs, node --check app.js and node --check ltc-inspector.js. The tests cover rotated-emitter Monte Carlo sidedness, all seventeen scene routes, control identity, viewport aspect and allocation bounds, and labels following the actual producer during a GPU fallback. Shader execution and appearance additionally need a browser with native WebGPU; these checks do not substitute for that review.

The inspection tests additionally cover half-float decoding, texel-center bilinear lookup, the WGSL column-major matrix convention, finite published-table outputs, control sensitivity, rejected emitter backsides, final paused edits and actual material-parameter packing. The garage work estimate counts light samples or edge sets per lobe, allowing for split allocation and lens rays, before surface misses and material-dependent lobe counts; it is not a measured GPU instruction count.

Third-Party Data

ltc-data.js contains half-float encodings of the GGX LTC lookup tables published in the three.js RectAreaLightTexturesLib, derived from Eric Heitz's reference ltc_code. Both sources are MIT licensed; see THIRD_PARTY_NOTICES.md.