Pixels dragged along a curling current — ink bleeding through water.
GLSL fragment shader
The effect's own code. The uniforms and helpers above it — uTex, uFeedback, the depth proxy, the frame ring, depthAt(), timeAt() — are the same header every Ether-MOSH shader opens with, and are documented once on the registry rather than reprinted on all 143 pages.
void main(){ float sc = mix(1.5, 9.0, uScale); float t = uTime * 0.3; float e = 0.02; // Curl of a scalar noise field is divergence-free, so the image is // advected along smooth closed streamlines instead of smearing outward. vec2 p = vUv * sc; vec2 curl = vec2(noise(p + vec2(0.0, e) + t) - noise(p - vec2(0.0, e) + t), -(noise(p + vec2(e, 0.0) - t) - noise(p - vec2(e, 0.0) - t))) / (2.0 * e); vec2 uv = vUv + curl * uFlow * 0.055; // A second advection step lengthens the streamlines into ribbons. vec2 p2 = uv * sc; vec2 curl2 = vec2(noise(p2 + vec2(0.0, e) + t) - noise(p2 - vec2(0.0, e) + t), -(noise(p2 + vec2(e, 0.0) - t) - noise(p2 - vec2(e, 0.0) - t))) / (2.0 * e); uv += curl2 * uFlow * 0.04; vec4 col = texture2D(uTex, uv); // Ink density shading along the flow, strong enough to read through a stack. float dens = clamp(length(curl) * 0.14, 0.0, 1.0); vec3 ink = mix(vec3(0.03, 0.05, 0.11), vec3(0.55, 0.16, 0.42), dens); col.rgb = mix(col.rgb, col.rgb * (1.0 - dens * 0.75) + ink * dens * 1.4, uFlow); gl_FragColor = col; }