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.

This demo needs a WebGPU-capable browser.
Try Chrome/Edge 120+ on macOS/Windows/Linux, or Safari 18+ on macOS/iOS.