Skip to content
Sample of Compute Particle System (WebGPU @compute pattern)

anadol_webgpu · computeParticleSystem

Compute Particle System (WebGPU @compute pattern)

The canonical WebGPU 'compute-to-render' pattern: a @compute shader updates N particles in a storage buffer (one thread per particle, simple Verlet-style integration: v += gravity; v *= drag; p += v; bounce off walls; life++; respawn if life>max), and a render pass consumes the same buffer as vertex data to draw point sprites. This numpy reference does exactly the same algorithm in vectorised form (one thread per particle, identical state transitions) so a downstream model can be trained on the GPU semantics without needing a browser runtime.

Original: https://www.mysimulator.uk/content/articles/webgpu-compute.html

▶ Try the live demo (JS port) ▶ Run reference.py via Pyodide 10 formulas have instant JS ports. The Pyodide path runs the actual Python in your browser — first load is ~6 MB, then instant.

Family
anadol_webgpu
Source
anadol · webgpu
Evolution
compute
Render
fading_glow_field
Complexity
medium
Agents
80000
Year
2024
Author
WebGPU compute primitives (concept)

Math essence

Per-particle update each step: vel += gravity; vel *= drag; pos += vel; pos reflected on wall; life += 1; if life >= life_max then respawn at centre with radial outward velocity and randomised hue seed. Canvas fades geometrically and each live particle splats a 5x5 weighted Gaussian point coloured by its current HSV(per-particle seed + step drift).

Parameters

Name Type Default Range Description
dim int 600 200–1500 Square canvas side length.
num_particles int 80000 1000–500000 Particle count N.
life_max int 240 16–2000 Life in steps before respawn.
gravity float 0.04 0–0.5 Per-step downward acceleration.
drag float 0.985 0.5–1 Per-step velocity damping.
spawn_radius float 0.3 0.05–0.5 Respawn radius (fraction of dim, centred).
workgroup_size int 64 32–256 Equivalent WebGPU @workgroup_size for the compute dispatch.

Reference

First 60 lines of reference.py. Full source: library_external/anadol_webgpu/computeParticleSystem/reference.py

"""computeParticleSystem — WebGPU compute-shader particle system.

This is the canonical "compute-to-render" pattern in WebGPU: a
@compute shader updates N particles in a storage buffer, and a
separate render pass draws them as point sprites.  Here we implement
the same algorithm in numpy as a faithful reference implementation
of the GPU loop.

Each particle has:
    pos: vec2f
    vel: vec2f
    life: f32 (in [0, life_max])
    seed: u32  (per-particle hue)

Every step the compute shader does:
    vel += gravity_and_drag(pos, vel, life, t);     // simple Verlet-ish
    pos += vel;
    vel *= drag;
    life += dt;
    if (life > life_max) reset particle to a new born state;

After the compute pass we render the live particles as glowing points
with colour = hsv(seed + life_normalised).  The numpy analogue updates
the same arrays with vectorised ops — semantically one thread per
particle.

Reference: WebGPU compute pattern as documented at
- https://www.mysimulator.uk/content/articles/webgpu-compute.html
- https://dev.to/dev48v/webgpu-i-simulated-250000-particles-entirely-on-the-gpu-in-the-browser-2o0f
- https://www.abratabia.com/webgpu/compute-shaders.php
- https://www.cg.tuwien.ac.at/research/publications/2023/PETER-2023-PSW/PETER-2023-PSW-.pdf
"""
from __future__ import annotations

from typing import ClassVar

import numpy as np


class ComputeParticleSystem:
    """WebGPU compute-shader style particle system (numpy reference).

    Parameters
    ----------
    dim : int
        Square canvas side length (px).
    num_particles : int
        Number of particles N.
    life_max : int
        Steps before a particle is "reset" (born anew at the centre
        with a random outward velocity).
    gravity : float
        Constant downward acceleration per step (per particle).
    drag : float
        Per-step velocity damping (1.0 = no damping).
    spawn_radius : float
        Radius (in pixels) inside which a respawning particle lands.
    workgroup_size : int
        For documentation only — this is the WebGPU @workgroup_size
        that an equivalent @compute shader would use.  Stored so the

Try running it locally

The reference implementation is self-contained Python with numpy. Install the package and run:

python library_external/anadol_webgpu/computeParticleSystem/reference.py

Need help? See Learn for the standard environment setup, or Playground for an in-browser sketchbook.

More from anadol · webgpu

Sample of 2D FFT Texture Spectrum (WebGPU butterfly FFT)

2D FFT Texture Spectrum (WebGPU butterfly FFT)

anadol_webgpu · fft2dTexture

The WebGPU butterfly 2D FFT pattern: stage by stage, a @compute shader reads two texels per output (input[p] and twiddle-weighted input[q]) and writes the combined result; after log2(N) horizontal stages, transpose, and log2(N) vertical stages, the 2D FFT is complete.

View →
Sample of Data Particle Field (Refik Anadol — Machine Hallucinations / AI Data Sculptures)

Data Particle Field (Refik Anadol — Machine Hallucinations / AI Data Sculptures)

anadol_webgpu · dataParticleField

Inspired by Refik Anadol Studio's AI Data Sculptures and 'data universe' portraits (e.g.

View →
Sample of Dreamscape Field (Refik Anadol — Dreams / Nature Dreams)

Dreamscape Field (Refik Anadol — Dreams / Nature Dreams)

anadol_webgpu · dreamscapeField

Inspired by Refik Anadol Studio's Dreams series (Nature Dreams, Urban Dreams, Space Dreams and the MoMA Fluid Dreams algorithm): a smooth low-frequency Perlin noise field drives slow global drift while a finer detail octave adds turbulent perturbation — Anadol's fluid solver aesthetic realised with cheap numpy.

View →
Sample of Latent Walk (Refik Anadol — Unsupervised / Machine Hallucinations)

Latent Walk (Refik Anadol — Unsupervised / Machine Hallucinations)

anadol_webgpu · latentWalk

Inspired by Refik Anadol Studio's Unsupervised — Machine Hallucinations — MoMA (2022): a chain of N points is sampled from a synthetic 64-dimensional latent space (a smoothed random walk) and projected to 2D via a fixed random matrix; the path is integrated step-by-step with spherical-linear interpolation between adjacent chain points, so the rendered trail visualises the path the 'latent space browser' would take through the embedding.

View →
Sample of Parallel Reduction (WebGPU workgroup-shared-memory tree reduction)

Parallel Reduction (WebGPU workgroup-shared-memory tree reduction)

anadol_webgpu · parallelReduction

The classic WebGPU parallel-reduction pattern: load N values into workgroup-shared memory, workgroupBarrier(), tree-reduce by stride (shared[lid.x] = op(shared[lid.x], shared[lid.x + stride]); stride /= 2), workgroupBarrier(), and emit per-workgroup partials from thread 0.

View →
Sample of Tile Blur (WebGPU separable Gaussian tile-shared-memory blur)

Tile Blur (WebGPU separable Gaussian tile-shared-memory blur)

anadol_webgpu · tileBlur

The WebGPU separable Gaussian blur primitive: a workgroup of TILE×TILE threads loads a tile+halo of the source image into workgroup-shared memory, workgroupBarrier(), convolves each pixel with a 1D Gaussian along the row, writes to a ping-pong texture; then a second compute pass applies the column-direction Gaussian on the intermediate texture.

View →

See all anadol_webgpu artworks →