RGB is a useful answer to the question “what color should this pixel be?” It is a poor description of what happens before the light reaches the camera. A prism bends violet more than red; a thin film reinforces some wavelengths and cancels others; the tracks on a disc send different wavelengths in different directions. If those effects are the point of a demo, the renderer needs to carry wavelength through the calculation and make RGB only at the end.

The physics we need

Visible light is a distribution of radiance over wavelength, L(λ). A camera pixel is an integral of that distribution weighted by its color response. We approximate the integral with eight 40 nm bands centered from 400 to 680 nm. This is deliberately small enough to run in a real-time material pass, while giving dispersion, diffraction and interference a common quantity to operate on.

From a spectrum to a display pixel
X = Σᵢ L(λᵢ) x̄ᵢ     Y = Σᵢ L(λᵢ) ȳᵢ     Z = Σᵢ L(λᵢ) z̄ᵢ
RGBlinear = MXYZ→sRGB · (X, Y, Z)

The weights are CIE 1931 color-matching functions integrated over each band. The matrix converts XYZ to linear sRGB; exposure and tone mapping come later. When an existing RGB light or environment sample enters the spectral shader, a smooth nonnegative basis expands it into eight values. That reconstruction is an approximation—many spectra produce the same RGB color—but it keeps white light neutral until a material separates its wavelengths.

For a dielectric surface, the refractive index depends on wavelength. We use Cauchy's relation n(λ) = A + B / λ², with λ in micrometres. Snell's law, n₁ sin θ₁ = n₂ sin θ₂, then gives a different transmitted direction per band. Fresnel reflectance decides how much radiance reflects and how much enters. At a sufficiently steep exit angle, total internal reflection keeps the ray in the solid. These are the operations behind both the prism's broad fan and the diamond's tighter flashes.

Two other exhibits need phase and geometry, not a different set of painted colors. For a soap film of thickness t, the round-trip phase is δ = 4πnt cos θₜ / λ. Reflections from the two surfaces interfere; the shader uses the multiple-reflection Airy expression for each polarization and averages them. A finite wavelength band damps very rapid oscillations, so a thick film tends toward an incoherent response. On the disc, grooves with pitch p send order m toward directions satisfying the grating condition dot(L + V, g) = mλ/p, with the along-groove component conserved. The colored arcs therefore move when the viewing angle or track pitch changes.

One compact spectrum in the shaders

The shared Spectral.h code stores eight samples as two four-component vectors. It contains the RGB-to-spectrum basis, the CIE integration, Cauchy index, exact unpolarized dielectric Fresnel, Snell refraction, the thin-film response and the grating orders. Keeping these calculations together matters: a red ray leaving the prism and a red contribution shading its face must agree on the wavelength and refractive index.

SpectralForward.fx shades the selected object. For glass, each band enters the convex hull, travels to the next face, and can reflect internally before it exits. The resulting direction samples the night sky, ground or studio environment. The bubble combines transmission with wavelength-dependent film reflection. The disc combines a reflective substrate with its grating response. The objects use different optical models, but every model writes the same eight-band radiance representation.

Why the renderer needs two passes

An RGB render target cannot hold the intermediate spectrum. The forward pass writes four bands to one RGBA16F target and four to another, alongside depth. Opaque and transparent spectral batches run through their own custom passes so the bubble can composite over the scene correctly. After each batch, the targets become shader inputs to a full-screen resolve.

SpectralResolve.fx reads the eight samples, integrates them to XYZ, converts to linear RGB and writes into the engine's HDR scene color. It also draws the prism experiment: a finite white source ray enters the mesh, each band follows its refracted path inside, exits at its own surface point and intersects the ground. The visible air beam and floor projection are evaluated from those same paths. The resolve compares beam depth with prism depth, which lets the split remain visible through the glass without a hard cut where a screen-space line reaches a facet. The engine's post process then applies exposure and tone mapping before presentation.

Frame structure
physical sky and ordinary scene → HDR scene color
spectral forward              → 8 bands in 2 × RGBA16F + depth
spectral resolve              → XYZ → linear RGB → HDR scene color
post process                  → exposure / tone map → display

The sky is part of the optical test, not simply a backdrop. A black world gives glass nothing to refract and a bubble nothing to reflect. The scene uses SETech's physical night sky with stars, and the material rays sample that environment. The prism beam's small amount of visible air scatter is a presentation choice; the wavelengths and landing positions still come from the traced path. The ground replaces the earlier decorative caustic pattern with a surface that actually receives those calculated paths.

Five ways to test the same representation

The prism exposes dispersion directly: move its beam or change incidence and the internal split, exit fan and ground landing follow. The diamond tests facets and internal reflection, including a closed crown. The glass cube gives a quieter comparison with a different index curve. The DVD tests reflective diffraction; its shallow hub, fine rings and edge profile keep it recognizable as a disc before the rainbow appears. The soap bubble tests interference as film thickness and view angle change. Orbiting the camera is useful on every object, because an optical effect that only survives one angle is usually hiding a shortcut.

The browser version is the same C++ demo compiled to WebAssembly and rendered through SETech's WebGPU backend. Its shaders are cooked into the asset package before deployment, so the page opens an executable demo rather than recompiling this material system in the browser. The controls call into the running application and alter the actual beam, grating, film and camera parameters.

Where the approximation ends

Eight bands will not resolve a narrow laser line or a coating whose response changes sharply inside one band. RGB-to-spectrum reconstruction cannot recover a source spectrum that was never measured. The beam is made visible with a controlled air-scatter approximation, and the showcase traces selected paths rather than solving full spectral global illumination. Those limits are useful to state because they show what each image can prove: the five materials share wavelength-aware transport and a common color conversion, while keeping the cost suitable for an interactive WebGPU exhibit.

The receipt

Open the Spectral Light Lab. Move the prism beam and follow one color from entry, through the solid, out to the floor. Then turn the disc and change its track pitch, or change the bubble's film thickness. The resulting colors respond to a physical parameter in the shader rather than to a swapped rainbow texture. That is the test this demo was built to make visible.