32. OpenGL Particle Engine (Version 2.0)
Introduction
Particle systems are an essential tool in graphics programming, used to simulate effects like fire, smoke, explosions, or even magic spells in OpenGL. They consist of numerous small, independently moving objects (particles) that together create complex visual effects.
In this tutorial, we’ll build a simple particle engine that generates, updates, and renders particles in real-time. This example uses textured quads to represent particles and allows customization of their movement, color, size, and more. By understanding the basics here, you’ll be able to expand the system for more advanced effects.
Understanding Particle Properties
Each particle is defined by a set of properties that govern its position, movement, appearance, and behavior. In our implementation, we use a structure (PARTICLES) to encapsulate these properties:
typedef struct { double Xpos, Ypos, Zpos; // Position coordinates double Xmov, Zmov; // Movement speed along X and Z axes double Red, Green, Blue; // Color components double Direction; // Rotation angle double Acceleration; // Upward acceleration double Deceleration; // Downward deceleration double Scalez; // Scale factor for size } PARTICLES;
– Position (Xpos, Ypos, Zpos): Determines where the particle is in 3D space.
– Movement (Xmov, Zmov): Specifies how fast the particle moves horizontally or along the depth axis.
– Color (Red, Green, Blue): Defines the particle’s color using RGB values.
– Rotation (Direction): Adds a spinning effect to the particle.
– Acceleration and Deceleration: Controls the particle’s upward and downward motion over time.
– Scale (Scalez): Changes the size of the particle, allowing for variety in appearance.
Initializing Particles
Before rendering, we initialize the particles with random properties. This adds variety, making the system look dynamic and natural. The glCreateParticles function does this:
void glCreateParticles(void) { for (int i = 0; i < ParticleCount; i++) { Particle[i].Xpos = 0; // Start at the origin Particle[i].Ypos = -5; // Start below the visible area Particle[i].Zpos = -5; // Start away from the viewer Particle[i].Xmov = ((rand() % 10) - 5) * 0.01; // Random X movement Particle[i].Zmov = ((rand() % 10) - 5) * 0.01; // Random Z movement Particle[i].Red = 1; // Full red Particle[i].Green = 1; // Full green Particle[i].Blue = 1; // Full blue Particle[i].Scalez = 0.25; // Smaller size Particle[i].Direction = 0; // No initial rotation Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; // Random upward speed Particle[i].Deceleration = 0.0025; // Initial deceleration } }
Code Explanation:
- rand(): Generates random values for properties like movement, acceleration, and color. This ensures that each particle behaves uniquely.
- Position Initialization: All particles start at the same origin point and spread outward due to their random movement values (Xmov, Zmov).
- Scaling and Rotation: Setting all particles to the same scale and rotation simplifies the initial setup. These can be varied dynamically later.
Animating Particles
To create the illusion of movement, we update each particle's properties every frame. This is handled by the glUpdateParticles function:
void glUpdateParticles(void) { for (int i = 0; i < ParticleCount; i++) { // Update position Particle[i].Ypos += Particle[i].Acceleration - Particle[i].Deceleration; Particle[i].Xpos += Particle[i].Xmov; Particle[i].Zpos += Particle[i].Zmov; // Increase deceleration for downward motion Particle[i].Deceleration += 0.0025; // Update rotation Particle[i].Direction += (rand() % 5) * 0.1; // Reset particle if it falls below starting position if (Particle[i].Ypos < -5) { Particle[i].Ypos = -5; Particle[i].Xpos = 0; Particle[i].Zpos = 0; Particle[i].Deceleration = 0.0025; Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; } } }
Code Explanation:
- Particle[i].Ypos: Adjusts the vertical position based on acceleration (upward) and deceleration (downward). This creates a natural rise-and-fall motion.
- Particle[i].Xpos, Particle[i].Zpos: Moves the particle in the X and Z directions, creating lateral motion.
- Particle[i].Deceleration: Gradually increases, causing the particle to fall faster over time, mimicking gravity.
- Particle Reset: When a particle falls out of view, its properties are reset to simulate a continuous system.
Rendering Particles
Particles are drawn as textured quads. Each quad's transformation (position, rotation, and scale) is applied individually to maintain its unique behavior.
void glDrawParticles(void) { for (int i = 0; i < ParticleCount; i++) { glPushMatrix(); // Apply particle transformations glTranslatef(Particle[i].Xpos, Particle[i].Ypos, Particle[i].Zpos); glRotatef(Particle[i].Direction, 0, 0, 1); glScalef(Particle[i].Scalez, Particle[i].Scalez, Particle[i].Scalez); // Draw particle quad glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_DST_COLOR, GL_ZERO); glBindTexture(GL_TEXTURE_2D, texture[0]); glBegin(GL_QUADS); glTexCoord2d(0, 0); glVertex3f(-1, -1, 0); glTexCoord2d(1, 0); glVertex3f(1, -1, 0); glTexCoord2d(1, 1); glVertex3f(1, 1, 0); glTexCoord2d(0, 1); glVertex3f(-1, 1, 0); glEnd(); glEnable(GL_DEPTH_TEST); glPopMatrix(); } }
Code Explanation:
- Transformations: Each particle's position, rotation, and scale are applied using glTranslatef, glRotatef, and glScalef. These ensure that particles are drawn in their correct location and size.
- Texturing: Textures are applied to the quads to give the particles a realistic look.
- Blending: The blending functions (glBlendFunc) create transparency effects, essential for natural-looking particles like smoke or fire.
Tutorial Code
Here’s the full program for creating, updating, and rendering particles:
#include#include #include // For rand() // Number of particles in the system const int ParticleCount = 500; // Structure to hold individual particle properties typedef struct { double Xpos, Ypos, Zpos; // Position double Xmov, Zmov; // Movement double Red, Green, Blue; // Color double Direction; // Rotation angle double Acceleration; // Upward speed double Deceleration; // Downward speed double Scalez; // Scale } PARTICLES; // Array to store all particles PARTICLES Particle[ParticleCount]; // Texture array for particle rendering GLfloat texture[10]; // Function prototypes void glCreateParticles(void); void glUpdateParticles(void); void glDrawParticles(void); // Initialize the particle system void glCreateParticles(void) { for (int i = 0; i < ParticleCount; i++) { Particle[i].Xpos = 0; // Start at the origin Particle[i].Ypos = -5; // Start below the visible area Particle[i].Zpos = -5; // Start away from the viewer Particle[i].Xmov = ((rand() % 10) - 5) * 0.01; // Random X movement Particle[i].Zmov = ((rand() % 10) - 5) * 0.01; // Random Z movement Particle[i].Red = 1; // Full red Particle[i].Green = 1; // Full green Particle[i].Blue = 1; // Full blue Particle[i].Scalez = 0.25; // Smaller size Particle[i].Direction = 0; // No initial rotation Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; // Random upward speed Particle[i].Deceleration = 0.0025; // Initial deceleration } } // Update particle properties for animation void glUpdateParticles(void) { for (int i = 0; i < ParticleCount; i++) { // Update position Particle[i].Ypos += Particle[i].Acceleration - Particle[i].Deceleration; Particle[i].Xpos += Particle[i].Xmov; Particle[i].Zpos += Particle[i].Zmov; // Increase deceleration for downward motion Particle[i].Deceleration += 0.0025; // Update rotation Particle[i].Direction += (rand() % 5) * 0.1; // Reset particle if it falls below starting position if (Particle[i].Ypos < -5) { Particle[i].Ypos = -5; Particle[i].Xpos = 0; Particle[i].Zpos = 0; Particle[i].Deceleration = 0.0025; Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; } } } // Render all particles void glDrawParticles(void) { for (int i = 0; i < ParticleCount; i++) { glPushMatrix(); // Save the current transformation state // Apply particle transformations glTranslatef(Particle[i].Xpos, Particle[i].Ypos, Particle[i].Zpos); glRotatef(Particle[i].Direction, 0, 0, 1); glScalef(Particle[i].Scalez, Particle[i].Scalez, Particle[i].Scalez); // Draw particle quad with texture glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_DST_COLOR, GL_ZERO); glBindTexture(GL_TEXTURE_2D, texture[0]); glBegin(GL_QUADS); glTexCoord2d(0, 0); glVertex3f(-1, -1, 0); glTexCoord2d(1, 0); glVertex3f(1, -1, 0); glTexCoord2d(1, 1); glVertex3f(1, 1, 0); glTexCoord2d(0, 1); glVertex3f(-1, 1, 0); glEnd(); // Enable depth testing and restore transformations glEnable(GL_DEPTH_TEST); glPopMatrix(); } } // Display function void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glTranslatef(0, 0, -10); // Move camera back glUpdateParticles(); // Update particle positions glDrawParticles(); // Render particles glutSwapBuffers(); // Swap buffers for smooth animation } // Initialize OpenGL settings void init(void) { glEnable(GL_TEXTURE_2D); // Enable 2D textures glEnable(GL_DEPTH_TEST); // Enable depth testing glCreateParticles(); // Initialize particles // Load textures here (use your texture loading function)... } // Main entry point int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("Particle Engine"); init(); glutDisplayFunc(display); glutIdleFunc(display); // Continuously update glutMainLoop(); return 0; }
If you have any questions, feel free to email me at swiftless@gmail.com. Happy coding!
I am trying to change this to act as a water fountain and I’m having a hard time making the particle have some sort of parabola behavior. Like it’ s kind of random at the moment.
I’m affecting this to limit the height and the width of the particle but it’s not working.
if ((Particle[i].Ypos 2) && (Particle[i].Xpos 1) && (Particle[i].Zpos 2)) //remake itself y-something instead of -5, create y and x and z bounds with ||
Someone please help please
Hi,
Just busy working through this tutorial and was just curious if it is intentional that you loop through your particles starting with i = 1 rather than i = 0?
I’m pretty new to openGL. I’ve compiled and run your code, but i’m not able to colour the particles separately. Either all are red, or all are blur and so forth. Please suggest a way to colour them separately. Thank you.
I’m pretty new to openGL. I’ve compiled and run yo the above code but the particles are all black and white. They don’t seem to get coloured. Could you please help me on this.
why the raw file cannot be loaded?tq
Thanks for writing these tutorials!!! I have worked my way through almost all of them now.
A question I have about this tutorial is that you mentioned that it runs at 80 frames per second but when I run this on my computer it is all a blur.
I added the line (and the library unistd.h):
usleep(10000 – glutGet(GLUT_ELAPSED_TIME)/1000) % 10000);
right before glutSwapBuffers();
I think this gets me to 100 frames per second, is there a better way to do this?
Thanks.
Hey Mark,
It will run at whatever speed your system will support. The best way to keep it all at a common speed is to switch to time based movement, so that everything you do is based on the current elapsed time. There is plenty of information on the Internet about this, but feel free to get back to me if you have any issues.
Cheers,
Swiftless
I have to say, big fan of your tutorials. This one in particular really made my day.
Best tutorials since Nehe. (I switched over to yours because you don’t use glaux)
Thanks.
Hello
Good tutorial.
Why you cannot change the color to 0.1, 0.2, 0.3… ?
it worked for me.
Aslam o alkum,, can you give me full tutorial of particle engine in opengl … because I am new in using opengl .. plz help me in this matter
Dude. Ive learnt alot from this site. Its sick. But, my particle system runs and no errors are coming up but the display is blank, just the black background. The warning messages ‘Cannot find or open the PDB file’, do come up but i heard that does’nt effect the program and i have no idea and don’t want to know about symbol servers at the minute. Can you help me? Seriously sick tutorials every other one, and I’ve nearly done all of them are sweet. Particle systems have a history of beating me, desperate to figure them out. Many Thanks.
Hey Sean,
That PDB message won’t effect the final output of the application. I’m not quite sure what could be causing the problem. My first guess would be, is it loading the textures correctly?
Cheers,
Swiftless
Oh, sorry 🙂 I think about cstdlib header file.
Hello! I think You should add line:
#include
for rand().