The Bloom
The story behind The Bloom
Brief minimal-maximal × tension × audio-visual-synesthetic × vapochromism
Inspired by Vapochromism on Wikipedia
Built with WebGPU (compute + render) · 3D storage textures · Volumetric raymarching through an octahedral SDF · Web Audio API additive bell synthesis + ConvolverNode plate reverb · Fraunces italic display · Cormorant SC small caps
Techniques 96³ RGBA16F 3D storage texture ping-pong · 6-neighbour Laplacian diffusion in a compute shader · Gaussian cursor-as-vapor source injection · Fullscreen-triangle raymarch with 48-step SDF sphere-trace entry and 56-step volumetric integration · Thresholded smoothstep phase transitions (white → amber → rose → crimson) · Blue-noise-style jitter to kill banding · Climax detection drives an 8-second ConvolverNode plate-reverb opening
Direction A James Turrell volume crossed with a Hermès silk plate and a Mann-lab vapochromic crystal macrograph. Fraunces italic for the climactic word, Cormorant SC for the threshold labels. Bone-white chamber; warm palette on cool-pristine.
Result You bring your cursor near an octahedral crystal. Invisible vapor diffuses through its 3D lattice. The crystal flips between discrete colour states — white to amber to rose to crimson — at sharp thresholds you can hear as bell-tones. When you’ve driven the whole lattice past saturation, the full CHROMA blooms over the crystal in deep charcoal Fraunces italic; the chamber opens briefly with plate reverb; the crystal vents back to pristine.
The Story
Vapochromism is one of those laws of matter you don’t want to believe. Most chemistry happens in solution — you pour one liquid into another and a colour changes. Vapochromic compounds change colour from a solid state when you expose them to a vapour. A yellow crystal sits on the bench. You wave a beaker of methanol past it. It flips to deep red. Not gradually, not by absorption of a liquid — it changes colour because methanol molecules have slipped into the lattice and altered the distance between metal centres in a platinum coordination complex, shifting the absorption spectrum by tens of nanometres. The effect is fast. It’s reversible — air the crystal out and it flips back. Different volatiles produce different colours from the same substrate: methanol red, ethanol rose, acetone citrine. The crystal is a sensor of what it cannot see.
This experience is a vapochromic crystal you play with. Not a specific Mann-lab platinum-complex — an abstracted one, rendered as a single octahedron floating in bone-white space. Your cursor is the vapour. Hold it near and concentration builds in the lattice. At each threshold, the crystal flips — not fades — from one colour state to the next. Amber, rose, crimson. Each flip has a bell-tone. Drive the whole lattice past full saturation and the crystal enters CHROMA bloom.
The brief was minimal-maximal × tension × audio-visual-synesthetic × vapochromism. Minimal-maximal meant one object, vast white space, no decoration. Tension meant threshold anxiety — the crystal is about to flip, is flipping, has flipped. Audio-visual-synesthetic meant the sound and image co-author the event — neither speaks alone. A silent crystal is a demo; a chiming crystal at every phase crossing is the experience.
The Take
The interaction is the chemistry. There’s nothing to click, drag, or scroll — the cursor is the vapour source. You hold it near the crystal and concentration accrues; you pull it away and concentration vents. Phase transitions are the discovery — you didn’t know the crystal would flip until it did. Then you realise it does this three times, each with its own chime and its own colour; and if you keep bringing vapour close for long enough, the crystal blooms into a final, reverent, audible state where every voxel is past threshold and the word CHROMA surfaces in Fraunces italic before everything vents back to pristine white.
Typography does real work here. The threshold labels (AMBER / ROSE / CRIMSON) type in briefly at the top of the crystal only at the moment a phase flips, then fade with the voxel’s own decay. The climactic CHROMA blooms in deep charcoal Fraunces italic at the moment full saturation is reached, persists for 4 seconds, and fades with the venting. The type is time-locked to the physics. It is not UI; it is the chemistry’s voice.
Silence would have defeated this piece. The audio decision was full-score — a 55 Hz sine drone with a 110.3 Hz detuned octave under everything, low-passed at 180 Hz for chamber presence; a per-threshold bell chime at pitches encoding the destination colour (E₄ → B₄ → E₅ ascending for up-crossings, same chord descending for venting); a four-note climax chord at full bloom, with a ConvolverNode plate reverb that opens to 0.28 wet for six seconds — the chamber audibly grows — and closes again. Each phase flip has a sound because each flip is a discovery. Silent flips would be unlandable.
The Tech
WebGPU, not WebGL
This is the first Artificial Noodles piece to run in pure WebGPU. The concept is native: 3D diffusion demands a compute shader over a 3D storage texture, which is a WebGPU-specific capability — in WebGL 2 you would ping-pong 2D textures and either slice the 3D volume manually or skip it. The render pass is a straightforward fullscreen-triangle fragment shader that raymarches through the crystal, samples the 3D texture, and colour-grades per voxel.
Detection is explicit — on load, the script checks navigator.gpu, falls through to a tasteful Fraunces-italic fallback card (“Vapor — This experience uses WebGPU… Best viewed in Chrome 113+, Edge, or Safari 18+”) if the browser doesn’t expose the API. The canvas is set to position: fixed; top: 0; left: 0; z-index: 1 immediately after creation — a lesson learned the hard way from an earlier WebGPU attempt where the canvas flowed off-screen behind the site layout.
96³ voxel ping-pong
Two rgba16float 3D textures (vaporA and vaporB) hold concentration in the red channel; the other channels are unused. A ping-pong index swaps which texture is read-from and written-to each frame. rgba16float was chosen because it’s both storage-writable and filterable by default in WebGPU’s core spec — so the compute shader can textureStore into it and the render shader can linearly-interpolate-sample it without opting into an extension.
Compute shader: diffusion + source + decay + SDF mask
Every frame, the compute pipeline dispatches ceil(96/4)³ workgroups of 4×4×4 threads — one voxel per thread. Each thread:
- Reads its voxel and the six axial neighbours (zero-flux boundary via
clampto grid edges). - Adds the 6-neighbour Laplacian weighted by the diffusion rate (per-second, scaled by real
dt):next = cur + D * ((sum_neighbours - 6*cur)) * dt. - Applies linear decay:
next -= decay * next * dt. - Computes the octahedral SDF in normalised voxel coordinates (
|x| + |y| + |z| - 1) and zeros voxels outside the crystal body — the crystal is truly shaped, not a box. - If the cursor is active, adds a Gaussian source centred at the cursor’s voxel-space coordinate with
σ = 20voxels. - Clamps to
[0, 1.05]andtextureStores to the output.
The sigma is deliberately wide: the vapour doesn’t localise at a pixel, it infuses a region. Diffusion then smooths the Gaussian over time, so the concentration field stays a soft 3D cloud that approximates vapour uptake.
Render shader: volumetric raymarch
A single fullscreen triangle (vertex_index 0..3) drives a fragment shader that:
- Constructs a camera ray per pixel from a hard-coded position
(0, 0.2, 3.0)looking at origin, basis computed each frame and passed as a uniform. - Ray-AABB early cull against the
[-1, 1]³bounding box. Bone-white background if the ray misses, with a subtle radial vignette and a soft floor shadow that only paints on non-crystal pixels. - SDF sphere-trace up to 48 steps to find the entry point on the octahedron — faster than stepping fixed distances and produces a pixel-perfect silhouette.
- Walks to the exit with small fixed steps from the entry.
- Volumetric integration of 56 evenly-spaced samples between entry and exit, with a blue-noise-style hash jitter to kill banding. At each sample:
textureSampleLevelthe vapour field, computephaseColor(v)via three smoothsteps (white → amber → rose → crimson at 0.20, 0.40, 0.60 with ~0.06 transition width — sharp, so the phase change reads as a flip rather than a fade), compute density and per-segment alpha, and front-to-back composite. - Rim light from an edge-Fresnel approximation at the entry point, added on top of the volumetric accumulation.
- Climax sheen — during the bloom, a spectral shimmer modulates the final colour with
sin(time + uv)so the chamber visibly sings.
Phase detection on the CPU
Because GPU readback would be expensive, phase detection runs on the CPU as a faithful proxy ODE: dV/dt = S - D·V with S = 0.20/sec when the cursor is inside the crystal’s 2D footprint and D = 0.10/sec rising or D = 0.40/sec venting. Thresholds at 0.25, 0.45, 0.65, 0.82 drive label + chime + climax-title triggers. The GPU peak voxel and the CPU proxy diverge slightly in the first second (diffusion drains the GPU peak faster than the CPU proxy thinks) but re-converge after about four seconds. In practice, labels fire almost exactly when the visible colour flips — within the transition window.
Audio: additive bell, drone, plate reverb
Each phase chime is a three-partial additive bell (fundamental + ×2 + ×3, amplitudes 1.0 / 0.3 / 0.14, 3ms attack / 1.8s exponential decay). The climax chord stacks four bells (220 + 330 + 495 + 660 Hz, with a 6× harmonic reinforcement) and opens a ConvolverNode plate reverb — a noise-seeded impulse response of 300ms with a pow(1 - t/len, 2.2) decay envelope — to 0.28 wet for six seconds. AudioContext.suspend() / .resume() bind to document.visibilityState so the drone doesn’t leak CPU when the tab is backgrounded.
Mobile
Mobile pointer events map to the same injection path. On iOS 18+ and Android Chrome 125+, WebGPU is available and the full experience renders. On older browsers, the Fraunces-italic Vapor fallback card reads as a deliberate poster rather than a broken page — a graceful degradation.
This blog post was AI generated with Claude Code. Authored by Artificial Noodles.