Inspiration

The deep ocean is the largest living space on Earth, and almost none of us will ever see it. I wanted to build a site about that — specifically about losing light. Most 3D websites keep adding more to look at. I wanted one that takes things away as you go down, until the only light left is yours.

What it does

Abyss is a scroll-driven descent. You don't click through it — scrolling is sinking. The water darkens through the real zones of the ocean (Sunlight, Twilight, Midnight, the Abyss), a submersible-style depth gauge climbs past 4,000 meters, and marine life appears and fades with the light. Once it goes fully dark, your cursor becomes a lamp: bioluminescent jellyfish brighten and swell as you move it near them. It ends four kilometers down, where a colossal creature reveals itself in the black.

How we built it

Built from scratch with React Three Fiber and three.js. The descent is driven by drei's ScrollControls feeding a single smoothed "depth" value that everything reads from: background and fog color are interpolated across six depth stops, fog density ramps up, and a world group translates upward so creatures rise past the camera. The jellyfish are fully procedural — a lathed bell, a glowing rim, a halo sprite, and animated tentacle lines, with no downloaded models. The fish are an instanced mesh; marine snow is a particle system that recycles around the camera forever. Surface caustics are a small custom GLSL shader. Bloom and vignette (via @react-three/postprocessing) intensify with depth so the bioluminescence reads as real light. The cursor interaction works by projecting each jellyfish into screen space every frame and comparing it to the pointer. Optional ambient sound is filtered noise generated with the Web Audio API.

Challenges we ran into

Making darkness feel beautiful instead of empty was the hardest part — I solved it with additive glow sprites, marine snow that catches your light, and bloom that grows as you sink. Performance was the next: to hold 60fps with hundreds of moving things, I faked every glow with additive sprites instead of real lights, used instancing for the fish, and recycled particles instead of spawning them. Finally, keeping the scroll buttery while the depth readout and fading text stayed perfectly in sync with the 3D — without React re-rendering every frame — meant building a tiny shared store that the WebGL scene writes and the DOM overlay reads in its own animation loop.

Accomplishments that we're proud of

The moment the screen goes black and people realize their cursor is the only light, and the jellyfish start answering them. It reliably makes people lean in. I'm also proud that the entire scene is procedural — there isn't a single downloaded 3D model in it.

What we learned

A lot about React Three Fiber, GLSL shaders, and postprocessing — but mostly that in 3D, restraint is the effect. Taking light away turned out to be far more powerful than adding more of it.

What's next for Abyss

Depth-linked audio that shifts as you descend, more deep-sea life (anglerfish lures, drifting siphonophores), a shareable "how deep did you go" moment, and a WebGPU pass for much heavier particle counts.

Built With

Share this project:

Updates