WebGL study
Arc
A gallery that runs along a curve instead of a straight line, over a grid floor that disappears toward the horizon. You drag it, and the whole row swings past you like it's on a rail around your head.
The interesting constraint: no card is positioned individually. The curve is one continuous surface, and every card rides it.
- Type
- WebGL study
- Year
- 2026
- Stack
- Three.js, GLSL
Origin
The spark was Jesper Landberg's site. His work has a way of making a page feel like a physical surface you are moving across rather than a document you are scrolling, and I wanted to understand how much of that is geometry and how much is easing.
So the constraint I set was: one shared surface, no card animating on its own. Everything you see below comes out of that.

The curve lives in the vertex shader
Each vertex takes its world x — not its local x — and wraps that coordinate onto a cylinder centred on the viewer. Because the input is a world coordinate, the curve runs continuously across the whole row rather than each card bending on its own axis.
One uniform, uFlat, lifts everything back off the cylinder. That is how the same shader also renders a dead-straight column that only bends once a scroll pushes it round.
bool vert = uVertical > 0.5;
float along = vert ? world.y : world.x;
// position along the run, as an angle on the shared cylinder
float theta = along / uBend;
// BEND: wrap that travel coordinate onto a cylinder centred on the viewer.
// uFlat lifts it back off the cylinder entirely.
float bAlong = mix(uBend * sin(theta), along, uFlat);
if (vert) world.y = bAlong; else world.x = bAlong;
world.z += mix(uBend * cos(theta) - uBend, 0.0, uFlat);A bulge anchored to the screen
On top of the bend there is a standing wave in depth — a crest toward the viewer and a trough beyond it. The trick is that it is anchored to the screen rather than to the cards.
So the bulge stays put about a third of the way along while the row streams through it. Cards swell as they pass the crest and recede after it, which is what makes the row feel like it has weight rather than being a flat strip on a curve.
Titles are painted into the texture
The obvious way to label a card is HTML positioned over the canvas. It always looks stuck on, because it is — the type stays flat while the card bends away underneath it.
Instead, the title and button are drawn into a 2D canvas that becomes a texture on the card surface. Then they bend, twist and darken with the card, like ink on paper rather than a sticker on glass.
Drag never touches the renderer
Pointer handling sits on a transparent div over the WebGL canvas. The renderer never has to care about input, and the drag stays responsive even while a frame is mid-flight.

Next
Verdant Ridge
A property site for nine houses that don't exist.