SHEET 003 โ 2026
Making a tree you can touch
Art direction first, then the shader work that keeps fifteen thousand leaves responsive to a pointer. Built step by step with the real WebGL renderer behind this site.
SCROLL
01 / 07
Making a painted tree is mostly an art problem. How round should the canopy be? How soft should the leaves feel? Where should the eye rest? The engineering supports those decisions, and most of it goes into drawing and moving enough leaves without slowing the page down.
This article builds the tree on the home page, from one leaf to a canopy that parts around your pointer.
02 / 07
One leaf is a card
A leaf begins as a square that always faces the camera. The shape is not in the geometry at all: the fragment shader draws a rounded, slightly pointed outline and discards everything outside it. That keeps the mesh at two triangles per leaf.
float w = pow(sin(3.14159 * pow(t, 0.85)), 0.55) * 0.92;
float m = w - abs(q.x);
if (smoothstep(-0.04, 0.04, m) < 0.5) discard;
Roundness is an art-direction choice. A sharp tip reads as wiry; a fuller outline reads as a painted mass. The site uses the fuller one.
03 / 07
Fifteen thousand copies
Fifteen thousand leaves cannot be fifteen thousand objects. Instancing shares one quad and gives every copy a small record: position, size, rotation, colour and a phase for the wind. The GPU draws them in one call.
iPosโ where the leaf sits in the canopyiDataโ size, angle, phase, growth delayiColorโ one of six palette greens, with a few rust accents
04 / 07
Planting the canopy
The canopy is a handful of overlapping spheres at the branch tips plus three big ones in the middle. Leaves are sampled inside them, biased toward the surface so clumps read as volumes instead of fog.
05 / 07
Wind that moves together
Unrelated motion on every leaf looks like vibration. Wind should connect neighbours: a slowly moving noise field gives nearby leaves similar values, so a gust travels through the canopy.
float gust = fbm(iPos.xy * 0.35 + vec2(uTime * 0.32, uTime * 0.18)) - 0.5;
float flutter = sin(uTime * 2.6 + phase * 6.2831) * 0.5;
06 / 07
Pushing leaves aside
The pointer is projected onto a plane through the canopy. Every leaf compares its position with that point and slides away with a smoothstep falloff; eight fading positions behind the pointer leave a short wake.
The trick that stopped the jitter: only a moving pointer adds to the wake. A resting pointer must not keep poking the same spot.
07 / 07
What is next
Birds, the grass, and the characters that stand in front of all this have their own notes. Each one is a smaller problem than it looks once the leaves are out of the way.