For my CSE167 Final Project I implemented a standalone particle engine in OpenGL. This task proved to be quite a bit more difficult than I had initially anticipated, especially in terms of achieving “nice”-looking effects that could render efficiently on non-top-of-the-line PC’s. Still, with a great amount of time and effort I did manage to implement virtually everything that I initially wanted.
My goal was originally to make a particle engine that could be easily used in larger-scale products without having to worry about its internal implementation; in this I succeeded completely: The particle engine is contained in a c++ class, and can be used by placing calls to a set of very simple functions including:
int initParticleEngine(…);
int
setTexture(GLuint tex);
int
setParticleSize(float size);
int
setGravity(Vectorf gravity);
int
setEmitterPos(Vectorf pos, Vectorf var);
int
setParticleColor(float colorR, float colorG, float colorB);
int
setFade(float fadeSpeed, float fadeVar);
int
setVelocity(float velocity, float velocityVar);
void
setAngle(Vectorf angle, Vectorf angleVar);
void
setAttractor(Vectorf attractor, float attractorStrength);
int
changeNumOfParticles(int howMany);
int
drawParticles();
void
updateParticles(float frameTime);
void
cleanupParticleEngine();
where a “Vectorf” is just a struct containing x, y, and z floating point values. With the exception of loading its own textures and determining the application’s frame rate, the engine completely takes care of everything needed to render the particles (including memory allocation/deallocation, particle tracking & positioning, physics, blending, etc). More on all of this below.
The basic concept behind my engine is very simple: The emitter is given some location in our 3D world, and some number of particles (each of which is stored in a dynamically allocated array). Each particle is then “initialized” with a position, velocity, lifetime, and several other pieces of information. To render each particle, the engine simply needs to cycle through this array once per frame, altering each particle’s position by it’s velocity and decreasing its remaining lifetime!
Making the particle look good and operate efficiently, however proved to be the most difficult part. Initially, I was rendering my particles with simple calls to glVertex3f – which was, of course, far too slow when then number of particles grew to any reasonable number. Display Lists, while easy to implement, offered virtually no performance boost due to the large overhead required for each call to glCallLists(). So, the best results I got were by using vertex arrays (I wanted to implement the engine using interleaved arrays to minimize cache misses, but unfortunately I ran out of time before I could finish this…). So, now the particles are rendering (fairly) quickly! What about some controls?
The most obvious first “feature” I added was the ability to specify different angles and velocities of emission; the velocities were easy: just give each particle a different velocity when it’s first initialized! Angles, however, were slightly more difficult. In order to specify the emitter’s direction in terms of angles, I had to implement a function to convert from spherical to Cartesian coordinates. This is because in our 3D OpenGL world, it is desirable to specify the emission direction in terms of angles (spherical coordinates), yet each particle’s velocity is specified in terms of a vector with X, Y, and Z components (Cartesian coordinates).
Still, the engine didn’t look quite right – with each particle dieing at the same time, and each particle coming out at the exact same velocity, the effect wasn’t convincing at all. So, I added some randomization to each of these factors – particle lifetime, emission angle (which effectively specifies the emission stream’s width), and emission velocity. By specifying the constraints within which each of these numbers is allowed to vary, the programmer can achieve a much wider variety of effects. For instance:
-Zero fade-variation, high velocity-variation, 360-degree angular variation on all planes creates an effect that looks like exploding fireworks!
-Middle fade-variation, middle velocity-variation, low angular variation on all planes creates an effect that looks like flames coming from a candle!
While I was on the track of randomization, I also decided to add the ability to randomize the emitter’s location (subject to user-specified-constraints, of course) along either the X or Y axis. This provided the effect of line-particle sources (instead of only point-sources). Cool!
Now that all of the basics seemed to be working (of course, the farther I went the more bugs continued to arise), I decided to get a little more fancy. Here, I added blending effects so that each particle wouldn’t just live until it died, but instead would “fade” to death. This effect was necessary for any type of fire to appear convincing at all. This effect wasn’t too tough at all – I simply decided to use each particle’s remaining lifetime as its alpha-value; this way, the closer to death they come, the smaller of an alpha-value they have, until they finally disappear and die (at which point the emitter reclaims the memory and shoots out a new particle).
The final effects I added were gravity – both universal (i.e. pulls uniformly in one direction), and in terms of a “local attractor” (I’m not sure if this is the correct term…). The first effect was really simple. To implement gravity in the positive y-direction, for instance, I just removed some of each particle’s velocity in the y-direction during each frame. The attractor, however, was much more difficult (by “attractor,” I mean a single point that attracts or repels particles). To achieve this effect, I had to obtain a vector from each particle’s current position to the attractor’s position, and then use this vector to add/remove certain amounts of velocity from each particles’ velocity vectors.
This was as far as I went in terms of effects, and this was pretty much what I’d hoped to achieve with my engine. In playing with it a bit more, however, I recognized a couple of areas where it could be improved. For instance, I decided that simply “updating” each particle by the same amount for each frame was foolish – what happens if the frame rate drops to from 60FPS to 30FPS? All of a sudden, your particles are moving half as quickly as you wanted them to be! So, I used Windows’ built-in high-resolution performance counter to determine the frame rate of the application on-the-fly, and then alter the engine’s updating factors according to this frame rate. Thus, as the frame rate drops, particles appear to “skip” across the screen more, but they still move at the same overall speed as if they were being rendered at the (ideal) 60FPS!
Also, notice that while the engine is pretty versatile, it still lacks one important feature: Once the engine has been initialized, the number of particles must always be the same! So, I added a way to decrease the number of particles that are currently being rendered and updated below the “maximum” number for which memory was initially allocated. This way, the user can “tweak” the engine to obtain maximum performance given the needs of their application. For instance, if you were creating a game that had a small candle in it, and the user were right in front of the candle, you’d need a pretty large number of flame-particles to make the fire look nice. But if the candle were across the room, decreasing the number of particles could save a lot of processing time. I performed this reduction simply by adding the following check to each particle’s per-frame update. If a particle is alive, update it; if a particle is dead, re-create it ONLY if the current number of living particles is less than or equal to the desired number; otherwise, leave it dead.
That pretty much sums up all of the engine’s features – so, on to the driver. In order to test and debug the engine, I wrote a simple windows “driver”, a dialog box containing sets of scrollbars, each enabling the user to one aspect of the particle engine (angles, colors, gravity, etc). This dialog box, of course, doesn’t handle any of the OpenGL rendering – instead it simply pops up another GLUT-window to perform the actual rendering. A snapshot of the driver can be seen on the course website; of course, all of the features mentioned above can be manipulated from within it (except for setting the emitter and attractors’ positions, which are handled with mouse-clicks inside the glut window).
So, that’s my particle engine! A demo executable can be downloaded from the course webpage, and you can try using any image you’d like for the particles (it must be a 24-bpp bitmap image named “Particle.bmp”, placed in the executable’s directory). Have fun~