Advertisement

Adding Turbulence to a Particle System

by

You can create a variety of effects with particle systems. In this article I'm going to describe a simple way to improve them by adding some turbulence. I'll start with a 2D particle system for simplicity. Code examples are in Javascript, but they are mostly math, so it's easy to adapt them to any other language.



Regular Particle System

Let's start by making a regular particle system in 2D. We'll use simple sprites with:

  • Position: x, y.
  • Velocity: vx, vy.
  • Scale.
  • Alpha (transparency).
  • Lifetime, so we know when to re-spawn the particle.
  • Updating Particles

    For each particle, we update velocity first. In this case, we just damp it a bit to make motion more interesting and realistic. Forces like gravity or wind can be applied here as well.

    var damping=1-0.005
    this.vx*=damping
    this.vy*=damping

    Then, we update the position using the velocity. We'll use fixed time step for this tutorial. If your time step varies, you'll need to multiply the velocity by your time delta to get change in position.

    this.x+=this.vx
    this.y+=this.vy

    We scale particles a bit to simulate expanding smoke puffs.

    this.scaleAll(1.01)

    We reuse particles instead of removing and adding them since we want constant flow here:

    this.life-=1
    if (this.life<=0) this.respawn(particleLifetime)

    And finally, we update particle transparency to fade them out over time.

    this.alpha=this.life/particleLifetime

    The respawn method just sets initial values for the particle. It's also used to populate the particle system at start-up.

    this.x=jaws.width*(Math.random()*0.1-0.15)
    this.y=jaws.height*(0.45+Math.random()*0.1)
    this.vx=Math.random()*2+4
    this.vy=(Math.random()+Math.random()+Math.random()-1.5)*2
    this.life=life
    this.scaleTo(1)

    Adding Vortices

    The defining characteristic of turbulence is the vorticity. So the simple and straightforward way to simulate turbulence is to create a vortex object which affects particle motion.

    Let's start by calculating the velocity in the vortex, like if it'd be a solid wheel.

    Velocity at the point on the wheel.

    Velocity at the point on the wheel.
    Wheel image by Pearson Scott Foresman from Wikimedia Commons.

    First, we need a vector from the vortex center to the particle:

    var dx=particle.x-vortex.x
    var dy=particle.y-vortex.y

    Then we rotate that vector 90 degrees to make an orthogonal one and multiply it by the vortex rotation speed:

    var vx=-dy*vortex.speed
    var vy= dx*vortex.speed

    Great, we have the velocity that the vortex is trying to force on the particles, but how do we apply it?
    We need some weighting factor which will be 1 in the center of the vortex and will fade out with distance.
    We'll use this ad-hoc formula, which is based on inverse square distance, but avoids singularity at the center:

    var factor=1/(1+(dx*dx+dy*dy)/vortex.scale)
    Weighting factor curve.
    Weighting factor curve.

    Let's apply the vortex velocity by blending it with the particle one:

    particle.vx+=(vx-particle.vx)*factor
    particle.vy+=(vy-particle.vy)*factor

    That's it, we have a vortex affecting particles now!


    Moving Vortices

    A single static vortex is good for figuring out how to apply it to the particles, but we want multiple vortices, and we want them moving around. Let's just make them into another (invisible) particle system! So, now they have velocity and life time as well. That'll need some changes to the code.

    When calculating the velocity in the vortex, we need to add the vortex movement as well:

    var vx=-dy*vortex.speed+vortex.vx
    var vy= dx*vortex.speed+vortex.vy

    And we want the vortices to fade in and out smoothly, not just pop suddenly in and out of existence. Let's apply a simple curve to the weighting factor:

    var lifeFactor=vortex.life/vortexLifetime
    factor*=(1-lifeFactor)*lifeFactor*4
    Scaling vortex with its life time.
    Scaling vortex with its life time.

    What Can Be Done With Vortex Motion

    In the example for this tutorial, I'm using several vortices which move similar to the particles but have a bit shorter lifetime. However, moving vortices in different ways, as well as tuning their rotation speed and range, can produce a plethora of different effects.

    For example, since a vortex tries to move particles with it, a static vortex will try to stop particles flying through it and put them into a swirl. You can use this effect to create a turbulent area which slows down any particles that come into it.

    You can create vortices when a character moves in the game, or when an object smashes into the ground.

    Kármán vortex street caused by wind flowing around the Juan Fernández Islands off the Chilean coast.

    Kármán vortex street caused by wind flowing around the Juan Fernández Islands off the Chilean coast.
    Image by Bob Cahalan from Wikimedia Commons.

    A common turbulence effect is vortex street which is easy to recreate as well, for a static object in the flow or for a fast moving object.

    You can also collide vortices with the environment or let the character motion affect them.


    Improvements

    I didn't rotate the particles in the example. It's easy to add and it improves the regular particle system, but it can be further enhanced by using vortices. Just blend particle rotation speed with the vortex like we did with the linear velocity.

    You can improve vortex motion by letting other vortices affect it, just like with particles.

    Different vortex weighting functions can produce different results. I just used an ad-hoc one which worked for this example, but you might find others more fitting for your case. Pretty much anything fading out with the distance from the center will do.


    Going 3D

    The technique is easy to use in 3D as well. Use 3D vectors for positions and velocities (including vortex angular velocity), and compute the velocity in the vortex using cross product:

    Vector3 r=particle.position-vortex.position;
    Vector3 v=Vector3.Cross(vortex.angularVelocity, r);
    Velocity in the vortex is computed with cross product.
    Velocity in the vortex is computed with cross product.

    Other Ways to Do Turbulence

    Of course, it's not the only way to do turbulence and not the only way to do vortices. Let's look at some others.

    Embedded Vortices

    We were using the list of vortices at the level of the particle system. It's fairly efficient, but you can store a vortex (or several) in each particle.

    While it needs more memory, it improves data locality and allows customizing particle motion even more. You can randomize the vortex a bit for each particle or use several layers of vortices to make 2D particle system look more 3D.

    Global Vortices

    Instead of storing the list of vortices in the particle system, it can be made global. This saves memory and CPU time updating vortices. It's also easy to add new vortices caused by character motion, collide vortices with environment etc.

    Fluid Simulation

    You can go a more realistic route and do actual physical simulation of the flow. It's likely too heavy for anything moderately-sized in 3D without GPU computing, but quite feasible in 2D. There are grid-based methods (you'll want multi-resolution ones for performance) and particle-based ones. Also, look into adding back lost vorticity using noise, there are papers on that.

    Vorticity Grid

    Instead of doing a full physical simulation or particle-like vortices, you can store the vorticity in a grid. Store angular and linear velocities and propagate them on each time step. Then, in your particle system, sample the grid to find the vortex velocity affecting the particle.

    It's not as realistic as fluid simulation, but it's much faster since you don't need to solve full-grid pressure equations, just propagate and damp down velocities.

    Procedural Noise

    If you just want a lot of chaotic turbulence without pronounced vortices ("bubbling"), using procedural noise might be a better idea. Look for Perlin or simplex noise. You'll need an extra dimension so you can animate the noise. So for 2D you'll need 3D noise scrolled in the third dimension with time, and 4D noise for 3D.

    You can sample the noise several times at some fixed offsets to get components of the force affecting your particle.


    Conclusion

    Now you can enhance your particle effects by adding a secondary system of vortex-particles. Imagine an explosion caused by a rocket forcing nearby smoke to whirl around the blast radius, or leaves swirling as they float down a river. Experiment with how you spawn and move vortices to achieve the desired effect or discover something new!

Advertisement