Creating a particle fountain

In computer graphics, a particle system is a group of objects that are used to simulate a variety of fuzzy systems such as smoke, liquid spray, fire, explosions, or other similar phenomena. Each particle is considered to be a point object with a position, but no size. They could be rendered as point sprites (using the GL_POINTS primitive mode), or as camera aligned quads or triangles. Each particle has a lifetime: it is born, animates according to a set of rules, and then dies. The particle can then be resurrected and go through the entire process again. In this example, particles do not interact with other particles, but some systems, such as fluid simulations, would require a particle to interact. A common technique is to render the particle as a single, textured, camera-facing quad with transparency.

During the lifetime of a particle, it is animated according to a set of rules. These rules include the basic kinematic equations that define the movement of a particle that is subjected to constant acceleration (such as a gravitational field). In addition, we might take into account things such as wind, friction, or other factors. The particle may also change shape or transparency during its lifetime. Once the particle has reached a certain age (or position), it is considered to be dead and can be recycled and used again.

In this example, we'll implement a relatively simple particle system that has the look of a fountain of water. For simplicity, the particles in this example will not be recycled. Once they have reached the end of their lifetime, we'll draw them as fully transparent so that they are effectively invisible. This gives the fountain a finite lifetime, as if it only has a limited supply of material. In later recipes, we'll see some ways to improve this system by recycling particles.

The following image shows a sequence of images—several successive frames from the output of this simple particle system:

To animate the particles, we'll use the standard kinematics equation for objects under constant acceleration:

The previous equation describes the position of a particle at time t. P0 is the initial position, v0 is the initial velocity, and a is the acceleration.

We'll define the initial position of all particles to be the origin (0,0,0). The initial velocity will be determined randomly within a range of values. Each particle will be created at a slightly different time, so the time that we use in the previous equation will be relative to the start time for the particle.

Since the initial position is the same for all particles, we won't need to provide it as an input attribute to the shader. Instead, we'll just provide two other vertex attributes: the initial velocity and the start time (the particle's time of birth). Prior to the particle's birth time, we'll render it completely transparent. During its lifetime, the particle's position will be determined using the previous equation with a value for t that is relative to the particle's start time (Time - StartTime).

To render our particles, we'll use a technique called instancing, along with a simple trick to generate screen-aligned quads. With this technique, we don't actually need any vertex buffers for the quad itself! Instead, we'll just invoke the vertex shader six times for each particle in order to generate two triangles (a quad). In the vertex shader, we'll compute the positions of the vertices as offsets from the particle's position. If we do so in screen space, we can easily create a screen-aligned quad. We'll need to provide input attributes that include the particle's initial velocity and birth time.

This technique makes use of the vertex shader to do all the work of animating the particles. We gain a great deal of efficiency over computing the positions on the CPU. The GPU can execute the vertex shader in parallel, and process several particles at once.

The core of this technique involves the use of the glDrawArraysInstanced function. This function is similar to the familiar glDrawArrays, but instead of just drawing once, it does so repeatedly. Where glDrawArrays would just walk through the vertex buffers once, glDrawArraysInstanced will do so a specified number of times. In addition, while walking through the buffers, we can also configure when to move to the next element in the buffer (how quickly to walk). Normally, we move to the next element with each invocation of the vertex shader (essentially once per vertex). However, with instanced drawing, we don't always want that. We might want several (sometimes hundreds) of invocations to get the same input value.

For example, each particle in our particle system has six vertices (two triangles). For each of these six vertices, we want the same velocity, (particle) position, and other per-particle parameters. The key to this is the glVertexAttribDivisor function, which makes it possible to specify how often the index is advanced for a given attribute. A divisor value of 0 indicates that the index is advanced once per vertex. A value that is greater than zero (n > 0) indicates that the index advances once after n instances of the shape are drawn.

For example, suppose we have two attributes (A and B) and we set the divisor to zero for attribute A and one for attribute B. Then, we execute the following:

glDrawArraysInstanced( GL_TRIANGLES, 0, 3, 3);

The first three arguments are the same as glDrawArrays. The fourth argument is the number of instances. So, this call would draw three instances of a triangle primitive (for a total of nine vertices), and the values of attributes A and B would be taken from the corresponding buffers at the indices shown here:

Attribute Vertex indices
A 0,1,2,0,1,2,0,1,2
B 0,0,0,1,1,1,2,2,2

 

Note how setting the vertex attribute divisor for B to one causes the index to advance once per instance, rather than once per vertex. In fact, in this recipe, we'll set the divisor for all of our attributes to one! We'll compute the positions of each vertex of a particle as offsets from the particle's position.

You might be wondering how it will be possible to distinguish one vertex from another in the vertex shader in order to determine the needed offsets if the attribute values are the same for all vertices of the particle. The solution comes via the built-in variable gl_VertexID. More on this follows.

We'll render each particle as a textured-point quad of two triangles. We'll increase the transparency of the particle linearly with the age of the particle, to make the particle appear to fade out as it animates.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.84.32