Gamasutra is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Gamasutra: The Art & Business of Making Gamesspacer
Building an Advanced Particle System
View All     RSS
July 9, 2020
arrowPress Releases
July 9, 2020
Games Press
View All     RSS







If you enjoy reading this site, you might also want to check out these UBM Tech sites:


 

Building an Advanced Particle System


June 23, 2000 Article Start Previous Page 2 of 3 Next
 

Creating the Data Structure

Now that we know what features we need, it's time to design our classes. Figure 1 shows an overview of the system we're going to build. Notice that there is a particle
manager, which I will explain more about in a moment.


Table 1. Particle attributes
Data type
Name
Description
Vector3 position The position of the particle in world-space
Vector3 oldPos The previous position of the particle, useful in some systems
Vector3 velocity The velocity vector (position += velocity)
dword color The color of the particle (its vertex colors)
int energy The energy of the particle
float size The size of the particle

Let's use a bottom-up approach to design our classes, beginning with the particle class.

Figure 1. A global overview of the sytem we are going to build

The particle class. If you have built a particle system before, you probably know the types of attributes a particle must have. Table 1 lists of some common attributes.

Note that the previous position of a particle can also be useful in some systems. For example, you might want to stretch a particle between its previous and current positions. Sparks are a good example of particles that benefit from this feature. You can see some spark effects I've created in Figure 2.

Figure 2. Some spark effects you can create using a particle system such as theone discussed in this article. Note that all particles perform accurate collision detectino and response in these two screenshots.

The color and energy attributes can be used to create some interesting effects as well. In a previous particle system I created, I used color within the smoke system, which let me dynamically light the smoke particles using lights within the scene.
Energy value is very important as well. Energy is analogous to the age of the particle - you can use this to determine whether a particle has died. And because the color or intensity of some particles (such as sparks) changes over time, you may want to link it to the alpha channel of the vertex colors.
I strongly recommend that you leave the constructor of your particle class empty, because you don't want to use default values at construction time, simply because these values will be different for most particle systems.

The particle system class. This class is the heart of the system. Updating the particle attributes and setting up the shape of the particles takes place inside this class. My current particle system class uses the node base class of my 3D engine, which contains data such as a position vector, a rotation quaternion, and scale values. Because I inherit all members of this node class, I can link my particle systems within the hierarchy of the engine, allowing the engine to affect the position of the particle system as discussed in the above cigarette example. If your engine does not have hierarchy support, or if you are building a stand-alone particle system, this is not needed. Table 2 lists the attributes which you need to have in the particle system base class.

Table 2. Particle system base class attributes
Data type
Name
Description
Texture *texture A pointer to a texture, which all particles will use. For performance reasons, we only use one texture for each individual particle system; all particles within the specific system will have the same texture assigned.
BlendMode blendMode The blend mode you want to use for the particles. Smoke will probably have a different blend mode from blood - that's the reason you also store the blend mode for each particle system.
int systemType A unique ID, which represents the type of system (smoke or sparks, for example). The systemType identifier is also required, since you may want to check for a specific type of particle system within the collection of all systems. For example, to remove all smoke systems, you need to know whether a given system is a smoke system or not.
Array Particle particles The collection of particles within this system. This may also be a linked list instead of an array.
Array PShape shapes A collection of shapes, describing the shapes of the particles. The shape descriptions of the particles usually consist of four positions in 3D camera-space. These four positions are used to draw the two triangles for our particle. As you can see in Table 1, a particle is only stored as a single position, but it requires four positions (vertices) to draw the texture-mapped shape of the particle.
int nrAlive Number of particles in the system which are still alive. If this value is zero, it means all particles are dead and the system can be removed.
BoundingBox3 boundingBox boundingBox The 3D axis-aligned bounding box (AABB), used for visibility determination. We can use this for frustum, portal, and anti-portal checks.

Here's how to calculate the four positions of a normal (not stretched) particle that always faces the active camera. First, transform your particle world-space position into camera-space (multiply the world-space position and your active camera matrix) using the size attribute of the particle to calculate the four vertices.
The four vertices, which form the shape, are what we use to render the particle, though a particle has only one position, xyz. In order to render a particle (such as a spark), we need to set up a shape (created from four vertices). Two triangles are then rendered between these four points. Imagine a non-stretched particle always facing the camera in front of you, as seen in Figure 3.

Figure 3. Setting up a shape to render a particle

In our case, the particle is always facing the active camera, so this means we can simply add and subtract values from the x and y values of the particle position in camera-space. In other words, leave the z value as it is and pretend you are working only in 2D. You can see an example of this calculation in Listing 1.

Listing 1. Calculating the shape of the particle facing the camera.

void ParticleSystem::SetupShape(int nr)
{
&nbspassert(nr < shapes.Length()); // make sure we don't &nbsptry to shape anything we
// don't have

// calculate cameraspace position
&nbspVector3 csPos = gCamera->GetViewMatrix() * &nbspparticles[nr].position;

// set up shape vertex positions
&nbspshapes[nr].vertex[0]
= csPos + Vector3(-particles[nr].size, particles[nr].size, 0);
&nbspshapes[nr].vertex[1]
= csPos + Vector3( particles[nr].size, particles[nr].size, 0);
&nbspshapes[nr].vertex[2]
= csPos + Vector3( particles[nr].size, -particles[nr].size, 0);
&nbspshapes[nr].vertex[3]
= csPos + Vector3(-particles[nr].size, -particles[nr].size, 0);
}

The functions. Now that we know what attributes are needed in the particle system base class, we can start thinking about what functions are needed. Since this is the base class, most functions are declared as virtual functions. Each type of particle system updates particle attributes in a different way, so we need to have a virtual update function. This update function performs the following tasks:

  • Updates all particle positions and other attributes.
  • Updates the bounding box if we can't precalculate it.
  • Counts the number of alive particles. It returns FALSE if there are no alive particles, and returns TRUE if particles are still alive. The return value can be used to determine whether a system is ready to be deleted or not.

Now our base class has the ability to update the particles, and we are ready to set up the shapes which can be constructed using the new (and perhaps previous) position. This function, SetupShape, needs to be virtual, because some particle system types will need to have their particles stretched and some won't. You can see an example of this function in Listing 1.

To add a particle to a given system, or to respawn it, it's useful to have a function that takes care of this. Again, it should be another virtual function, which is declared like this:

virtual void SetParticleDefaults( Particle &p );

As I explained above, this function initializes the attributes for a given particle. But what if you want to alter the speed of the smoke or change the wind direction that affects all of your smoke systems? This brings us to the next subject: the particle system's constructor. Many particle systems will need their own unique constructors, forcing us to create a virtual constructor and destructor within the base class. In the constructor of the base class, you should enter the following information:

  • The number of particles you initially want to have in this particle system.
  • The position of the particle system itself.
  • The blend mode you want to use for this system.
  • The texture or texture file name you want this system to use.
  • The system type (its ID).

In my engine, the constructor in the particle system base class looks like this:

virtual ParticleSystem(int nr, rcVector3 centerPos, BlendMode
blend=Blend_AddAlpha, rcString file name="Effects/Particles/
green_particle", ParticleSystemType type=PS_Manual);

So where do various settings, such as the wind direction for the smoke system, get addressed? You can either add settings specific to the system type (such as wind direction) into the constructor, or you can create a struct called InitInfo inside each class, which contains all of the appropriate settings. If you use the latter method, make sure to add a new parameter in the constructor, which is a pointer to the new struct. If the pointer is NULL, the default settings are used.

As you can imagine, the first solution can result in constructors with many parameters, and that's not fun to work with as programmer. ("Parameter number 14…hmmm. What does that value represent again?") That's the main reason I don't use the first method. It's much easier to use the second method, and we can create a function in each particle system class to initialize its struct with default settings. An example of this code and a demo application can be found on the Game Developer web site (http://www.gdmag.com) or my own site at http://www.mysticgd.com.


Article Start Previous Page 2 of 3 Next

Related Jobs

Remedy Entertainment
Remedy Entertainment — Helsinki, Finland
[07.08.20]

Technical Director
Wooga GmbH
Wooga GmbH — Berlin, Germany
[07.08.20]

Unity Game Engineer
Disbelief
Disbelief — Cambridge, Massachusetts, United States
[07.07.20]

Programmer
tinyBuild
tinyBuild — Hilversum, Netherlands
[07.07.20]

Unreal Engine 4 Senior Developer





Loading Comments

loader image