OpenGL:Tutorials:Tutorial Framework:Particles
From GPWiki(Redirected from OpenGL Tutorial Framework:Particles)
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello.
[edit] Setting UpEach particle has unique position, direction vector, color and a 'life' values. These values are contained by a structure: typedef struct { float xPos,yPos,zPos; float xVec,yVec,zVec; float r,g,b,life; }SpriteInfo;
const float PARTICLE_SIZE = 0.5f; const int NUM_PARTICLES = 10000; const int INITIAL_PARTICLE_SPREAD = 100; const float SPEED_DECAY = 0.00005f; // (Gravity)
Here we use three long values to manage the time: long Time1,Time2,Ticks;
Time1 stores the tick count of the last frame, Time2 is the tick count of the current frame and Ticks is the difference between them. This is the value we will use to calculate our movement. [edit] Managing the ParticlesThe first thing we must do with our particles is set them all to a known state. Setting all the 'life' attributes to zero means that each particle will be initialised by the main loop. for(Index=0;Index!=NUM_PARTICLES;Index++) { Spr[Index].life=0.0f; Spr[Index].r=1.0f; Spr[Index].b=0.0f; } We also set the red and blue color values here as they will not change during the program.
If a particle is live (life>0), we add the direction vectors to the position. We use the Ticks value multiplied by the direction vectors to calculate the correct distance: if(Spr[Index].life>0.0f) { Spr[Index].xPos+=(Spr[Index].xVec*Ticks); Spr[Index].yPos+=(Spr[Index].yVec*Ticks); Spr[Index].zPos+=(Spr[Index].zVec*Ticks); Spr[Index].yVec-=(SPEED_DECAY*Ticks); We also subtract the 'SPEED_DECAY' value from the Y vector to give the illusion of gravity.
// 'Bounce' particle if on the floor square if(Spr[Index].xPos>-10.0f && Spr[Index].xPos<10.0f && Spr[Index].zPos>-10.0f && Spr[Index].zPos<10.0f) { if(Spr[Index].yPos<PARTICLE_SIZE) { Spr[Index].yPos=PARTICLE_SIZE; Spr[Index].life-=0.01f; Spr[Index].yVec*=-0.6f; } }
Spr[Index].life-=(0.0001f*Ticks); }
// Reset position Spr[Index].xPos=0.0f; Spr[Index].yPos=PARTICLE_SIZE; Spr[Index].zPos=0.0f;
// Get a random spread and direction Spread=(float)(rand()%MaxSpread)/10000.0f; Angle=(float)(rand()%157)/100.0f; // Quarter circle // Calculate X and Z vectors Spr[Index].xVec=cos(Angle)*Spread; Spr[Index].zVec=sin(Angle)*Spread;
// Randomly reverse X and Z vector to complete the circle if(rand()%2) Spr[Index].xVec= - Spr[Index].xVec; if(rand()%2) Spr[Index].zVec= - Spr[Index].zVec;
// Get a random initial speed Spr[Index].yVec=(float)(rand()%500)/10000.0f; // Get a random life and 'yellowness' Spr[Index].life=(float)(rand()%100)/100.0f; Spr[Index].g=0.2f+((float)(rand()%50)/100.0f);
[edit] BillboardingSo, now we have updated all our particles, it's time to actually draw them. The technique of billboarding is used to give the illusion of complexity by aligning a textured polygon to face the viewer as the scene or observer moves. Cylindrical billboarding uses a single axis to align the polygon and is often used for trees and other objects which have a fixed position. The problem here is that the illusion is lost if the viewer moves above the object. Spherical billboarding uses two axes alignment and is used for clouds, explosions and particle effects. It does not suffer the same problems as the cylindrical method. There are two alignment methods for billboarding; Simple or scene based alignment uses the same angles for all polygons, this is the fastest method and works well for most applications. Complex or camera based alignment calculates the facing for each polygon individually, aligning it exactly with the viewer. This is time consuming, but produces better results for large billboards. We will use simple spherical billboarding in this example. First, we'll turn off depth writes to prevent interference patterns from appearing in the output (we're going to be drawing lots of overlapping polygons). This way, the quads are still affected any depth information in the scene: glDepthMask(GL_FALSE); Now we use the glPushMatrix() / glPopMatrix() technique to move each particle into place. Note the reverse application of the view rotations to align the quad with the viewer.
for(Index=0;Index!=MaxParticles;Index++) { glPushMatrix(); // Place the quad and rotate to face the viewer glColor4f(Spr[Index].r,Spr[Index].g,Spr[Index].b,Spr[Index].life); glTranslatef(Spr[Index].xPos,Spr[Index].yPos,Spr[Index].zPos); glRotatef(-ViewYaw,0.0f,1.0f,0.0f); glRotatef(-ViewPitch,1.0f,0.0f,0.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f,0.0f); glVertex3f(-PARTICLE_SIZE, PARTICLE_SIZE,0.0f); glTexCoord2f(0.0f,1.0f); glVertex3f(-PARTICLE_SIZE,-PARTICLE_SIZE,0.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( PARTICLE_SIZE,-PARTICLE_SIZE,0.0f); glTexCoord2f(1.0f,0.0f); glVertex3f( PARTICLE_SIZE, PARTICLE_SIZE,0.0f); glEnd(); glPopMatrix(); } If all goes well, we should have a nice fountain of particles like this: During execution, use the left/right keys to control the spread and the up/down keys to control the particle count. [edit] Source CodeThe source to Render.cpp, compile this demo using the OpenGL Tutorial Framework. #include "Framework.h" #include <cmath> #include <ctime> #include <cstdlib> #include "tga.h" // A few parameter definitions, tweak these to alter the appearance of the spray const float PARTICLE_SIZE = 0.5f; const int NUM_PARTICLES = 10000; const int INITIAL_PARTICLE_SPREAD = 100; const float SPEED_DECAY = 0.00005f; // (Gravity) // Particle structure typedef struct { float xPos,yPos,zPos; float xVec,yVec,zVec; float r,g,b,life; }SpriteInfo; // Function declarations bool LoadTexture(char *TexName, GLuint TexHandle); // Here we go! void Render(void) { GLuint Texture[128]; // Handles to our textures SpriteInfo Spr[NUM_PARTICLES]; // Array of particles int MaxSpread,MaxParticles,Index; float Spread,Angle; float ViewYaw, ViewPitch; long Time1,Time2,Ticks; // Allocate all textures in one go glGenTextures(128,Texture); // Background color glClearColor(0.0f,0.0f,0.0f,1.0f); // Setup our screen glMatrixMode(GL_PROJECTION); glViewport(0,0,800,600); glLoadIdentity(); glFrustum(-.5f,.5f,-.5f*(600.0f/800.0f),.5f*(600.0f/800.0f),1.0f,500.0f); glMatrixMode(GL_MODELVIEW); // Enable z-buffer glEnable(GL_DEPTH_TEST); // Enable blending glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // Load the textures LoadTexture("../particle.tga",Texture[0]); LoadTexture("../Marble.tga",Texture[1]); glEnable(GL_TEXTURE_2D); // Seed the randomiser srand(time(NULL)); // Set up adjustable parameters and timing variables MaxSpread=INITIAL_PARTICLE_SPREAD; MaxParticles=NUM_PARTICLES/2; Time1=Time2=clock(); // Set all particles to dead for(Index=0;Index!=NUM_PARTICLES;Index++) { Spr[Index].life=0.0f; Spr[Index].r=1.0f; Spr[Index].b=0.0f; } // Main Loop while(RunLevel) { if(Keys[VK_ESCAPE]) RunLevel=0; // Tighten spray if(Keys[VK_LEFT]) { MaxSpread--; if(MaxSpread<1) MaxSpread=1; } // Widen spray if(Keys[VK_RIGHT]) { MaxSpread++; if(MaxSpread>500) MaxSpread=500; } // Reduce particle count (This takes a while to be noticable) if(Keys[VK_DOWN]) { MaxParticles-=1; if(MaxParticles<1) MaxParticles=1; } // Increase particle count if(Keys[VK_UP]) { MaxParticles+=1; if(MaxParticles>NUM_PARTICLES) MaxParticles=NUM_PARTICLES; // Kill new particle to ensure good start point Spr[MaxParticles].life=0.0f; } // Reset view glLoadIdentity(); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // Get ticks since last frame Time2=clock(); Ticks=Time2-Time1; Time1=Time2; // Calculate view angles ViewPitch=(Mouse.My-300)/4.0f; ViewYaw=(Mouse.Mx-300)/3.0f; // Set up the view based on the mouse position glTranslatef(0.0f,-10.0f,-50.0f); glRotatef(ViewPitch,1.0f,0.0f,0.0f); glRotatef(ViewYaw,0.0f,1.0f,0.0f); // Set up for floor plane glBindTexture(GL_TEXTURE_2D,Texture[1]); glColor4f(1.0f,1.0f,1.0f,1.0f); glDisable(GL_BLEND); // Draw floor plane glBegin(GL_QUADS); glTexCoord2f(0.0f,1.0f); glVertex3f(-10.0f, 0.0f, 10.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( 10.0f, 0.0f, 10.0f); glTexCoord2f(1.0f,0.0f); glVertex3f( 10.0f, 0.0f,-10.0f); glTexCoord2f(0.0f,0.0f); glVertex3f(-10.0f, 0.0f,-10.0f); glEnd(); // Update particles, generating new if required for(Index=0;Index!=MaxParticles;Index++) { if(Spr[Index].life>0.0f) { Spr[Index].xPos+=(Spr[Index].xVec*Ticks); Spr[Index].yPos+=(Spr[Index].yVec*Ticks); Spr[Index].zPos+=(Spr[Index].zVec*Ticks); Spr[Index].yVec-=(SPEED_DECAY*Ticks); // 'Bounce' particle if on the floor square if(Spr[Index].xPos>-10.0f && Spr[Index].xPos<10.0f && Spr[Index].zPos>-10.0f && Spr[Index].zPos<10.0f) { if(Spr[Index].yPos<PARTICLE_SIZE) { Spr[Index].yPos=PARTICLE_SIZE; Spr[Index].life-=0.01f; Spr[Index].yVec*=-0.6f; } } Spr[Index].life-=(0.0001f*Ticks); } else // Spawn a new particle { // Reset position Spr[Index].xPos=0.0f; Spr[Index].yPos=PARTICLE_SIZE; Spr[Index].zPos=0.0f; // Get a random spread and direction Spread=(float)(rand()%MaxSpread)/10000.0f; Angle=(float)(rand()%157)/100.0f; // Quarter circle // Calculate X and Z vectors Spr[Index].xVec=cos(Angle)*Spread; Spr[Index].zVec=sin(Angle)*Spread; // Randomly reverse X and Z vector to complete the circle if(rand()%2) Spr[Index].xVec= - Spr[Index].xVec; if(rand()%2) Spr[Index].zVec= - Spr[Index].zVec; // Get a random initial speed Spr[Index].yVec=(float)(rand()%500)/10000.0f; // Get a random life and 'yellowness' Spr[Index].life=(float)(rand()%100)/100.0f; Spr[Index].g=0.2f+((float)(rand()%50)/100.0f); } } // Select particle texture glBindTexture(GL_TEXTURE_2D,Texture[0]); glEnable(GL_BLEND); glDepthMask(GL_FALSE); // Draw the particles for(Index=0;Index!=MaxParticles;Index++) { glPushMatrix(); // Place the quad and rotate to face the viewer glColor4f(Spr[Index].r,Spr[Index].g,Spr[Index].b,Spr[Index].life); glTranslatef(Spr[Index].xPos,Spr[Index].yPos,Spr[Index].zPos); glRotatef(-ViewYaw,0.0f,1.0f,0.0f); glRotatef(-ViewPitch,1.0f,0.0f,0.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f,0.0f); glVertex3f(-PARTICLE_SIZE, PARTICLE_SIZE,0.0f); glTexCoord2f(0.0f,1.0f); glVertex3f(-PARTICLE_SIZE,-PARTICLE_SIZE,0.0f); glTexCoord2f(1.0f,1.0f); glVertex3f( PARTICLE_SIZE,-PARTICLE_SIZE,0.0f); glTexCoord2f(1.0f,0.0f); glVertex3f( PARTICLE_SIZE, PARTICLE_SIZE,0.0f); glEnd(); glPopMatrix(); } glDepthMask(GL_TRUE); // Show our scene FlipBuffers(); } // Clean up textures glDeleteTextures(128,Texture); } // Load a TGA texture bool LoadTexture(char *TexName, GLuint TexHandle) { TGAImg Img; // Image loader // Load our Texture if(Img.Load(TexName)!=IMG_OK) return false; glBindTexture(GL_TEXTURE_2D,TexHandle); // Set our Tex handle as current // Create the texture if(Img.GetBPP()==24) glTexImage2D(GL_TEXTURE_2D,0,3,Img.GetWidth(),Img.GetHeight(),0, GL_RGB,GL_UNSIGNED_BYTE,Img.GetImg()); else if(Img.GetBPP()==32) glTexImage2D(GL_TEXTURE_2D,0,4,Img.GetWidth(),Img.GetHeight(),0, GL_RGBA,GL_UNSIGNED_BYTE,Img.GetImg()); else return false; // Specify filtering and edge actions glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); return true; } [edit] DownloadsGLTut6_Particles.zip - A zip including Win32 and GLFW source code, images and a Win32 Binary. |



