vidar.games

GPU boids

2026-11-09 · Discuss on X

4,096 fish, same seed, same moving target. Left: each fish only steers at the target, and they pile into a clump. Right: three local rules, checked against every other fish each step, make a school.

The problem

Point every agent at one target and they converge on the same spot. In my run, 3 s after the start, the goal-only fish average 0.15 m to their nearest neighbour and 177 of 256 sampled fish are within 10 cm of another. Boids fixes this with three local rules. The hard part is that every boid needs to know who is near it.

How it works

The source (Unity-GPU-Boids, its final clean version) runs one compute thread per boid, every frame:

  1. Loop over every other boid. Anything within NeighbourDistance (2 m) is a neighbour.
  2. Separation: push away from each neighbour, weighted 1 − distance / 2.
  3. Alignment: average the neighbours' headings.
  4. Cohesion: average the neighbours' positions, starting from the flock target, so the target always counts as one neighbour.
  5. Affectors: anything inside an affector's radius gets pushed out.
  6. Steer: blend toward the new direction by exp(−RotationSpeed × dt), so turns stay smooth.
  7. Move at BoidSpeed with per-boid noise, plus a speed-up when far from the flock.

There is no grid and no sorting. The README says the author tried multilateration and bitonic sorting, and the plain loop stayed faster. Their table (author's numbers, GTX 980 Ti): plain GPU flock 93 FPS at 32,000 boids, bitonic sorting 20 FPS.

What I hit

WebGL2 has no compute shaders. Positions and directions live in two 64 x 64 float textures. Each step is two full-screen passes, new direction then new position, swapped between two copies. The fish vertex shader reads its own boid straight from the texture, so drawing needs no CPU copy.

The neighbour lines in the last shot do copy positions back to the CPU, like a debugger would. The simulation itself doesn't need that.

My build

three.js 0.170, WebGL2. 4,096 boids (the source's prefab uses 32,000), 60 simulation steps per second, about 16.8 million distance checks per step. Source defaults everywhere except the predator, which is mine: force 14 and range 5 m, so the push reads on screen.

Measured on the split shot, mean distance to nearest neighbour: at 3 s, 0.15 m goal-only vs 0.76 m boids. At 7.9 s, 0.07 m vs 0.73 m.

Trade-offs

When to use it

Fish, birds, insects, ambient crowds up to a few thousand agents. Past that, or with many separate flocks, test a grid or spatial hash, even though sorting lost in the source's own tests.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique source: Unity-GPU-Boids by Shinao, https://github.com/Shinao/Unity-GPU-Boids, MIT license (Copyright 2018 Raphael Monnerat), commit 7488321060181c4ef1a856434132dab42a94079e. Ported to three.js and GLSL from its final clean version (the Boid compute shader, the Boids instancing shader and the GPUFlock component): the brute-force neighbour loop, separation weighted by 1 - distance / NeighbourDistance, alignment, cohesion that starts from the flock position, the sphere affector push, the exp(-RotationSpeed x dt) turn blend, the speed noise and far-from-flock speed-up, the look-at orientation per instance, and the default parameters (RotationSpeed 4, BoidSpeed 6, NeighbourDistance 2, BoidSpeedVariation 0.9, BoidFrameSpeed 10, AffectorForce 2, AffectorDistance 2, SpawnRadius 10, size 0.5 to 1.5). The benchmark numbers quoted in the post are the source author's, measured on a GTX 980 Ti, from the README. No files were copied unchanged.

Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, npm package version 0.170.0. Used for rendering and render targets. It is not the source of the boids technique.

My own code: the WebGL2 version of the compute step (two float textures and two fragment passes per step), the low-poly fish and its tail motion, the underwater background, the moving target path, the predator path and its force and range values, the switches that turn the rules on one at a time, the neighbour debug lines, the spacing measurements, labels and video. The source's sparrow model, point-cloud drawings and skull model were not used. No external models or assets. No IA model.