Spectral glass
WebGPU · spectral dispersion · live HTML background
What this is
Realtime physically accurate spectral dispersion through Apple “Liquid Glass”-style
pills, prisms, rotating cubes, wavy plates, and a single brilliant-cut diamond. This samples the
full visible spectrum per wavelength and reconstructs colour with
CIE 1931 colour-matching — not the usual “shift R/G/B IORs” trick.
Live: saqoosha.github.io/Spectral-Glass/ · WebGPU in Chrome/Edge/Safari · HTML text background in Chrome with CanvasDrawElement
Why not just shift R/G/B?
A 3-sample RGB IOR looks like a three-band rainbow. Real glass is a continuous spectrum.
The default scene uses N = 16; hold Z to force N = 3
and compare fake RGB bands against the spectral reconstruction.
Controls (from README)
- Drag a shape — move it (cube / diamond: circular hit radius)
- Z (hold) — force N = 3 + pin temporal jitter off so the 3-band RGB structure is visible
- Space — random positions (four non-diamonds, one diamond)
- R — new Picsum (when HDR env is off, same as Random photo)
- T / S / B / F — diamond view presets (diamond only)
- Tweakpane — IOR, Abbe, samples, shape, background, materials, env, AA…
Presets & materials
Presets — Subtle pill, Prism rainbow, Rotating cube, Wavy plate, Diamond.
Diamond intentionally shows one stone; the other presets keep four objects.
Materials — Real catalog from water / BK7 / flints / diamond / moissanite to fantasy
glasses (high nd, low Vd) for extreme fire.
Environment & reflections
Optional HDR env (Poly Haven CC0): background, refracted rays, and reflections sample the
same linear panorama — exposure + yaw without re-downloading. Legacy path: Picsum background with
UV-offset reflection hack when env is off. The Diamond preset loads Brown Studio 2 at 2K.
Antialiasing
- None — no spatial or temporal AA (history EMA still runs)
- FXAA — single-frame 9-tap; no ghosting, slightly soft
- TAA — sub-pixel jitter + motion-vector history reprojection; converges when paused
Post pass applies sRGB OETF once (FXAA luma in perceptual space, blend in linear).
?perf logs GPU ms when timestamp queries exist.
Diamond & TIR
Brilliant cut uses a TIR bounce chain (1–32 bounces, default 6): internal reflects until
an analytic exit refracts out — that’s the sparkle. Pink debug = bounce budget exhausted; approx mode
skips the chain for stability.
Quick start
bun install
bun run dev # http://localhost:5173
bun run test
bun run build
Requires a WebGPU-capable browser. HTML background: Chrome + CanvasDrawElement flag; unsupported browsers automatically choose Picsum only. This strip is on the left (wide, over the photo) so the Tweakpane inspector can stay on the right — drag and orbit glass in the open area in the middle and to the right.
Pipeline in short
- Two passes: cheap fullscreen bg + history, then instanced 3D proxy; heavy refraction only inside the silhouette.
- Five SDF families: pill, prism, rounded cube, wavy plate, 58-facet Tolkowsky diamond with analytic exits + TIR chain.
- Cauchy + Abbe (glTF dispersion), Wyman CIE XYZ, per-λ Fresnel, per-λ sRGB weighting, fixed-or-jittered spectral sampling, TAA/FXAA, HDR env optional.
- Photo mipmaps, temporal history (EMA + paused progressive mean), reprojection for rotating shapes.
localStorage persists validated params and pill layout; pagehide flushes
the debounced saver. Math in src/math/ is mirrored in WGSL; Vitest is the ground truth.
GPU time (README sample)
On Apple Silicon ~1290×1070, four instances, p50: pill N=8 ≈1.7 ms, N=32 ≈6.4 ms;
cube N=8 ≈1.05 ms, N=64 ≈3.2 ms — inside a 16.67 ms frame. Background
pixels are cheap; the per-λ loop dominates in the proxy silhouette.
References
glTF KHR_materials_dispersion (Cauchy+Abbe) · Wyman–Sloan–Shirley CIE XYZ (JCGT 2013) ·
Wilkie et al. hero wavelength (EGSR 2014) · Peters Spectral rendering notes ·
blog.maximeheckel.com refraction & dispersion
This panel
The Picsum underlay is the same image the GPU fetches. This wide, scrollable
left strip (with a long fade) keeps Tweakpane’s space on the right. For
HTML + CanvasDrawElement, the DOM is rasterised into a texture each paint — you’re
reading that composite here. If the copy API is missing or fails, the app switches back to
Picsum only. Scroll with the wheel; click the open canvas to drag glass again.