OpenGL - Particle System Tutorial - Blending - Point Sprites
OpenGL - Particle System Tutorial - Blending - Point Sprites
uk
08
Contents
Introduction ............................................................................................................................................ 3 Prerequisites ....................................................................................................................................... 3 Setting up our Particle functions ............................................................................................................ 4 Rendering Points (GL_POINTS) ............................................................................................................... 4 Initial Translation ................................................................................................................................ 4 Draw Points ......................................................................................................................................... 4 Creating the Particle System ................................................................................................................... 6 Setting a Constant for MAX_PARTICLES ............................................................................................. 6 Defining our Particle Structure ........................................................................................................... 6 Float X,Y,Z........................................................................................................................................ 6 Float sX, sY, sZ, tX, tY, tZ.................................................................................................................. 6 Float R,G,B....................................................................................................................................... 7 Bool Active ...................................................................................................................................... 7 Int Age and MaxAge ........................................................................................................................ 7 Int BounceCount ............................................................................................................................. 7 Particles[MAX_PARTICLES]; ............................................................................................................ 7 Initialising the Particles ........................................................................................................................... 7 Activating the Particles ........................................................................................................................... 8 Adjusting the Particles ............................................................................................................................ 9 Rendering the Particles ......................................................................................................................... 10 Changing Colours .................................................................................................................................. 11 Using Textured Quads instead of Points ............................................................................................... 13 Loading the Bitmap Texture.............................................................................................................. 13 Rendering Quads as Points ............................................................................................................... 14 Adding a plane to bounce off................................................................................................................ 17 Adding Reflection of the particle system .............................................................................................. 19
Introduction
The scope of this document is to introduce beginners and newbie developers (of which I am most certainly one) to some of the concepts and ideas that I have used during the process of learning to develop 3D applications and games using Microsoft Visual C++ 2008 Express Edition and OpenGL. In this document I will cover the requirements for creating a simple particle system that can be used to create nice visual effects for your 3D projects. This example will create a particle system that fires particled upwards from a single point of origin. The particles will rise, fall and then bounce a couple of times before disappearing ready to be fired again.
Prerequisites
In order to paste and compile the code and follow the examples in this document you will need to download and install the following components: Microsoft Visual C++ 2008 Express Edition Microsoft Platform SDK
Both of these can be downloaded from the Microsoft Website free of charge. Details on downloading, installing and setting these up is available on MSDN and there are some snippets entries and details on the my website (www.fullonsoftware.co.uk/snippets/snippets.asp) It is also assumed that you have created a simple OpenGL Application that is ready for code and rendering a blank screen. There is a simple OpenGL Application Template and details on setting up the Visual Studio Project also posted on snippets. IMPORTANT NOTE I am not a professional developer. I am a hobby coder that has struggled through internet tutorials to learn what I have. Although the code I have generated will compile and works it may not represent industry standards and it is certainly possible that there are better ways of performing the some functions. One of the biggest problems I had was finding good quality and easy to understand tutorials to show me how to do stuff. This is one of the main reasons for wanting to write this guide. I want to group together some of the knowledge that I have gained from online tutorials and development experience into one place. I hope it helps to get people started.
Initial Translation
We need to translate into the GL World so that we can see what we are rendering. Add the following line before your RenderParticles() call: glTranslatef(0.0f, -1.0f, -10.0f); This will move our origin 10 into the scene and down 1. This should move everything we render from here on into our field of view so that we can see it all. These are good values to start with, we can adjust them later if we need to.
Draw Points
Initially we will use GL_POINTS to render our Particle System. Once we have it working we will look at adding textures, enabling blending and simple reflections but lets not run before we can walk. Lets add a few lines to our RenderParticles() functions so that it looks like this:
void RenderParticles(){ glColor4f(1.0f, 1.0f, 1.0f, 0.5f); glBegin(GL_POINTS); glVertex3f(-1.0f,0.0f, 0.0f); glVertex3f(0.0f,0.0f, 0.0f); glVertex3f(1.0f,0.0f, 0.0f); glEnd(); }
What this code does is sets the colour (White) for our points and then renders 3 GL_POINTS. It will render a white point at every vertex submitted between glBegin(GL_POINTS) and glEnd(). If you compile and run now you should see 3 white dots rendered in a line about 1/3 from the bottom of your screen.
You should see something like the screenshot above. This is just to confirm that we have everything setup correctly and that we are ready to start creating our Particle System.
vector. We will move it by a percentage which will effectively steer the particle towards its desired target speed/direction. If it is moving up it will slow down before it starts falling and will fall faster for each frame. Also, the left/right motion will slowly reduce to nearly zero along X and Z. I will explain more when we get to coding the AdjustParticle() Routine. Float R,G,B These values will hold the colour of the particle. This will be useful if we want to rotate the colours in our Particle System. Bool Active This will be set to true if the particle is active. We will only adjust and render active particles. Also, each frame we will activate one particle if there are any that are inactive up to the MAX_PARTICLES value. Int Age and MaxAge We will assign each particle a maximum age which will be the maximum number of frames that the particle will be rendered for before it is set to inactive. Each frame we will add 1 to the age until it reaches MaxAge and then set to inactive. Int BounceCount During adjustment of the particle we will check to see if the Y position has dropped below zero. If it has then we will reverse the sY value to send it back up which will create a bouncing effect. Also, because of the interaction between sY and tY each bounce will be slightly lower than the next creating an effective bouncing simulation that can be quite realistic with the right values. We will use the BounceCount to store a count of times each particle has bounced. This will allow us to set a particle to inactive if it bounces more than, say, three times. This will avoid having active particles sitting motionless. Particles[MAX_PARTICLES]; This line at the end defines an array of PARTICLE structures.
What we are doing here is simply looping through all of our particles and setting the target speed values that we want to use. We are also setting all of them to inactive as they will be activate one each frame. This gives us a smoother start to our rendering rather than starting with 100 particles onscreen. Also, notice that we are taking the opportunity to seed the rand() function so that we can use it later to generate random valued later on. As we are seeding by the system time we need to include time.h at the top of our main.cpp file. #include <time.h>
tweaked the sY values to get a good height of our particle system. You can play with these numbers to change the effect you get. An additional not regarding this routing is the placement of the return; statement. Putting it inside the if() statement means that only 1 particle will be activated per frame because the function will exit once this has been done.
For each of the speed values sX, sY, sZ we are taking the difference between the target and the source speeds and dividing it by 20 before adding it to the current speed.
We have already translated to the origin of our Particle System so, in the RenderParticles() function we first instruct OpenGL that every vertex until glEnd() should be rendered as a point. We then loop through our particle array and issue a glVertex3f() call for each active particle. Then, when we are done we issue glEnd() and we are done. Compile and run the code. You should see a cool looking fountain of points rendered on the screen like this.
Changing Colours
OK. What we have now is OK and it is a functioning Particle System. Now lets take a look at how we can improve the effect we are getting. The first thing we can do is have the colours rotate. To do this we can use the R,G and B values for our particles. Before we can start rotating colours we need to setup some additional variables. Add the following code at the top of your main.cpp file: float float float float float float R = 0.8f; G = 0.2f; B = 0.0f; cR = 0.001f; cG = 0.002f; cB = 0.003f;
We are defining global variables for Red, Green and Blue which is the colour we will be setting for each particle when we activated it. I have assigned some values picked at random. Then we are defining change variables cR, cG and cB. These will be added to R,G and B each time we activate a particle so that the colour changes. The reason these are small values to to make the colour change slowly. The next code we need to add is in our ActivateParticles() function. It should now look like this: void ActivateParticles(){ int p; for(p=0; p<MAX_PARTICLES; p++){ if(!Particles[p].Active){ // Start the particle at 0,0,0 origin Particles[p].X = 0.0f; Particles[p].Y = 0.0f; Particles[p].Z = 0.0f; // The following lines set a random Particles[p].sX = (((float)((rand() 1000.0f) Particles[p].sY = (((float)((rand() 500.0f); Particles[p].sZ = (((float)((rand() 1000.0f) speed value % 100) + 1)) / - 0.05f; % 100) + 50)) / % 100) + 1)) / - 0.05f;
// We also activate the particle Particles[p].Active = true; // Set it's Age to zero Particles[p].Age = 0; // We also assign a max age to the particles Particles[p].MaxAge = MAX_PARTICLE_AGE; // We Also reset the bouncecount to zero Particles[p].BounceCount = 0;
Particles[p].R = R; Particles[p].G = G; Particles[p].B = B; R+=cR; G+=cG; B+=cB; if(R>1.0f){R=1.0f; cR=-cR;} if(R<0.0f){R=0.0f; cR=-cR;} if(G>1.0f){G=1.0f; cG=-cG;} if(G<0.0f){G=0.0f; cG=-cG;} if(B>1.0f){B=1.0f; cB=-cB;} if(B<0.0f){B=0.0f; cB=-cB;} return; } } }
What we have added is code to set the particle colour to our R,G, and B variables. Then we add the colour change variable to the colours so that the next particle activated will have a slightly different colour. Then, the last 6 lines simply check if the R,G and B values are greater than 1.0f or less than 0.0f and, if they are, the change variables are reversed to move in the opposite direction. Now all we need to do is to use the Particle colour values in our RenderParticles() function. Change the glColor4f() call from from all 1.0f values to read the following: glColor4f(Particles[p].R, Particles[p].G, Particles[p].B, 0.5f); Now we can compile again and run the code. You should now see the colour of the particles slowly changing as it runs. Looks a little better but we can still improve it. Lets move on.
hBMP=(HBITMAP)LoadImage(GetModuleHandle(NULL), FileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE ); if (!hBMP) // Does The Bitmap Exist? return FALSE; // If Not Return False GetObject(hBMP, sizeof(BMP), &BMP); // Get The Object // hBMP: Handle To Graphics Object // sizeof(BMP): Size Of Buffer For Object Information // &BMP: Buffer For Object Information glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // Pixel Storage Mode (Word Alignment / 4 Bytes) // Typical Texture Generation Using Data From The Bitmap glBindTexture(GL_TEXTURE_2D, texid);// Bind To The Texture ID glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); // Linear Min Filter glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Linear Mag Filter glTexImage2D(GL_TEXTURE_2D, 0, 3, BMP.bmWidth, BMP.bmHeight, 0, GL_BGR_EXT, GL_UNSIGNED_BYTE, BMP.bmBits); DeleteObject(hBMP); return TRUE; } This function will load a bitmap file and store it as a texture we can use when rendering. For the purpose of this tutorial I am not going to go into the ins and outs of each line of code. I may write a tutorial on this at some point in the future but for now, if you want more information on this then checkout NeHe.GameDev.net which is a fantastic site for OpenGL tutorials. // Delete The Object // Loading Was Successful
Once this function has been added you will then need to call it in your InitParticles() function. Before you do that though you will need to add the function declaration at the top of your main.cpp and you will have to define a GLuint value to hold the GL Texture ID that you load. Add the following lined to the top of your code: bool LoadBitmapTexture(char * FileName, GLuint &texid); GLuint txParticle; What we are going to do now is call LoadBitmapTexture() from our InitParticles() function which will store the loaded TextureID into the txParticle GLuint variable. This is the texture we will be loading:
This was created with the GiMP. It has to be 24bit colour bitmap image. The effect in the image is white but, when we render, the colour specified for the particle will be blended with the image to allow the colours to continue to change. Add the following line to your InitParticles() routine: LoadBitmapTexture("./Particle.bmp", txParticle); This will now have loaded the texture and it should be ready to use. We can now update our rendering routine to make use of the texture. You need to place a copy of Particle.bmp in your Debug folder so that it can be found by the application when it runs.
This is the size of the points we will render. Actually, in this example it will be the distance between the centre of the quad and each edge. What we will do is enable textures (GL_TEXTURE_2D) and bind our texture. Then we will need to enable blending. We dont want each of our particles to have a black square around it. We will use a glBlendFunc() with GL_SRC_COLOR, GL_ONE as the parameters. Again, a full description of the glBlendFunc() function is outside the scope of this tutorial but basically, these attributes mean that we will mix the source (IMAGE) colour with the destination background.
We also need to disable GL_DEPTH_TEST so that the engine will not check if there is another quad in front of it. If we do not do this then the ordering of the quads will lead to some of the quads being rendered with black squares around them. We now need to update our RenderParticles() function. It should look like this: void RenderParticles(){ int p; // Enable textures and bind our particle texture glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, txParticle); // Disable Depth testing. glDisable(GL_DEPTH_TEST); // Enable blending glEnable(GL_BLEND); glBlendFunc(GL_SRC_COLOR,GL_ONE); for(p=0; p<MAX_PARTICLES; p++){ if(Particles[p].Active){ glColor4f(Particles[p].R, Particles[p].G, Particles[p].B, 1.0f); glPushMatrix(); glTranslatef(Particles[p].X, Particles[p].Y, Particles[p].Z); glBegin(GL_QUADS); glNormal3f(0.0f, 0.0f, 1.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-Size, -Size, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f(Size, -Size, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(Size, Size, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-Size, Size, 0.0f); glEnd(); glPopMatrix(); } } glEnable(GL_DEPTH_TEST); }
For each particle we are going to render a quad. There are a few steps to undertake to do this. First we are going to push the translation matrix to the matrix stack. This is because we are going to need to translate to the centre of each particle and we will need to be able to return the matrix back to its original state ready to render the next particle. We then translate to the centre point (X,Y,Z) of the particle. Once there we call glBegin(GL_QUADS) which instructs OpenGL to draw a QUAD for each set of 4 vertices entered until the call to glEnd(). We are setting a glNormal3f() which points away from the surface of the quad. (Take a look in snippets on www.fullonsoftware.co.uk for a tutorial on calculating normals). We have not enabled lighting but the call is there if we do. We then set the texture coordinates and vertex for each corner of our quad. We have to issue the vertices in anticlockwise order. In this case we start with the bottom left and work our way round. We use Size and Size for our values. Z stays static at 0.0f. At the end we re-enable GL_DEPTH_TEST as it is likely that we will need this on later. Now build and run your code. I think that is a significant improvement on using GL_POINTS. Also, the results of the blending and removing the depth testing are very effective. It should look something like this:
Save a copy of it as a 256x256 24bit Bitmap file called Plane.bmp in your debug folder and then add the following line in your InitParticles() functions: LoadBitmapTexture("./Plane.bmp", txPlane); We can then add a few lines of code to your DrawGLScene() function to render the plane. You need to add the following in this function. It needs to be after your initial translation to the point of the particle system but before the call to RenderParticles(). glEnable(GL_BLEND); glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA); glColor4f(0.0f, 0.2f, 0.2f, 0.5f); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, txPlane); glBegin(GL_QUADS); glNormal3f(0.0f, 1.0f, 0.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(-10.0f, 0.0f, 10.0f); glTexCoord2f(1.0f, 0.0f); glVertex3f( 10.0f, 0.0f, 10.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f( 10.0f, 0.0f, -10.0f); glTexCoord2f(0.0f, 1.0f); glVertex3f(-10.0f, 0.0f, -10.0f); glEnd(); This will enable textures, blending and set a colour for the texture. It will then render a quad from that centre point.
You can adjust the colour and blend settings to change the appearance of the base but I like the way it looks with these settings. It isnt too bright and that is good as you will see in the next section.
What this code does is push the matix so that we can set it back afterwards. Then it issued glScale() with -1.0f for the Y value. All subsequent vertex calls and translations etc, will be multiplied by the values of glScale(). By setting 1.0f for X and Z we are leaving them unchanged. Then the -1.0f will invert all of the Y coordinates for our particles. Hey presto, instant reflection. It should look something like this:
All Done
So that is it. A simple OpenGL Particle System that uses textures to create pretty cool visual effect. You can try messing with the parameters for colours and blending, changing the images and rotating the whole scene to generating different effects. As far as point sprites are concerned, I think what we have implemented in this tutorial is a method of point billboarding that does not require the use of specific extensions. There are specific point sprite extensions for Nvidia devices for example but I think using textured quads as our points is very effective. Also, note that this is a simple scene that is static. If you want to use this style of particle system in your own projects then it is likely that your scene will not be static. In these cases you will need to implement some code to make sure that each quad is rendered facing the camera. If you dont then your points will distort as your scene moves and rotates.
Useful references
http://NeHe.GameDev.net The NeHe OpenGL Tutorials are second to none and a fantastic place to get started with OpenGL. http://www.fullonsoftware.co.uk This is my website. There is a snippets section with code samples and tutorials and stuff. http://www.gimp.org Website of the GNU Image Manipulation Project (GIMP). This program is free and was used to generate the images for textures in this example. mailto:richard.sabbarton@gmail.com This is my email address. Let me know if you have any questions regarding the contents of this tutorial or would like to suggest a subject for a future tutorial. Also, please let me know if you find any errors so that I can correct them. Also, I would be interested to know if the code in this tutorial is used in your project. I would be great to see it in action.