emircancaglar.com
A scroll-driven WebGL cathedral built solo in Three.js and Blender
This site is a single continuous 3D descent: scrolling drives a camera along one stitched path through a hand-modeled cathedral, rendered in three.js on a fixed canvas. A generative audio score reacts to depth and scroll speed underneath it, and the whole thing is built and run by one person.
the problem
A scroll-driven 3D scene has two problems an ordinary site doesn't: the whole experience has to run inside a WebGL frame budget on a phone GPU, and the crawlable, indexable layer has to exist independently of the canvas, since search engines and AI crawlers don't render WebGL. Both had to be solved without a visible drop in quality, since the point of the piece is that it shouldn't read as a tech demo.
how it is built
- Every asset modeled in Blender by hand, exported to glTF, then meshopt-packed: total scene payload dropped from roughly 17MB to about 1.3MB.
- three.js renders to one fixed canvas; page scroll drives a virtual camera along a single continuous stitched spline through five modeled chapters rather than cutting between separate scenes.
- A generative Web Audio score, not an audio file: a sub-bass drone that deepens with descent, metal-groan transients on chapter crossings and hard scroll input, a rising hum near the end, plus event-driven voices — a heartbeat near the core, a vowel-glide when a record's verse begins.
- Next.js server-renders the crawlable text content underneath the canvas, so the page carries real indexable HTML independent of what the WebGL layer does.
- Deployed on Cloudflare Workers through OpenNext.
- React Three Fiber (drei, postprocessing) plus GSAP and Lenis sit alongside a large hand-written three.js world module driving chapter transitions, the camera rig, and scroll-to-verse pacing.
- Static chapter geometry collapses into one merged draw call per chapter, cutting total draw calls from 234 to 71, with meshopt-quantized attributes dequantized to full precision before the merge transform runs.
- A scroll-pacing governor rate-limits how fast the reading can be forced even under a slammed scroll wheel, so each verse holds on screen for a minimum time in both directions.
- Built and run solo, single author.
the hard parts
- Mobile framerate sat at 30-40fps even with extra shading, lights, post-processing and particles all disabled through debug probes — meaning the bottleneck wasn't any of the expensive-looking effects, it was 200-plus separate draw calls per frame from individually-meshed static geometry. Merging each chapter's static meshes into one draw call (234 to 71 total, measured) fixed it; a later pass found the merge broke visually on one chapter with no measured benefit there, so it's now off by default for that chapter specifically rather than reverted everywhere.
- The audio has to be generative rather than a sound file, because there's no fixed timeline to sync a file to — scroll position and speed are the only clock, so the drone, groan transients and rising hum are all built from oscillators and noise driven directly by live scroll state.
- The build carries its own diagnostic tooling in production: a live draw-call HUD and a family of isolation flags (individually disabling soot shading, extra lights, post-processing, particles, or forcing a specific render resolution) exist specifically because mobile GPU behavior doesn't reproduce reliably under desktop or emulated profiling — the bottleneck had to be measured on an actual device.
Need something like this built?
Backend systems, payment and API integrations, AI pipelines, and the interfaces in front of them. Freelance or contract, remote.