Skip to content

134. Particles – Smoke

November 3, 2013

When I was looking at physics, there was quite a lot on particle systems. I didn’t want to go down the fireworks route, because that’s been done by several people already, and probably the best is here.

So, smoke it is.

There’s quite a lot of fun new stuff in this post, anyway. It does require some shader knowledge, if you want to fully understand it.

Smoke is really difficult to simulate, because it swirls around, rises up, spreads out, has a mix of gray colours, and isn’t a nice circular or rectangular shape.

Generating a smoke image with terrain mapping

I started by creating an image that I could use for all my smoke. I could have used the noise function in Codea (see the demo project for what it can do), but I had recently found some really nice code for creating random terrain (ie bumpy surface).

You’ll find this code in the Heightmap2 tab of the code at the bottom. It creates random terrain using the “diamond square” algorithm (more here).

Because of the way it works, the width and height need to be a power of two. The code will actually handle any size, but what it does is round up to the next power of two, create a table, then discard anything you didn’t ask for. I started by asking for a 512 x 512 square of data (which is a power of two, so I don’t waste any data!).

What you get back is a table (in my case, 512 x 512) of values, all of them between -1 and +1. So what do I do with that? Let’s see.

I have a special function that does all this, below. It first takes a size parameter (512) and creates a table of height values as described above, in step [1].

In step [2], we loop through the height table and find the min and max values. What we are going to do is replace the minimum value with the smallest gray colour we want to use, and replace the maximum value with the largest gray colour we want to use (and interpolate in between).

In step [4], we set the smallest and largest grays in our smoke colours.

In step [5], we loop through the table and convert the height values into gray colours, storing them in the image.

function Smoke.createImage(size)
    --[1] create height map
    --the 0.3 parameter is how noisy it is
    --it returns a table m[size][size]
    local m=hmap.create(size,size,0.3)
    --map the height values to colour values
    --[2] find min and max height values
    local z1,z2=0,0
    for i=0,m.w do
        for j=0,m.h do
    --[3] create an image to play with
    --m.w and m.h are width and height of table
    local img=image(m.w,m.h)
    --[4] set min and max range of gray colours
    --can be anything between 0 and 255
    local min,max=100,255
    --[5] rescale height table and put in image
    local f=(max-min)/(z2-z1)
    for i=1,m.w do
        for j=1,m.h do
            local intensity=min+f*(m[i][j]-z1)

So now I have an image like this.

And now you can see why I used a special algorithm. The image isn’t a mess of random dots. Instead it seems to swirl like smoke. This is because the values for each pixel are based on the values of its neighbours, so the colours change smoothly.

I wanted it big so I could choose small pieces at random and they wouldn’t all look the same. The price of a big image is that it takes a few seconds to create the image, at the beginning (although I guess you could create the image just once, the first time, save it to Documents, then just use that forever).

Simulating a smoke puff

Next, I tried simulating just one puff of smoke at a time. I’m going to put each one in its own mesh, because although I may have several on the screen at once, and they will all use the same texture image (above), they will move independently.

I’ll also create a Smoke class, so I can have several puffs at once. So here is the basic code for adding a mesh, inside the init function that sets up the class. The mesh is placed at the source of the smoke.

The tx1 and ty1 variables choose a random starting place for the texture values, in our smoke image.

The final line attaches a shader, for reasons I’ll explain below.

local w=Smoke.img.width
local x1,y1,x2,y2=-size/2,-size/2,size/2,size/2
local tx1=math.random()*(1-size/w)
local ty1=math.random()*(1-size/w)
local tx2,ty2=tx1+size/w,ty1+size/w
v[1]=vec2(x1,y1) t[1]=vec2(tx1,ty1)
v[2]=vec2(x2,y1) t[2]=vec2(tx2,ty1)
v[3]=vec2(x2,y2) t[3]=vec2(tx2,ty2)
v[4]=vec2(x1,y2) t[4]=vec2(tx1,ty2)
v[5]=vec2(x1,y1) t[5]=vec2(tx1,ty1)
v[6]=vec2(x2,y2) t[6]=vec2(tx2,ty2)

Animating the smoke puff

I want the smoke puff to start small and expand to a maximum size, then fade. For fun, I’m going to simulate anti aircraft explosions in the sky.

I have two inputs, explodeSecs and fadeSecs, which tell my class how long it takes for the smoke to expand fully, and then how long it takes to fade away (and then I’ll delete it).

So I thought I’d use a tween to animate the smoke. I think of tweens as simply being little loops that change a variable over a given number of seconds.

local t1=tween(explodeSecs,self.f,{frac=1},
        { easing = tween.easing.quadOut } )
local t2=tween(fadeSecs,self.f,{fade=0},
        { easing = tween.easing.linear } )

I first create a little table, self.f, to hold two variables

  • frac = how big the smoke is as a fraction 0-1
  • fade = how opaque the smoke is, 0-1

Then the t1 tween animates the frac value from 0 to 1, over explodeSecs seconds, and the t2 tween animates the fade value from 1 to 0 over fadeSecs seconds. The tween.sequence command chains the tweens together so they run one after the other.

So if explodeSecs is 20 seconds, and fadeSecs is 10 seconds, the smoke image will grow from a size of 0 to its full size over 20 seconds, and then it will fade over the next 10 seconds until it is transparent.

Managing the explosions

I need some code to create random explosions, and here it is. The Smoke class takes 4 inputs – position (vec2), size, time to explode fully, and time to fade.

function setup()
    shellbursts={}  --table of explosions
    --random time to next explosion

function draw()
    --check if it's time for a new explosion
    if nextExplosion<0 then
        local s=Smoke(
        --set timer for next explosion
    --draw current explosions
    background(117, 154, 181, 255)
    for i,s in pairs(shellbursts) do
        --if false is returned, the explosion is finished
        --we can remove it from our table
        if s:draw()==false then table.remove(shellbursts,i) end

Drawing the smoke itself

This is the important part, after creating the smoke image. I want a circular puff of smoke, that fades toward the edges, which can be any size, but always has its centre at the same place in the smoke image.

So imagine choosing a random point on the smoke image. We draw a 1 pixel circle around it, then 2 pixels, then 3, expanding outward until we reach the final size we chose. This is where the shader comes in, because it will only draw pixels which are inside these circles, making the smoke appear to expand steadily.

So in the Smoke class, I’ve given the shader four values to work with

  • frac = fraction of full size
  • fade = opacity (0=transparent, 1 = opaque)
  • size = size of smoke puff *
  • centre = vec2, centre of smoke puff *

* as fraction of smoke image size

The first two are being updated by the tweens all the time, so I need to update the shader values in the draw function of the Smoke class, before drawing the mesh.

The fragment shader

Below is the code for the fragment shader. I’ve numbered the lines and will explain below.

uniform lowp sampler2D texture;
uniform float size;
uniform float frac;
uniform float fade;
uniform vec2 centre;

varying lowp vec4 vColor;
varying highp vec2 vTexCoord;

void main()
lowp vec4 col = texture2D( texture, TexCoord ) ; //[1]
float f = length( centre - vTexCoord ) / ( size * frac); //[2]
if (f >= 0.5 ) discard; //col.a = 0.0;  //[3]
else col.a = 2.0 * ( 0.5 - f ) * fade;  //[4]
gl_FragColor = col;

Line 1 looks up the pixel in the smoke image.

Line 2

  • calculates the distance from that pixel to the centre of the smoke puff (the length part)
  • divides by (puff size x fraction expanded)  so the distance is a fraction of the current size of the smoke puff, rather than of the size of the whole smoke image

Line 3 discards the value if it’s more than 0.5, because it is outside the radius of our imaginary circle

Line 4 applies the fading, if any. We multiply by 2 because the maximum value of f is 0.5 and we want it to be 1.

The full code is here.

And now you will find it produces explosions, but not the nice column of smoke in the video at the top of this post.

That’s because we’re only halfway.

One Comment
  1. Saurabh permalink

    Just one suggestion. While tweeting you don’t need to create a new table self. f, you could as well use self as the table with self. fade as the variable in it. Since self is itself a table.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: