Three.js · WebGL · Remotion
A 3D composition that is live in your browser.
169 blocks whose heights come from two travelling waves plus a per-cell phase offset. Everything below is the real scene running in WebGL — move your mouse to orbit, drag the frame slider to scrub. Every value is a pure function of the frame, so it seeks exactly.
The piece
Nothing is keyframed by hand, and nothing is random at render time — which is why the same scene scrubs cleanly here and renders identically to video.
Deterministic
Every value derives from the frame index. No wall-clock time, no Math.random.
Drag the slider backwards and the field rewinds exactly.
Seeded, not random
Per-cell variation comes from a hash of the cell coordinates. The same seed always produces the same field, so a re-render matches the original.
Lit deliberately
One warm key light, one cool rim to separate the field from the background, and low ambient. Default lighting is what makes generated 3D look generated.
Type over depth
The serif overlay sits above the canvas so the geometry reads as depth behind the words, not an object on display.
One scene, two outputs
The WebGL scene above and the video below are the same composition — one runs live, one is rendered frame by frame.
-
in the browseraboveWebGL via Three.js, no bundler and no build step. It ships as plain files, which is why this page has no framework to break.
-
stillone frame, to inspectnpx remotion still DataField out/frame.png --frame=45— the only reliable way to catch a scene that renders black, is mis-lit, or is off-camera. -
renderMP4, belownpx remotion render DataField out/datafield.mp4— H.264, 2.0s, 1920×1080, 768 KB. Remotion bundles its own FFmpeg, so nothing needs installing.
// the one line that makes headless 3D work at all Config.setChromiumOpenGlRenderer('angle'); // else: Error creating WebGL context // in the scene — never R3F's useFrame; animate from the frame const frame = useCurrentFrame(); <ThreeCanvas width={width} height={height} />
Where it fights back
Real friction from building this, not hedged caveats.
- WebGLHeadless Chrome cannot create a WebGL context without
setChromiumOpenGlRenderer('angle'). A 2D composition never needs it, so it is easy to miss when you add 3D later. It fails at frame 1 with no output. - React ceiling
@react-three/fiber@9.7.0requiresreact >=19 <19.3. Plainreact@19resolves to 19.3.0 and npm aborts withERESOLVE. Pin 19.2.3 rather than forcing it. - Renders are slow169 meshes across 60 frames took minutes, not seconds.
Iterate with
--frames=0-9before committing to a full render. - No GPU in CISoftware rendering is what makes headless 3D possible at all. Cloud runners will be slower than a machine with a real GPU.