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:

  1. typedef struct {  
  2.     double Xpos, Ypos, Zpos; // Position coordinates  
  3.     double Xmov, Zmov;       // Movement speed along X and Z axes  
  4.     double Red, Green, Blue; // Color components  
  5.     double Direction;        // Rotation angle  
  6.     double Acceleration;     // Upward acceleration  
  7.     double Deceleration;     // Downward deceleration  
  8.     double Scalez;           // Scale factor for size  
  9. } 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:

  1. void glCreateParticles(void) {  
  2.     for (int i = 0; i < ParticleCount; i++) {  
  3.         Particle[i].Xpos = 0;   // Start at the origin  
  4.         Particle[i].Ypos = -5;  // Start below the visible area  
  5.         Particle[i].Zpos = -5;  // Start away from the viewer  
  6.         Particle[i].Xmov = ((rand() % 10) - 5) * 0.01; // Random X movement  
  7.         Particle[i].Zmov = ((rand() % 10) - 5) * 0.01; // Random Z movement  
  8.         Particle[i].Red = 1;    // Full red  
  9.         Particle[i].Green = 1;  // Full green  
  10.         Particle[i].Blue = 1;   // Full blue  
  11.         Particle[i].Scalez = 0.25; // Smaller size  
  12.         Particle[i].Direction = 0; // No initial rotation  
  13.         Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; // Random upward speed  
  14.         Particle[i].Deceleration = 0.0025; // Initial deceleration  
  15.     }  
  16. }  

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:

  1. void glUpdateParticles(void) {  
  2.     for (int i = 0; i < ParticleCount; i++) {  
  3.         // Update position  
  4.         Particle[i].Ypos += Particle[i].Acceleration - Particle[i].Deceleration;  
  5.         Particle[i].Xpos += Particle[i].Xmov;  
  6.         Particle[i].Zpos += Particle[i].Zmov;  
  7.   
  8.         // Increase deceleration for downward motion  
  9.         Particle[i].Deceleration += 0.0025;  
  10.   
  11.         // Update rotation  
  12.         Particle[i].Direction += (rand() % 5) * 0.1;  
  13.   
  14.         // Reset particle if it falls below starting position  
  15.         if (Particle[i].Ypos < -5) {  
  16.             Particle[i].Ypos = -5;  
  17.             Particle[i].Xpos = 0;  
  18.             Particle[i].Zpos = 0;  
  19.             Particle[i].Deceleration = 0.0025;  
  20.             Particle[i].Acceleration = (rand() % 5 + 5) * 0.02;  
  21.         }  
  22.     }  
  23. }  

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.

  1. void glDrawParticles(void) {  
  2.     for (int i = 0; i < ParticleCount; i++) {  
  3.         glPushMatrix();  
  4.   
  5.         // Apply particle transformations  
  6.         glTranslatef(Particle[i].Xpos, Particle[i].Ypos, Particle[i].Zpos);  
  7.         glRotatef(Particle[i].Direction, 0, 0, 1);  
  8.         glScalef(Particle[i].Scalez, Particle[i].Scalez, Particle[i].Scalez);  
  9.   
  10.         // Draw particle quad  
  11.         glDisable(GL_DEPTH_TEST);  
  12.         glEnable(GL_BLEND);  
  13.         glBlendFunc(GL_DST_COLOR, GL_ZERO);  
  14.         glBindTexture(GL_TEXTURE_2D, texture[0]);  
  15.         glBegin(GL_QUADS);  
  16.         glTexCoord2d(0, 0); glVertex3f(-1, -1, 0);  
  17.         glTexCoord2d(1, 0); glVertex3f(1, -1, 0);  
  18.         glTexCoord2d(1, 1); glVertex3f(1, 1, 0);  
  19.         glTexCoord2d(0, 1); glVertex3f(-1, 1, 0);  
  20.         glEnd();  
  21.   
  22.         glEnable(GL_DEPTH_TEST);  
  23.         glPopMatrix();  
  24.     }  
  25. }  

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:

  1. #include <gl gl.h="">  
  2. #include <gl glut.h="">  
  3. #include <cstdlib> // For rand()  
  4.   
  5. // Number of particles in the system  
  6. const int ParticleCount = 500;  
  7.   
  8. // Structure to hold individual particle properties  
  9. typedef struct {  
  10.     double Xpos, Ypos, Zpos; // Position  
  11.     double Xmov, Zmov;       // Movement  
  12.     double Red, Green, Blue; // Color  
  13.     double Direction;        // Rotation angle  
  14.     double Acceleration;     // Upward speed  
  15.     double Deceleration;     // Downward speed  
  16.     double Scalez;           // Scale  
  17. } PARTICLES;  
  18.   
  19. // Array to store all particles  
  20. PARTICLES Particle[ParticleCount];  
  21.   
  22. // Texture array for particle rendering  
  23. GLfloat texture[10];  
  24.   
  25. // Function prototypes  
  26. void glCreateParticles(void);  
  27. void glUpdateParticles(void);  
  28. void glDrawParticles(void);  
  29.   
  30. // Initialize the particle system  
  31. void glCreateParticles(void) {  
  32.     for (int i = 0; i < ParticleCount; i++) {  
  33.         Particle[i].Xpos = 0;   // Start at the origin  
  34.         Particle[i].Ypos = -5;  // Start below the visible area  
  35.         Particle[i].Zpos = -5;  // Start away from the viewer  
  36.         Particle[i].Xmov = ((rand() % 10) - 5) * 0.01; // Random X movement  
  37.         Particle[i].Zmov = ((rand() % 10) - 5) * 0.01; // Random Z movement  
  38.         Particle[i].Red = 1;    // Full red  
  39.         Particle[i].Green = 1;  // Full green  
  40.         Particle[i].Blue = 1;   // Full blue  
  41.         Particle[i].Scalez = 0.25; // Smaller size  
  42.         Particle[i].Direction = 0; // No initial rotation  
  43.         Particle[i].Acceleration = (rand() % 5 + 5) * 0.02; // Random upward speed  
  44.         Particle[i].Deceleration = 0.0025; // Initial deceleration  
  45.     }  
  46. }  
  47.   
  48. // Update particle properties for animation  
  49. void glUpdateParticles(void) {  
  50.     for (int i = 0; i < ParticleCount; i++) {  
  51.         // Update position  
  52.         Particle[i].Ypos += Particle[i].Acceleration - Particle[i].Deceleration;  
  53.         Particle[i].Xpos += Particle[i].Xmov;  
  54.         Particle[i].Zpos += Particle[i].Zmov;  
  55.   
  56.         // Increase deceleration for downward motion  
  57.         Particle[i].Deceleration += 0.0025;  
  58.   
  59.         // Update rotation  
  60.         Particle[i].Direction += (rand() % 5) * 0.1;  
  61.   
  62.         // Reset particle if it falls below starting position  
  63.         if (Particle[i].Ypos < -5) {  
  64.             Particle[i].Ypos = -5;  
  65.             Particle[i].Xpos = 0;  
  66.             Particle[i].Zpos = 0;  
  67.             Particle[i].Deceleration = 0.0025;  
  68.             Particle[i].Acceleration = (rand() % 5 + 5) * 0.02;  
  69.         }  
  70.     }  
  71. }  
  72.   
  73. // Render all particles  
  74. void glDrawParticles(void) {  
  75.     for (int i = 0; i < ParticleCount; i++) {  
  76.         glPushMatrix(); // Save the current transformation state  
  77.   
  78.         // Apply particle transformations  
  79.         glTranslatef(Particle[i].Xpos, Particle[i].Ypos, Particle[i].Zpos);  
  80.         glRotatef(Particle[i].Direction, 0, 0, 1);  
  81.         glScalef(Particle[i].Scalez, Particle[i].Scalez, Particle[i].Scalez);  
  82.   
  83.         // Draw particle quad with texture  
  84.         glDisable(GL_DEPTH_TEST);  
  85.         glEnable(GL_BLEND);  
  86.         glBlendFunc(GL_DST_COLOR, GL_ZERO);  
  87.         glBindTexture(GL_TEXTURE_2D, texture[0]);  
  88.         glBegin(GL_QUADS);  
  89.         glTexCoord2d(0, 0); glVertex3f(-1, -1, 0);  
  90.         glTexCoord2d(1, 0); glVertex3f(1, -1, 0);  
  91.         glTexCoord2d(1, 1); glVertex3f(1, 1, 0);  
  92.         glTexCoord2d(0, 1); glVertex3f(-1, 1, 0);  
  93.         glEnd();  
  94.   
  95.         // Enable depth testing and restore transformations  
  96.         glEnable(GL_DEPTH_TEST);  
  97.         glPopMatrix();  
  98.     }  
  99. }  
  100.   
  101. // Display function  
  102. void display(void) {  
  103.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  104.     glLoadIdentity();  
  105.     glTranslatef(0, 0, -10); // Move camera back  
  106.   
  107.     glUpdateParticles(); // Update particle positions  
  108.     glDrawParticles();   // Render particles  
  109.   
  110.     glutSwapBuffers(); // Swap buffers for smooth animation  
  111. }  
  112.   
  113. // Initialize OpenGL settings  
  114. void init(void) {  
  115.     glEnable(GL_TEXTURE_2D);  // Enable 2D textures  
  116.     glEnable(GL_DEPTH_TEST);  // Enable depth testing  
  117.     glCreateParticles();      // Initialize particles  
  118.     // Load textures here (use your texture loading function)...  
  119. }  
  120.   
  121. // Main entry point  
  122. int main(int argc, char **argv) {  
  123.     glutInit(&argc, argv);  
  124.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH);  
  125.     glutInitWindowSize(500, 500);  
  126.     glutCreateWindow("Particle Engine");  
  127.     init();  
  128.     glutDisplayFunc(display);  
  129.     glutIdleFunc(display); // Continuously update  
  130.     glutMainLoop();  
  131.     return 0;  
  132. }  
  133. </cstdlib></gl></gl>  

If you have any questions, feel free to email me at swiftless@gmail.com. Happy coding!

  • March 25, 2010
  • 15