Boids

GPU flocking simulation

Quick Start

import { createBoids } from './webgpu-market/boids/boids';

const boids = createBoids(device, {
  count: 2048,
  bounds: { width: 100, height: 100 },
  separationWeight: 1.5,
  alignmentWeight: 1.0,
  cohesionWeight: 1.0,
});

// Each frame
boids.update(deltaTime);

// Bind boids.positions (GPUBuffer of vec2f) in your render pipeline
boids.destroy();
Source
// GPU Boids — Reynolds' flocking simulation
// Each thread processes one boid: reads all other boids for neighbor search (O(n^2)),
// applies separation, alignment, and cohesion rules, then updates position and velocity.
// Uses ping-pong buffers — reads from "in" buffers, writes to "out" buffers.

struct Uniforms {
  delta_time: f32,
  count: u32,
  bounds_width: f32,
  bounds_height: f32,
  separation_weight: f32,
  alignment_weight: f32,
  cohesion_weight: f32,
  separation_radius: f32,
  neighbor_radius: f32,
  max_speed: f32,
  max_force: f32,
  _pad: u32,
}

@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var<storage, read> positions_in: array<vec2f>;
@group(0) @binding(2) var<storage, read> velocities_in: array<vec2f>;
@group(0) @binding(3) var<storage, read_write> positions_out: array<vec2f>;
@group(0) @binding(4) var<storage, read_write> velocities_out: array<vec2f>;

// Limit vector magnitude without branching on zero-length vectors
fn limit_vec(v: vec2f, max_len: f32) -> vec2f {
  let len_sq = dot(v, v);
  if (len_sq > max_len * max_len) {
    return normalize(v) * max_len;
  }
  return v;
}

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) gid: vec3u) {
  let idx = gid.x;
  if (idx >= u.count) {
    return;
  }

  let pos = positions_in[idx];
  let vel = velocities_in[idx];

  // Accumulators for the three flocking rules
  var separation = vec2f(0.0);
  var alignment = vec2f(0.0);
  var cohesion = vec2f(0.0);
  var sep_count = 0u;
  var neighbor_count = 0u;

  // Brute-force neighbor search over all boids
  for (var i = 0u; i < u.count; i++) {
    if (i == idx) {
      continue;
    }

    let other_pos = positions_in[i];
    let diff = pos - other_pos;
    let dist_sq = dot(diff, diff);

    // Separation: steer away from boids that are too close
    let sep_r_sq = u.separation_radius * u.separation_radius;
    if (dist_sq < sep_r_sq && dist_sq > 0.0) {
      let dist = sqrt(dist_sq);
      // Weight inversely by distance — closer boids push harder
      separation += (diff / dist) / dist;
      sep_count++;
    }

    // Alignment and cohesion: consider boids within the neighbor radius
    let neighbor_r_sq = u.neighbor_radius * u.neighbor_radius;
    if (dist_sq < neighbor_r_sq) {
      alignment += velocities_in[i];
      cohesion += other_pos;
      neighbor_count++;
    }
  }

  var force = vec2f(0.0);

  // Separation: average repulsion vectors, scale to max speed, compute steering force
  if (sep_count > 0u) {
    separation /= f32(sep_count);
    if (dot(separation, separation) > 0.0) {
      separation = normalize(separation) * u.max_speed;
      separation -= vel;
      separation = limit_vec(separation, u.max_force);
    }
    force += separation * u.separation_weight;
  }

  // Alignment: steer toward average heading of neighbors
  if (neighbor_count > 0u) {
    alignment /= f32(neighbor_count);
    if (dot(alignment, alignment) > 0.0) {
      alignment = normalize(alignment) * u.max_speed;
      alignment -= vel;
      alignment = limit_vec(alignment, u.max_force);
    }
    force += alignment * u.alignment_weight;
  }

  // Cohesion: steer toward average position of neighbors
  if (neighbor_count > 0u) {
    cohesion /= f32(neighbor_count);
    let desired = cohesion - pos;
    if (dot(desired, desired) > 0.0) {
      let steer = normalize(desired) * u.max_speed - vel;
      force += limit_vec(steer, u.max_force) * u.cohesion_weight;
    }
  }

  // Integrate velocity and position
  var new_vel = vel + force * u.delta_time;
  new_vel = limit_vec(new_vel, u.max_speed);

  var new_pos = pos + new_vel * u.delta_time;

  // Wrap around bounds (toroidal boundary)
  let hw = u.bounds_width * 0.5;
  let hh = u.bounds_height * 0.5;

  if (new_pos.x < -hw) { new_pos.x += u.bounds_width; }
  if (new_pos.x > hw) { new_pos.x -= u.bounds_width; }
  if (new_pos.y < -hh) { new_pos.y += u.bounds_height; }
  if (new_pos.y > hh) { new_pos.y -= u.bounds_height; }

  positions_out[idx] = new_pos;
  velocities_out[idx] = new_vel;
}
Documentation

Boids

Flocking simulation using Reynolds' boids algorithm. A compute shader updates positions and velocities each frame. Rendering is separate.

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).

Further Reading

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.2002.3065

  • 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