Field studies

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

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.

jesperlandberg.com
Arc — a still from the piece

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);
Wrap the travel coordinate onto a shared cylinder.

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.