Particle Systems

How do you animate fire? Not with a mesh — flame has no surface to model. It flickers, splits, curls and dies, and it has no fixed shape from one frame to the next. The same is true of smoke, sparks, rain, dust, a waterfall's spray, or the shimmer of movie magic. These fuzzy phenomena defeated the polygon-and-mesh pipeline entirely — until, in 1982, Bill Reeves needed to set a dead planet ablaze for the "Genesis effect" in Star Trek II: The Wrath of Khan and reached for a completely different idea: don't model the shape, simulate a crowd of thousands of tiny independent points and let the look emerge.

That idea — the particle system — is still the workhorse for every fuzzy effect in games and film. This page teaches what a particle is, how an emitter births them, how each one is pushed around by forces and then quietly dies, and how millions of them get drawn as glowing camera-facing sprites. It builds on computer animation.

A particle is almost nothing

The whole trick rests on making each particle as cheap as possible, because you want a great many of them. A particle is not a mesh, not an object — it is a lightweight bundle of numbers: a point that carries a little state.

In code a particle is just a struct, a dozen numbers wide:

interface Particle { pos: Vec3; // position vel: Vec3; // velocity mass: number; // for F = m·a age: number; // seconds lived so far life: number; // seconds allowed to live size: number; color: Vec4; // r, g, b, opacity }

There is no geometry, no vertices, no faces. One particle on its own is invisible-dull — a single dot. The magic is entirely in the numbers: put ten thousand of these together, each following the same simple rules with slightly different starting conditions, and the crowd behaves like fire.

Emitters: where particles are born

Particles don't exist forever, and a fire needs a steady supply of fresh ones to replace those that burn out. That supply comes from an emitter — a source that spawns new particles at some spawn rate (say 500 per second), handing each newborn its initial state. Emitters come in shapes:

EmitterSpawns particles…Good for
Pointall from one spota spark shower, a fountain jet
Area / discacross a 2-D patchrain over a region, a campfire base
Volumethroughout a 3-D box or spheredust in a room, a fog bank
Mesh surfaceover an object's skina body catching fire, steam off a kettle

The single most important thing an emitter does is randomise those initial conditions. If every particle launched with the identical velocity, they would all move in lockstep and read as one rigid clump. Instead the emitter jitters each birth: a random direction inside a cone, a random speed in a range, a random lifetime, a small colour variation. That scatter is exactly what gives the system its organic, stochastic look.

function spawn(): Particle { const speed = 4 + Math.random() * 2; // 4–6 units/s const angle = (Math.random() - 0.5) * 0.4; // narrow cone return { pos: { x: 0, y: 0, z: 0 }, // point emitter vel: { x: Math.sin(angle) * speed, y: Math.cos(angle) * speed, z: 0 }, mass: 1, age: 0, life: 1.5 + Math.random(), // 1.5–2.5 s size: 0.2, color: { x: 1, y: 0.6, z: 0.1, w: 1 }, // warm, fully opaque }; }

The update step: integrate, age, die

Every frame (a time step \Delta t), the system walks its whole list of live particles and updates each one independently. The update is the same three-part loop for all of them:

The forces are what give each effect its character. A short menu of the common ones:

function update(p: Particle, dt: number): boolean { const g = { x: 0, y: -9.8, z: 0 }; // gravity const drag = -0.1; // simple linear drag // total acceleration = gravity + drag/mass const ax = g.x + (drag * p.vel.x) / p.mass; const ay = g.y + (drag * p.vel.y) / p.mass; p.vel.x += ax * dt; p.vel.y += ay * dt; // integrate velocity p.pos.x += p.vel.x * dt; p.pos.y += p.vel.y * dt; // integrate position p.age += dt; // grow older p.color.w = 1 - p.age / p.life; // fade opacity toward 0 return p.age < p.life; // false → kill it }

Worked example: one particle, one step

Let's do a single step by hand. Take a spark just launched straight up: \mathbf{v} = (0,\,6)\ \text{m/s}, \mathbf{p} = (0,\,0), mass m = 1, lifetime L = 2\ \text{s}, currently age 0.9\ \text{s}. We take one frame at \Delta t = 1/60 \approx 0.0167\ \text{s} under gravity \mathbf{g} = (0,\,-9.8) (ignore drag for clarity).

1. Force → acceleration. The only force is gravity, so \mathbf{a} = \mathbf{g} = (0,\,-9.8)\ \text{m/s}^2.

2. Integrate velocity. \mathbf{v} \leftarrow \mathbf{v} + \mathbf{a}\,\Delta t = (0,\,6) + (0,\,-9.8)(0.0167) = (0,\,5.836)\ \text{m/s}. The upward speed has bled off a touch — gravity is winning.

3. Integrate position. Using the new velocity, \mathbf{p} \leftarrow \mathbf{p} + \mathbf{v}\,\Delta t = (0,\,0) + (0,\,5.836)(0.0167) = (0,\,0.0973)\ \text{m}. The spark has climbed about 9.7\ \text{cm}.

4. Age and fade. New age is 0.9 + 0.0167 = 0.9167\ \text{s}. The opacity is 1 - \text{age}/L = 1 - 0.9167/2 \approx 0.542 — a bit past half-faded, so this spark is dimming out. It is still younger than its lifetime (0.9167 < 2), so it survives to the next frame. Run those four lines on ten thousand particles and you have a fountain of fading sparks.

A fountain you can steer

Below is a tiny fountain of nine particles launched from a point emitter in a spread of directions, each an independent projectile under gravity. Drag the time slider (or press Play) to march the simulation forward and watch the arcs; drag launch speed and gravity to reshape the spray. Fast launch + weak gravity throws a tall, wide plume; slow launch + strong gravity gives a squat dribble. Every dot obeys the very same two integration lines — the variety comes entirely from their differing launch directions.

In 1982 the Pixar-to-be team at Lucasfilm had to show a barren planet erupting into a spreading wall of fire that races across its surface. There is no object there to model — a wall of fire is pure turbulent chaos. Bill Reeves's insight, written up in his 1983 paper "Particle Systems — A Technique for Modeling a Class of Fuzzy Objects," was to represent the fire as thousands of glowing particles thrown outward from expanding rings of emitters, each living a second or two, changing colour from white-hot to red to dark as it aged, and dying. No single particle looks like fire; the emergent behaviour of the whole cloud does. It was one of the first times computer graphics animated something with no surface at all, and the technique it launched now renders every explosion, waterfall and dust cloud you see on screen.

Rendering: billboards, blending, and life

A particle is a mathematical point, but you can't draw a point — you draw a small sprite (a textured quad: a soft blob, a smoke puff, a spark streak) at the particle's position. To avoid the illusion breaking when the camera moves around, the quad is a billboard: it is rotated every frame to always face the camera, so you never catch it edge-on and see it vanish.

Two rendering choices sell the effect:

Onto the GPU: millions at once

Because every particle updates independently with the identical little program, particle systems are almost perfectly parallel — which is exactly what a GPU eats for breakfast. A modern GPU particle system keeps the entire particle buffer in video memory and runs the spawn / integrate / age / cull loop in a compute (or vertex) shader, thousands of particles per shader invocation, all in flight at once. The particles never make a round trip back to the CPU. This is what lifts the budget from a few thousand CPU particles to millions: dense sandstorms, volumetric smoke, sparks by the fistful, all simulated and rendered on the graphics card in real time.

The reason particle systems are so cheap is a deliberate cheat: particles are almost always independent. Each one integrates against the global force field (gravity, wind, drag) but does not see the other particles — no collisions, no pushing, no piling up. The cost of the update is then simply linear, O(n), so you can afford millions.

This is exactly why cheap fire, smoke, sparks and rain look right: those things really are clouds of near-independent bits that pass through one another. But it is also why a naive particle system makes a terrible pile of sand or heap of gravel — a pile only exists because grains collide and rest on each other, and independent particles just fall straight through the floor into an overlapping mush. The moment you need particles to interact — sand, cloth, fluid with pressure, stacking — you must add inter-particle forces (collisions, SPH pressure, neighbour queries), and the cost jumps toward O(n^2) (or O(n\log n) with a spatial grid). Add that expense only when the effect genuinely demands it — most fuzzy phenomena don't.

The big picture

A particle system is a beautiful example of emergence: no particle knows it is part of a fire, yet many simple points running one three-line update produce something that convincingly is one. The recipe never changes — an emitter randomises births, a per-particle update integrates forces and ages each one, dead particles are culled, and survivors are drawn as camera-facing sprites whose size and colour ride their age. Swap the forces and the sprite and the same machine gives you sparks, smoke, rain, dust, or magic.