WebGL2GLSLGPGPUTypeScript
Controls
- Move the mouse (or drag a finger) to stir the particles into a vortex; on touch, the dashed strip on the right edge scrolls the page
- Press and hold on the canvas to gather the particles into the chosen shape; let go and they scatter back into the flow. Form keeps the shape without holding
- Shapes: your word (the initials VK to start), Sphere, Knot (a torus knot) and Galaxy
- Palette cycles Nebula, Ember, Aurora and Frost; Freeze stops the simulation; Snapshot downloads the frame as a PNG
- Tune opens a word field (type up to 10 characters and press Form it), sliders for flow, swirl, point size and glow, the particle count (64K, 256K, 1M), Trails and Orbit switches, and Reset
- Keyboard: F or Enter forms the shape, 1 to 4 pick a shape, P palette, T trails, Space freezes, arrow keys turn the view, S snapshot, R resets
- Add ?quality=low to the address to start with 256K particles
Needs WebGL2 with renderable float textures (current Chrome, Edge, Firefox and Safari). Without it the poster is shown; if the graphics context is lost, a button restarts the simulation. Starts with one million particles on desktops and a quarter million on phones, and steps down by itself (to 256K, then 64K) when frames get slow. Respects the reduced-motion setting: no idle orbit and no release burst, the page opens on a still frame and the particles move slowly, only while you interact.
How it’s built
Up to a million particles simulated on the graphics card. They drift in a curl-noise flow and spin around the pointer; press and hold and every particle is pulled to its own point on a shape (the word, VK to start, a sphere, a torus knot or a spiral galaxy), let go and they burst back into the flow.
How it works
- Raw WebGL2 and GLSL ES 3.00, no engine and no new dependencies.
- State in textures: position (xyz plus a random seed) and velocity live in two RGBA32F textures, one texel per particle: 1024 × 1024 for a million. Two such pairs ping-pong; one full-screen pass reads the old pair and writes the new one to both textures at once with multiple render targets (
drawBuffers). Needs EXT_color_buffer_float. Particles are seeded on the GPU too (an init pass), so the CPU never builds or uploads a particle array. - Flow: the velocity follows the cross product of the gradients of two 3D simplex noise fields (analytic gradients, so two noise calls per particle), which is divergence-free: it folds like smoke without clumping. A soft spherical wall pulls stragglers back.
- Pointer: the pointer is projected onto the plane through the centre facing the camera. Within a Gaussian radius it adds a vortex around the view axis, a stir along the pointer's own motion and a slight push.
- Shapes: targets are computed in the shader from per-particle hashes (sphere shell with a sparse core, points inside a tube around a (2,3) torus knot, a three-arm log-spiral galaxy with differential rotation and a bulge). The word is drawn into an offscreen 2D canvas in the site's display font, its filled pixels sampled into a 128 × 128 float texture (up to 16 384 points), and each particle takes one of them, jittered inside its pixel and laid out in the camera's plane so it always faces you.
- Morph: a damped spring towards the target, with drag rising as the shape forms. Each particle's start is offset by its seed, so the form gathers in a wave; releasing adds a short outward burst.
- Drawing: one
gl.POINTS draw with no vertex buffers; the vertex shader fetches its particle with gl_VertexID and texelFetch. Colour comes from speed, seed and depth through a three-colour palette; points add up in a half-float buffer, so dense places run hot. Trails darken the previous frame instead of clearing it. - Post: two bloom radii (quarter and eighth size, soft threshold, separable Gaussian), a faint palette-tinted backdrop, ACES filmic curve, vignette and grain that dithers away banding.
Performance
- Tiers: 1M particles on desktops, 256K on phones (
?quality=low), 64K at the bottom and from the start on software renderers (SwiftShader, llvmpipe). A governor steps down a tier when the smoothed frame time stays over 24 ms for 2 seconds; Tune can set the count by hand. - Pixel ratio capped at 2 (1.5 on the light tier). The loop runs only while the canvas is on screen and the tab is visible.
- A lost graphics context shows a "Scatter the particles again" state that rebuilds everything.
- Camera, projection and pointer picking, tiers, governor, palettes and the text sampler are pure functions with unit tests.
Reduced motion
No idle orbit and no release burst. The page opens on a still frame (the field is pre-run before the first frame), and the simulation runs at about a third of the speed and only while you interact.