Water caustics in the browser are computed as light rays in real time
A developer going by the handle ScottieFox has open-sourced the project CAUSTIC//VOLUME — two browser scenes with water in which the caustics on the floor are computed every frame from light rays. Every vertex of a fine mesh on the surface sends a single ray down through the water column, and bright lines appear where neighbouring rays converge on the floor.
Both versions are a single HTML file each built on three.js, with no installation and no build step: open it in a browser and it works. The code is released under MIT, meaning it can be read, modified and used in commercial projects as long as the copyright notice and the license text are preserved.
How caustic brightness is computed
Brightness comes from a ratio of areas: a patch of surface whose light lands on a smaller patch of the floor concentrates it more strongly, and the shader gets that ratio through screen-space derivatives. Overlapping patches add up — that is exactly how the characteristic shifting threads of light arise. Sunbeams entering through the glass wall below the waterline are added separately.
The surface is assembled from 24 travelling waves ranging from 1.2 metres to 12 centimetres in length, with directions spread by the golden angle so that waves of similar length do not run alongside each other. Because of this the caustic net comes out cellular rather than striped, as it would be with unidirectional waves. Wave heights are chosen so that each scale deflects light by roughly the same amount — and that is what gives the sharpest lines on the floor.
The interactive rings on the water are computed by a separate GPU simulation of the wave equation based on three.js's GPUComputationRenderer: it spreads user clicks, wind gusts and a duck's wake across the tank, reflecting all of it off the glass. The water material works from the Fresnel equations for reflecting the sky and Snell's law for refracting the line of sight into the depths, total internal reflection kicks in past the critical angle, and the colour comes from Beer–Lambert absorption: red fades faster than blue.
One demo for a phone, the other for a top-end GPU
CAUSTIC//LITE is a glass tank, a rubber duck on the surface and a switchable light source: the sun or a hanging lamp. A click raises a wave and nudges the duck, sliders change wave height and turbulence. The whole program takes about 990 lines including comments and was written on the assumption that people will read it. The page monitors its own frame rate and changes the render resolution, and if WebGL 2 is unavailable or three.js failed to load, the scene along with the duck is traced in plain JavaScript on a 2D canvas.
The full version is roughly 7,500 lines assembled from 18 modules and is aimed at powerful hardware: it needs WebGL 2 with the EXT_color_buffer_float extension. It adds:
- an FFT ocean spectrum on top of the interactive ripple simulation (iWave), foam and splashes;
- a caustic volume that drives light shafts through the water, and photon tracing of caustics with chromatic dispersion;
- physically based buoyancy for a toy battleship, a pirate ship, a rubber duck, a message in a bottle, a chest, an anchor, an amphora and a diving helmet;
- schooling clownfish and surgeonfish, a pulsing jellyfish with swaying tentacles, an octopus with two rows of suckers and a sideways-walking crab;
- a three-dimensional ink simulation and multicoloured sand settling in layers — both of which are picked up by the current when you stir the water by dragging;
- up to three lamps with their own colours and a laser that refracts at mid-water level and reflects off the walls;
- quality presets from Low to Insane with temporal antialiasing, bloom and depth of field.
The underwater viewpoint is separately intriguing: the tracer handles both total internal reflection and Snell's window — that very circle of sky visible from below when looking up at a calm surface.
Where to get it and what you can do with it
The CAUSTIC//VOLUME project is on GitHub in full: the repository can be downloaded as a ZIP archive and lite/index.html or sandbox/index.html opened locally. No server is needed, three.js version r186 is pulled from the jsDelivr CDN through an import map and cached by the browser afterwards. The sandbox is rebuilt from the modules with a single command on Node 18, with no extra dependencies; optional tests run both scenes in headless Chromium via Playwright and fail on shader errors.
The author asks contributors to preserve two conditions: each version stays a single HTML file built on three.js with no installation, and the lite variant must not exceed a thousand lines and has to keep a working CPU fallback.
Why demos like this are being made in the browser again
In real time, caustics are most often depicted the cheap way — with a precomputed animated texture projected onto the floor; that trick is used in the standard three.js examples too. In major engines a physically correct computation usually only shows up together with ray tracing, or stays inside a closed implementation that an artist cannot look into. The value of this release probably lies precisely in the fact that a readable version has been published in full and without licensing restrictions.
The method itself is not new: comparing the areas of the original and the refracted patch of surface was already used by Evan Wallace in his WebGL demo, which is still being ported to various platforms — 80 Level also links to such ports. What is new here is the scale and the accessibility: the same principle has been expanded into a full-fledged sandbox with buoyancy, ink, sand and light shafts, while remaining a file that opens with a double click.
The project appearing right now looks like a logical development: WebGL 2 has become the common denominator of every current browser, Safari included, and float targets have stopped being exotic, so serious simulation in the browser has become noticeably more accessible. Also telling is the note in the documentation that Chrome, as of version 141, no longer falls back to software WebGL on machines without a working GPU — hence the emphasis on the CPU fallback in the lite version. There is noticeable activity around water in three.js in general, including commercially, and a free project with a detailed breakdown of the algorithms sets the bar for what counts as achievable in a browser tab. If the format of single-file open techno-demos takes hold, the community will gradually accumulate a body of reference implementations for effects that used to be passed from project to project as approximate hacks.