A procedural, audio- and mouse-reactive WebGL recreation of a hand-drawn black-and-white Adult Swim wave/rain bumper — rendered live in a single fragment shader.
The whole scene is one WebGL2 fragment shader (index.html, no build step):
rolling forward-leaning swells, a stipple-boiled sand shore, a spray halo,
falling rain, and a stack of crest-parallel foam strands — all reacting to
mouse movement and to live microphone or an uploaded audio file.
The look isn't guessed. It was measured from the reference footage. Rather
than synthesizing foam procedurally (Voronoi cells and sine lines were both
tried and rejected), the hand-drawn foam is extracted from the source frames,
rectified into wave-space, converted to signed-distance fields, and stacked
into a texture atlas (foam_atlas.png) that the shader samples and morphs
through over time.
The reference video was analysed frame-by-frame; the shader was built from the numbers, not from eyeballing. The scripts that produced the assets:
| Script | Role |
|---|---|
extract_waves.py |
Pull the wave region out of each reference frame. |
make_atlas.py |
Rectify hand-drawn foam into wave-space, convert to SDF, stack into foam_atlas.png. |
study.py / study2.py / study3.py |
Fit the measured constants (waterline slosh, foam spacing/thickness, stipple density, rain streaks). |
compare.py |
Stack a headless render under the reference frame and diff coverage metrics. |
check_atlas.py |
Sanity-check the generated atlas slices. |
Key measured facts baked into the shader:
- Foam = level sets of a warped log-depth field, spacing ~0.042 uv at the crest widening ~4× by mid-depth; thickness tapers crest → deep.
- Waterline = a standing slosh, mean ~0.27 uv, ±0.07, period ~3 s.
- Sand = uniform-noise stipple re-randomized ~8×/s (the "boil").
- Rain = peak brightness ~0.17, streaks ~0.055H, over ~20% of columns.
- Audio = bass-onset detection surges wave height and breaking activity on the beat.
The demo is fully static — index.html only needs foam_atlas.png beside it,
so any static file server works:
npm install # optional: pulls express for the bundled server
npm start # serves on http://localhost:3000Or just open index.html over any static host (e.g. python -m http.server).
- Move the mouse — perturbs the sea.
T— toggle the tuner panel (12 live parameters; config is saved tolocalStorage).H— hide/show the UI.- mic / audio file — drive the animation with live or uploaded audio.
The heavy source assets (the reference *.mp4, the extracted frames/ and
wave_assets/, and the study/ screenshots) are gitignored to keep the repo
lean. Point the scripts at your own source video and re-run
extract_waves.py → make_atlas.py to rebuild foam_atlas.png.
MIT — see LICENSE.

