The day has finally come. I have completed the final chapter in OpenGL 4.0 Shading Language Cookbook which explores mesh deformation and particle systems. It was definitely an interesting challenge and has given me the want to create a flexible particle system incorporating all of the things I have learned and more. As always, these examples were developed in my very own game engine (Derydoca Engine).
If you want to follow along, the commit hash of the GitHub repository is 0ee6024b54cce60a7850b03f65b499833101200a.
This exercise takes a mesh and distorts it in the Y direction to move the vertices in a sin wave. I have chosen to distort a simple (but high density) quad which gives the impression of a waving flag. This works simply by having the engine tell the shader the current time since the program started. The engine also defines certain variables such as the wave’s period and amplitude. With that information, the vertex shader modifies the Y coordinate of the current vertex based on the position on the X-Z plane using a sin wave function. After that, a new normal vector is computed to compensate for the distortion in the mesh. From there, the fragment does whatever it needs to do. I could have extended it to take a texture to make it really appear like a flag flapping in the wind.
This particle fountain is a “one-shot” particle system that sprays particles in the air in a cone-like pattern with a constant downward force simulating gravity. I chose to use the same particle graphic I used in chapter 6’s point sprite exercise.
This particle system utilizes OpenGL’s glPointSize function to define a constant size for each particle. This allows for the particle system to be written in fewer lines of code, but there is the unfortunate effect where you start seeing the separation of each individual particle when you get closer to the emitter. In the real world, I would opt for using the geometry shader to create properly sized quads.
In order to have each particle have it’s own properties such as velocity and time they are supposed to start, we have to define buffers to store the information in and supply it to the shader. I have implemented it via separate buffers for each property. For instance, if we need to know the start time and start velocity of the fifth particle, you would look at startTimes and startVelocities respectively. You can also choose to interleave your data if you are so inclined to have a singular buffer to pass around, but I prefer the idea of separate buffers to keep the code cleaner and more modular.
The vertex shader then calculates the positions of each particle based on the world’s current time, the constant downward force, the particles start time, and the particle’s start velocity. Since the position is calculated on those variables alone, there is no way for complex movement of the particles to happen. For instance, particles would not be able to bounce off of the floor or be recycled. The next example solves this by taking this particle effect to the next level utilizing transform feedback buffers.
Continuous Particle Fountain
As mentioned before, this particle system builds on the previous one. This particle fountain continuously spouts out particles and never stops. It can do this thanks to the use of transform feedback buffers. Transform feedback buffers allow the shader to update the values in a buffer for complex behavior. In this example, when a particle has exceeded it’s maximum lifetime, the position and velocity is reset allowing us to recycle “dead” particles.
In order to have a transform feedback buffer, you need to define two buffers of equal length and submit them both up to the shader. The reason you need two for each variable is because one buffer is read from while the other buffer is written to. Every render cycle, you will swap which buffer is read-only and which one is write-only. This gives the illusion that you are working on a singular buffer that can be written to.
Instanced Mesh Particles
Next we have a particle system that renders a mesh at each point instead of a billboard sprite. I have opted to render a spiky mesh object for some reason. I thought it was more visually interesting when compared to spheres and cubes. This particle fountain is much like the first one in that we are not using transform feedback buffers. We could easily add that to this particle system, but in order to keep the code more targeted at illustrating this point, I decided not to.
The big difference here is that before I submit the draw call to the GPU, I call glBindVertexArray and bind the mesh I want to render at each point. Then, instead of calling glDrawArrays to draw the object, I call glDrawElementsInstanced and pass in information about the mesh and number of particles to render. The shaders from there on are very similar to the ones for our first particle fountain exercise. The big difference is in the fragment shader. In this case I chose to implement ADS rendering for my model. You can use whatever fragment shader you want here to achieve many different effects.
Fire Particle System
For this and the next exercise, i opted to extend the Continuous Particle Fountain object to accommodate the needs of these particle systems. Ultimately to achieve the fire effect in the book I extended the particle system to define different shapes for the emitter. Originally we were emitting from a point, now we needed to emit from a line in space. Instead of having a type of line emitter, I chose to implement a cuboid emitter where in the level file I zeroed out the Y and Z dimensions to achieve a line. I also exposed the constant acceleration value to the serialized level data and gave it a slight upward acceleration.
Combining all of that with a fire texture for the particles ended up getting me this amazing and roaring fire! Well… I admit it isn’t that impressive visually, but I’m just implementing what the book told me to. Luckily the next one looks a bit better.
Smoke Particle System
Again, I extended the continuous particle fountain to accommodate the requirements for this particle system. I utilized the same cuboid spawning and acceleration values in the previous fire particle system. From there, I just needed to give the system the ability to allow the particles to grow in size as they near the end of their lifetime. To allow the point sprites to grow in size, we need to enable defining the size in the vertex shader. This is done by calling glEnable(GL_PROGRAM_POINT_SIZE). Then, inside of the vertex shader code, you need to assign gl_PointSize to the size that you want to render that particular point. In my case it was calling mix(MinParticleSize, MaxParticleSize, ParticleAgePercent). After making those changes and swapping out the texture to a smoke texture, the particle system is done!
Where To From Here?
This book has been very enlightening. Now that I have gone through all of the examples, I have some tasks ahead of myself. For one, I want to clean up the code a bit, look for memory leaks, etc. Secondly, I would like to extend the engine so that I can load in an external library of game component objects. Once I do that I can pull out all of these examples into a separate Git repository which will allow me to keep the engine code clean and create other projects in the future without cluttering it up. Also, going through all of these examples have given me some ideas for shaders and effects that I want to play with once I have cleaned up the project.
Let me know in the comments below what you thought of the coverage of OpenGL 4.0 Shading Language Cookbook. Did you want to see some code highlighted in these articles? More videos of the shaders in action (like in this example? Did you just like being around for the ride and seeing pretty pictures as we went?