A hundred thousand drifters in a wind they can't see.
Each particle holds a position in a floating-point texture. Every frame, a fragment shader reads the texture, samples a three-dimensional curl-noise field at the particle's location, and writes a new position back. The result is a divergence-free flow — particles swirl without piling up. Then a second material reads the texture in its vertex shader and draws one additive point per pixel. The whole simulation lives on the GPU; the CPU only sets a few uniforms each frame.
Three.js · GPGPU · 100k particles · curl of simplex noise
Move the mouse to push the cloud · click to swirl harder · 50k/100k/200k buttons bottom-right
weekend sketch · Three.js r169 + GPUComputationRenderer
The state lives on the GPU
Each particle is one texel in a 317-by-317 floating-point texture. The fragment shader is the simulation: it reads the texture, computes a new position, writes it to a second texture, then they swap (ping-pong). The CPU never touches a particle, which is why 100k stays at 60fps on a laptop.
Why curl noise
Plain Perlin or simplex noise has divergence — sample it as a velocity field and particles collapse into sinks. The curl operator (∂/∂y of the z-component minus ∂/∂z of the y-component, and cyclic) produces a divergence-free field. Particles drift with the wind but never pile up — the visual texture that defines this aesthetic.
Mouse as a repulsor
Move the cursor and a soft Gaussian repulsor pushes particles outward in 3D. Click and drag and the strength doubles. The mouse position is projected from screen space to the z=0 plane of the simulation, so the depth of the cloud reads correctly under the cursor.
The next step
Same scaffold, different velocity field: swap the curl noise for a set of attractors, or for the gradient of a signed-distance field around a 3D logo, and the same particles will reshape themselves around the new geometry. That's how the Houdini-style aesthetic generalises in the browser.