Hostinicon
GET HOSTING FROM $3.95/MO PLUS A FREE YEAR ON TUTS+ (RRP $180). HURRY OFFER LIMITED. Check it out
Advertisement

Creating Smooth Particle Emission With Sub-Frame Interpolation

by
Gift

Get a free year on Tuts+ this month when you purchase a Siteground hosting plan from $3.95/mo

Particle effects greatly spice up game visuals. They are usually not the main focus of a game, but many games rely on particle effects to increase their visual richness. They are everywhere: dust clouds, fire, water splashes, you name it. Particle effects are usually implemented with discrete emitter movement and discrete emission "bursts". Most of the time, everything looks just fine; however, things break down when you have a fast-moving emitter and high emission rate. This is when sub-frame interpolation comes into play.


Demo

This Flash demo shows the difference between a common implementation of a fast-moving emitter and the sub-frame interpolation approach at different speeds.


Click to switch between different implementations of the interpolation at different speeds.
Tip: Sub-frame interpolation is slightly more computationally expensive than regular implementation. So if your particle effects look just fine without sub-frame interpolation, it's usually a good idea not to use sub-frame interpolation at all.

A Common Implementation

First, let's take a look at a common implementation of particle effects. I will present a very minimalistic implementation of a point emitter; on each frame, it creates new particles at its position, integrates existing particles, keeps track of each particle's life, and removes dead particles.

For simplicity's sake, I will not use object pools to reuse dead particles; also, I will use the Vector.splice method to remove dead particles (you usually do not want to do this because Vector.splice is a linear-time operation). The main focus of this tutorial is not efficiency, but how the particles are initialized.

Here are some helper functions we'll need later:

And below is the Particle class. It defines some common particle properties, including lifetime, grow and shrink time, position, rotation, linear velocity, angular velocity, and scale. In the main update loop, position and rotation are integrated, and the particle data is finally dumped into the display object represented by the particle. The scale is updated based on the particle's remaining life, compared to its grow and shrink time.

And finally, we have the point emitter itself. In the main update loop, new particles are created, all particles are updated, and then dead particles are removed. The rest of this tutorial will focus on the particle initialization within the createParticles() method.

If we use this particle emitter and make it move in a circular motion, this is what we'll get:

Common-Implementation-Slow

Let's Make It Faster

Looks fine, right? Let's see what happens if we increase the emitter's movement speed:

Common-Implementation-Fast

See the discrete point "bursts"? These are due to how the current implementation assumes that the emitter is "teleporting" to discrete points across frames. Also, new particles within each frame are initialized as if they are created at the same time and bursted out at once.


Sub-Frame Interpolation to the Rescue!

Let's now focus on the specific part of code that results in this artifact in the PointEmitter.createParticles() method:

To compensate for the discrete emitter movement and make it look as if the emitter movement is smooth, also simulating continuous particle emission, we are going to apply sub-frame interpolation.

In the PointEmitter class, we'll need a Boolean flag for turning on sub-frame interpolation, and an extra Point for keeping track of the previous position:

At the beginning of the PointEmitter.update() method, we need a first-time initialization, which assigns the current position to prevPosition. And at the end of the PointEmitter.update() method, we will record the current position and save it to prevPosition.

So this is what the new PointEmitter.update() method looks like (the highlighted lines are new):

Finally, we'll apply sub-frame interpolation to particle initialization in the PointEmitter.createParticles() method. To simulate continuous emission, the initialization for particle position now linearly interpolates between the emitter's current and previous position. The particle lifetime initialization also simulates the "time elapsed" since the last frame up till the particle's creation. The "time elapsed" is a fraction of dt and is also used to integrate the particle position.

We will therefore change the following code inside the for loop in the PointEmitter.createParticles() method:

...to this (remember that i is the loop variable):

Now, this is what it looks like when the particle emitter is moving at high speed with sub-frame interpolation:

Sub-Frame-Interpolation-Fast

Much better!


Sub-Frame Interpolation Is Not Perfect

Unfortunately, sub-frame interpolation using linear interpolation is still not perfect. If we further increase the speed of the emitter's circular motion, this is what we'll get:

Sub-Frame-Interpolation-Super-Fast

This artifact is caused by trying to match the circular curve with linear interpolation. One way to remedy this is not to just keep track of the emitter's position in the previous frame, but instead to keep track of previous position within multiple frames, and interpolate between these points using smooth curves (like Bezier curves).

In my opinion, however, linear interpolation is more than enough. Most of the time, you won't have particle emitters moving fast enough to cause sub-frame interpolation with linear interpolation to break down.


Conclusion

Particle effects can break down when the particle emitter is moving at a high speed and has a high emission rate. The discrete nature of the emitter becomes visible. To improve the visual quality, use sub-frame interpolation to simulate smooth emitter movement and continuous emission. Without introducing too much overhead, linear interpolation is usually used.

However, a different artifact would start showing up if the emitter moves even faster. Smooth curve interpolation can be used to fix this problem, but linear interpolation usually works well enough and is a nice balance between efficiency and visual quality.

Advertisement