Imagine
a scene in a game in which a rocket flies through the air, leaving a
smoke trail behind it. Suddenly the rocket explodes, sparks flying everywhere.
Out of the disintegrating rocket a creature is jettisoned towards you,
its body parts exploding and blood flying through the air, leaving messy
blood splatters on the camera lens. What do most of the elements in
this scene have in common?
Yes, most of these elements are violent. But in terms of technology,
most of the effects in a scene like this would benefit from a good particle
system. Smoke, sparks, and blood are routinely created in today's games
using particle systems.
To realize these effects, you need to build a particle system, and not
just a simple one. You need an advanced particle system, one that's
fast, flexible, and extensible. If you are new to particle systems,
I recommend you begin by reading Jeff Lander's article on particle systems
("The Ocean Spray in Your Face," Graphic Content, July 1998).
The difference between Lander's column and this article is that the
former describes the basics of particles, whereas I will demonstrate
how to build a more advanced system. With this article I will include
the full source code for an advanced particle system, and you can download
an application that demonstrates the system.
Performance
and Requirements
Advanced
particle systems can result in pretty large amounts of code, so it's
important to design your data structures well. Also be aware of the
fact that particle systems can decrease the frame rate significantly
if not constructed properly, and most performance hits are due to memory
management problems caused by the particle system.
When designing
a particle system, one of the first things to keep in mind is that particle
systems greatly increase the number of visible polygons per frame. Each
particle probably needs four vertices and two triangles. Thus, with
2,000 visible snowflake particles in a scene, we're adding 4,000 visible
triangles for the snow alone. And since most particles move, we can't
precalculate the vertex buffer, so the vertex buffers will probably
need to be changed every frame.
The trick
is to perform as few memory operations (allocations and releases) as
possible. Thus, if a particle dies after some period of time, don't
release it from memory. Instead, set a flag that marks it as dead or
respawn (reinitialize) it. Then when all particles are tagged as "dead,"
release the entire particle system (including all particles within this
system), or if it's a constant system, keep the particle system alive.
If you want to respawn the system or just add a new particle to a system,
you should automatically initialize the particle with its default settings/properties
set up according to the system to which it belongs.
For example,
let's say you have a smoke system. When you create or respawn a particle,
you might have to set its values as described in Table 1. (Of course,
the start color, energy, size, and velocity will be different for blood
than, say, smoke.) Note that the values also depend on the settings
of the system itself. If you set up wind for a smoke system so the smoke
blows to the left, the velocity for a new particle will differ from
a smoke system in which the smoke just rises unaffected by wind. If
you have a constant smoke system, and a smoke particle's energy becomes
0 (so you can't see it anymore), you'll want to respawn its settings
so it will be replaced at the bottom of the smoke system at full energy.
Some particle systems may need to have their particles rendered in different
ways. For example, you may want to have multiple blood systems, such
"blood squirt," "blood splat," "blood pool,"
and "blood splat on camera lens," each containing the appropriate
particles. "Blood squirt" would render blood squirts flying
through the air, and when these squirts collided with a wall, the "blood
splat" system would be called, creating messy blood splats on walls
and floors. A blood pool system would create pools of blood on the floor
after someone had been shot dead on the ground.
Each particle
system behaves in a unique manner. Blood splats are rendered differently
than smoke is displayed. Smoke particles always face the active camera,
whereas blood splats are mapped (and maybe clipped) onto the plane of
the polygon that the splat collides with.
When creating
a particle system, it is important to consider all of the possible parameters
that you may want to affect in the system at any time in the game, and
build that flexibility into your system. Consider a smoke system again.
We might want to change the wind direction vector so that a car moving
closely past a smoke system makes the smoke particles respond to the
wind generated by the passing car.
At this
point you may have realized that each of these systems (blood splat,
smoke, sparks, and so on) is very specific to certain tasks. But what
if we want to control the particles within a system in a way not supported
by the formulae in the system? To support that kind of flexibility,
we need to create a "manual" particle system as well, one
that allows us to update all particle attributes every frame.
The last
feature we might consider is the ability to link particle systems within
the hierarchy of our engine. Perhaps at some point we'll want to link
a smoke or glow particle system to a cigarette, which in turn is linked
to the head of a smoking character. If the character moves its head
or starts to walk, the position of the particle systems which are linked
to the cigarette should also be updated correctly.
So there you have some basic requirements for an advanced particle system.
In the next section, I'll show how to design a good data structure that
is capable of doing all the above-mentioned features.