Boids

GPU flocking simulation

Initializing WebGPU...

Quick Start

Loading...

Source

Loading...

Documentation

Boids

GPU-accelerated flocking simulation using Reynolds' boids algorithm. A compute shader updates positions and velocities each frame — you handle rendering however you want.

Usage

import { createBoids } from './boids';

const flock = createBoids(device, {
  count: 2048,
  bounds: { width: 100, height: 100 }
});

// Per frame
flock.update(deltaTime);

// Read positions/velocities for rendering
const positionBuffer = flock.positions; // GPUBuffer of vec2<f32>
const velocityBuffer = flock.velocities; // GPUBuffer of vec2<f32>

// Clean up when done
flock.destroy();

Rendering

The position and velocity buffers have VERTEX usage, so you can bind them directly in a render pipeline:

passEncoder.setVertexBuffer(0, flock.positions);
passEncoder.draw(6, flock.count); // instanced quads, triangles, etc.

Or use them as storage buffers in a custom compute/render shader.

API

createBoids(device, options?)

Returns a Boids instance. Boids are initialized with random positions within bounds and random velocity directions.

Option Type Default Description
count number 2048 Number of boids
bounds { width: number, height: number } 100x100 Simulation area (centered at origin)
separationWeight number 1.5 Separation force multiplier
alignmentWeight number 1.0 Alignment force multiplier
cohesionWeight number 1.0 Cohesion force multiplier
separationRadius number 3.0 Distance threshold for separation
neighborRadius number 6.0 Distance threshold for alignment/cohesion
maxSpeed number 10.0 Maximum boid speed
maxForce number 5.0 Maximum steering force per frame

flock.update(deltaTime)

Dispatches the compute shader to advance the simulation by deltaTime seconds.

flock.positions

GPUBuffer containing count vec2<f32> values (current positions). Usages: STORAGE | COPY_DST | VERTEX.

flock.velocities

GPUBuffer containing count vec2<f32> values (current velocities). Usages: STORAGE | COPY_DST | VERTEX.

flock.count

Number of boids in the simulation.

flock.destroy()

Releases all GPU buffers (both ping-pong pairs and uniforms).

Algorithm

The simulation implements Craig Reynolds' three classic flocking rules, evaluated per-boid on the GPU:

Separation — Steer away from nearby boids to avoid crowding. Each boid accumulates repulsion vectors from all boids within separationRadius, weighted inversely by distance (closer boids push harder). The result is normalized and scaled to produce a steering force.

Alignment — Match the heading of nearby boids. Each boid averages the velocity of all neighbors within neighborRadius, then steers toward that average direction.

Cohesion — Steer toward the center of mass of nearby boids. Each boid averages the position of all neighbors within neighborRadius, then steers toward that centroid.

The three forces are weighted, summed, and applied as acceleration. Velocity is clamped to maxSpeed. Boundaries wrap toroidally (boids exiting one side appear on the opposite side).

Neighbor search is brute-force O(n^2) — each boid reads all other boids. This is simple and works well for up to ~4K boids on modern GPUs. For larger counts, you'd add a spatial hash (see Modifying section below).

WGSL loading

The default import uses Vite's ?raw suffix:

import shaderSource from './boids.wgsl?raw';

If you're not using a bundler, load via fetch:

const shaderSource = await fetch(new URL('./boids.wgsl', import.meta.url)).then((r) => r.text());

Modifying

Extend to 3D

Change vec2f to vec3f throughout the WGSL shader and TypeScript. Update buffer sizes from count * 2 * 4 to count * 4 * 4 (vec3f is 16-byte aligned in WGSL storage buffers). Add a bounds.depth parameter and update the wrapping logic.

Add obstacle avoidance

Add a storage buffer of obstacle positions and radii. In the main loop, after the three flocking forces, add a repulsion force from any obstacles within a detection range:

for (var i = 0u; i < obstacle_count; i++) {
  let diff = pos - obstacles[i].xy;
  let dist = length(diff);
  if (dist < obstacles[i].z) { // z = radius
    force += normalize(diff) * avoidance_weight / max(dist, 0.001);
  }
}

Add spatial hashing for large flocks

Replace the O(n^2) neighbor search with a spatial hash grid. The approach:

  1. Add a compute pass that assigns each boid to a grid cell (hash position to cell index)
  2. Sort boids by cell index using a radix sort (see the radix-sort module)
  3. In the main flocking pass, only search boids in the 3x3 neighborhood of cells

This reduces the search from O(n^2) to O(n * k) where k is the average number of neighbors.

Tune flocking behavior

The balance of separationWeight, alignmentWeight, and cohesionWeight controls the overall character:

  • High separation, low cohesion: scattered, avoidant movement
  • High cohesion, low separation: tight clusters that clump together
  • High alignment: synchronized, parallel movement (schooling fish)
  • Equal weights: classic balanced flocking

The separationRadius and neighborRadius control the spatial scale. A larger neighborRadius means boids coordinate over longer distances; a larger separationRadius means they maintain more personal space.

Further Reading

Resources on flocking algorithms, boids, and GPU particle simulation.

Original Research

  • Craig Reynolds, "Flocks, Herds, and Schools: A Distributed Behavioral Model" (SIGGRAPH 1987) The foundational paper introducing boids. Defines the three rules (separation, alignment, cohesion) and demonstrates emergent flocking behavior from simple local interactions. Reynolds won an Academy Award for Technical Achievement for this work. https://dl.acm.org/doi/10.1145/37402.37406

  • Craig Reynolds, "Steering Behaviors for Autonomous Characters" (GDC 1999) Extends the original boids model with practical steering behaviors for games: seek, flee, pursue, evade, wander, path following, obstacle avoidance, and leader following. https://www.red3d.com/cwr/steer/gdc99/

  • Craig Reynolds' Boids page The author's own reference page with links to papers, implementations, and related work. https://www.red3d.com/cwr/boids/

GPU Implementation

  • WebGPU Samples — Compute Boids The official WebGPU samples include a boids implementation. A useful reference for WebGPU-specific patterns like ping-pong buffers and compute dispatch. https://webgpu.github.io/webgpu-samples/?sample=computeBoids

  • Simon Green, "Particle Simulation using CUDA" (2010) NVIDIA technical report covering GPU particle systems with spatial hashing for neighbor search. The spatial hash approach described here can be adapted to optimize this module's brute-force neighbor search. https://developer.download.nvidia.com/assets/cuda/files/particles.pdf

  • Joselli et al., "A Flocking Boids Simulation and Optimization Structure for Mobile Multiprocessor Systems" (2009) Explores optimization strategies for GPU flocking simulations, including spatial partitioning and workload balancing.

Spatial Hashing

Emergent Behavior

  • Iain Couzin et al., "Collective Memory and Spatial Sorting in Animal Groups" (2002) Studies how simple local rules produce complex group-level phenomena in animal collectives. Provides biological context for why the boids model works. https://doi.org/10.1006/jtbi.2001.2443

  • Vicsek et al., "Novel Type of Phase Transition in a System of Self-Driven Particles" (1995) Physics perspective on flocking as a phase transition. The Vicsek model is a simplified variant of boids that's easier to analyze mathematically. https://doi.org/10.1103/PhysRevLett.75.1226

General References

  • Daniel Shiffman, "The Nature of Code" — Autonomous Agents Accessible introduction to steering behaviors and flocking, with step-by-step code examples. Good for understanding the math behind the Reynolds model. https://natureofcode.com/autonomous-agents/

  • Sebastian Lague, "Coding Adventure: Boids" (YouTube) Visual walkthrough of implementing boids with GPU compute shaders, including spatial partitioning optimization. https://www.youtube.com/watch?v=bqtqltqcQhw