Particle systems are a very cheap and fun way to seriously boost the quality of your graphics. They allow you to create beautiful visual effects, such as explosions, lasers, fire, and so on. The basic idea behind particle systems is very simple: by drawing lots (hundreds or thousands) of very small (a few pixels) squares or circles, which behave individually according to some rules, you can generate a visual effect where the sum of the parts is much more than the parts separately.
For Castle Quest, we implemented a powerful particle system that allows us to make spectacular spell effects. The following movie demonstrates two creatures fighting, using their full array of weaponry to kill the opponent:
Particle systems are used to great effect in most modern-day video games. They are especially suitable to be run on the GPU, since they are the perfect example of highly parallelizable operations: all particles must be updated and drawn according to the same simple algorithm, so the GPU can take full advantage of its parallelization optimizations.
A naive implementation of a particle system could draw a particle on screen with the following code:
var a1 = alpha;
var a2 = alpha - 0.4;
if (a2 < 0.0) a2 = 0.0;
grad.addColorStop(0.0, "rgba(" + r + "," + g + "," + b + ", " + a1 + ")");
grad.addColorStop(0.5, "rgba(" + r + "," + g + "," + b + ", " + a2 + ")");
grad.addColorStop(1.0, "rgba(" + r + "," + g + "," + b + ", 0.0)");
ctx.fillStyle = grad;
ctx.arc(x, y, size, 0, 6.2831853, true);
This code will draw an arbitrarily sized particle with a given color to the screen as a small circle that becomes transparent near the edge of the circle (to avoid sharp edges).
This works. It’s actually pretty damn fast in Chrome: Chrome can easily render 1000 particles on screen at ~33 FPS. But in Firefox and IE, this method slows down to a crawl. A couple hundred particles will work, but any more will quickly reduce the FPS.
Because we want to at least support 1000 particles on the screen (less affects the quality of the visuals), an alternative approach was needed. It is known that most canvas implementations are faster at blitting images to the screen than rendering shapes, so we can leverage this fact by changing the way the particle system works.
Instead of drawing all particles on screen using paths, we draw all possible combinations of sizes and colors onto a hidden canvas, and just copy the appropriate part of this canvas to our main screen for each particle. The image to the right shows an example sprite sheet for an explosion.
In order to compare the performance of the three major browsers (Chrome 17, Firefox 11 and Internet Explorer 9), I have generated graphs of the average render time for 100, 1000 and 10000 particles on the three browsers:
Chrome is clearly the winner for this benchmark. In the naive implementation, Chrome is 8 times faster than Firefox and 6 times faster than IE9. For 10000 particles, Firefox crashed once while trying to render them; probably collapsing under their weight, as it took about 6 seconds to render one frame.
At 24FPS, about 41ms of rendering time for one frame is permitted without FPS loss. Note that all three browsers stay far below this for 1000 particles with the improved method. Chrome even almost manages to render 10000 particles at 24FPS, causing only a slight FPS drop. This is quite impressive, as a couple 100 particles are already sufficient to create spectacular particle effects.
With this optimization, the particle system is performant enough to cause no slowdowns on lower-end systems, and in the three major browsers.