Align Field
A grid of short strokes where each stroke has only two variables: its angle and its length. Unaligned strokes wander on slow noise. Three things align them and the effects stack: the upper half is settled, a diagonal front crosses on its own, and the pointer leaves a trail that fades in about two and a half seconds.
- 2026
- Shader
- Pointer
Controls
Speed
Pointer
Density
Origin
Written as the hero of alignscale.com, the studio I co-founded. Here the colours come from the theme and the trail warms to the accent.
Source
scene.ts74 lines
precision mediump float;
#define TRAIL 8
#define ALIGNED 0.7853982
uniform vec2 uRes;
uniform float uTime;
uniform float uEnergy;
uniform vec3 uTrail[TRAIL];
uniform vec3 uGround;
uniform vec3 uInk;
uniform vec3 uAccent;
uniform float uDensity;
uniform float uInteraction;
float hash(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
float noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), u.x),
mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), u.x), u.y);
}
void main() {
vec2 uv = gl_FragCoord.xy / uRes;
float aspect = uRes.x / uRes.y;
vec2 p = vec2(uv.x * aspect, uv.y);
vec2 g = p * uDensity;
vec2 id = floor(g);
vec2 f = fract(g) - 0.5;
vec2 centre = (id + 0.5) / uDensity;
// 1. The upper half is settled.
float split = smoothstep(0.38, 0.62, centre.y);
// 2. A front that crosses on its own.
float band = sin(centre.x * 1.3 + centre.y * 0.8 - uTime * 0.2) * 0.5 + 0.5;
float sweep = smoothstep(0.62, 0.98, band);
// 3. What the pointer has touched lately.
float trace = 0.0;
for (int i = 0; i < TRAIL; i++) {
vec2 d = centre - vec2(uTrail[i].x * aspect, uTrail[i].y);
trace = max(trace, exp(-dot(d, d) * 20.0) * uTrail[i].z);
}
trace *= uInteraction;
float align = clamp(split * 0.8 + sweep * 0.45 + trace, 0.0, 1.0);
// Unaligned strokes wander; aligned ones settle on one diagonal and grow.
float wild = noise(id * 0.37 + uTime * 0.06) * 6.2831853;
float angle = mix(wild, ALIGNED, align);
vec2 dir = vec2(cos(angle), sin(angle));
float len = mix(0.13, 0.46, align);
float w = mix(0.026, 0.05, align);
float along = clamp(dot(f, dir), -len, len);
float stroke = 1.0 - smoothstep(w, w + 0.035, length(f - dir * along));
float density = stroke * mix(0.12, 0.78, align) * (0.85 + uEnergy * 0.15);
// The trail also darkens the stroke, so the visitor's own trace reads even where the field is
// already aligned and could not turn any further.
density += stroke * trace * 0.22;
vec3 colour = mix(uInk, uAccent, clamp(trace * 1.4, 0.0, 1.0));
gl_FragColor = vec4(mix(uGround, colour, clamp(density, 0.0, 1.0)), 1.0);
}