 # How to Simulate Smoke with Shaders

This post will show how to simulate the diffusion of smoke using shaders. This part of the tutorial focuses on the Maths and the code necessary to recreate the smoke effect. To learn how to set up your project, check out the first part: How to Use Shaders For Simulations. ### Introduction

Creating realistic smoke in games has always been a challenge. The reason behind this is the fact that the large scale behaviour of smoke is determined by the complex interactions of billions of tiny particles, floating in air. Throughout the history of game development there have been many attempts to simulate smoke, mostly based either on particles or trails (animation below). Neither of these, however, are able to reproduce with fidelity the actual behaviours of a fluid when exposed to perturbations. To compensate for that, we need to simulate fluid dynamics. Like it happens with physics, you don’t actually need to simulate every aspects of fluid dynamics; you want something that looks good, but that is not too computational intensive.

### Part 1. The Maths

There are two main approaches to fluid dynamics: Lagrangian and Eulerian. While the former uses virtual particles to simulate the moving particles in a fluid, the latter divides the space into a grid. For this tutorial, we will focus on a simple grid-based smoke simulation. The value of each cell in the grid indicates the flow (or “amount of smoke”) contained; you can imagine it as a way of approximating its “density” in a given space. Simulating how the smoke behaves is now a matter of understanding how the density diffuses between adjacent cells.

Another assumption we introduce is that a cell can exchange flow only with its four immediate neighbours. This given a good approximation and reduces the number of texture lookups we need to perform for each pixel to five.

There are two components that determines how diffusion works. The first one is the incoming flow from the four neighbour cells: The outgoing flow of each cell is divided equally to its four neighbours, hence the coefficient.

The second component is the the outgoing flow . Not all the flow is transmitted at once, so we’ll use the diffusion coefficient to indicate the rate at which the amount of smoke in a cell is diffused to its four neighbours: To sum it up, the net balance of after a diffuse iteration is: The post How to Use Shaders for Simulations shows how a fragment shader can be used to iterate over a texture. We will use the same technique, encoding the flow (or amount of smoke) into the alpha channel of the main texture _MainTex. This allows to easily integrate a smoke effect into your game by simply overlaying a textured quad.

#### Grid representation

The first problem we encounter is the fact that we are using a grid-based approach, but this is a concept that is not present in a shader. Yes, images are indeed made out of pixels, but this is a concept that is not that easily accessible in a fragment shader. If we are using a standard Unity quad for this experiment, the pixels drawn by the shader are addressed by the quad UV. By knowing the size of the render texture we are using, we can use the value to find out which pixels we’re currently drawing. As a general approach, we will force the UV values of the fragment shader to assume values at fixed intervals, simulating a grid:

fixed2 uv = round(i.uv * _Pixels) / _Pixels;
half s = 1 / _Pixels;

The variable s indicates the distance between two cells. Now we can sample a texture like we can do with a grid:

// Neighbour cells
float cl = tex2D(_MainTex, uv + fixed2(-s,  0)).a;	// F[x-1, y  ]: Centre Left
float tc = tex2D(_MainTex, uv + fixed2( 0, -s)).a;	// F[x,   y-1]: Top Centre
float cc = tex2D(_MainTex, uv + fixed2( 0,  0)).a;	// F[x,   y  ]: Centre Centre
float bc = tex2D(_MainTex, uv + fixed2( 0, +s)).a;	// F[x,   y+1]: Bottom Centre
float cr = tex2D(_MainTex, uv + fixed2(+s,  0)).a;	// F[x+1, y  ]: Centre Right

If you prefer, you can use a more intuitive grid-like notation:

#define ARRAY(T,X,Y) (tex2D((T), uv + fixed2(s*(X), s*(Y))))
float cc = ARRAY(_MainTex, 0,0).a; // F[x+0, y+0]: Centre Centre

The last step is to implement the diffusion step:

float4 frag (v2f_img i) : COLOR
{
// Cell centre
fixed2 uv = round(i.uv * _Pixels) / _Pixels;

// Neighbour cells
half s = 1 / _Pixels;
float cl = tex2D(_MainTex, uv + fixed2(-s, 0)).a;	// Centre Left
float tc = tex2D(_MainTex, uv + fixed2(-0, -s)).a;	// Top Centre
float cc = tex2D(_MainTex, uv + fixed2(0, 0)).a;	// Centre Centre
float bc = tex2D(_MainTex, uv + fixed2(0, +s)).a;	// Bottom Centre
float cr = tex2D(_MainTex, uv + fixed2(+s, 0)).a;	// Centre Right

// Diffusion step
float factor =
_Dissipation *
(
0.25 * (cl + tc + bc + cr)
- cc
);
cc += factor;

return float4(1, 1, 1, cc);
}

Running this exact fragment shader, however, will not work as expected. Floating point arithmetic will collapse small numbers to zero, eventually stopping the smoke from flowing. To avoid this, Omar Shehata suggests in How to Write a Smoke Shader to enforce a minimum flowing quantity:

// Minimum flow
if (factor >= -_Minimum && factor < 0.0)
factor = -_Minimum;
cc += factor;

This provides a nice fluid dynamic which uniformly diffuses smoke in every direction, until it disspates. ##### ⭐ Suggested Unity Assets ⭐
Unity is free, but you can upgrade to Unity Pro or Unity Plus subscriptions plans to get more functionality and training resources to power up your projects.

### Simulating turbulences

The formula derived causes smoke to diffuse uniformly and in all directions. If we want the smoke to move up, we need to promote transmission to the upper cell. In general, we can add four coefficients for each cell ( , , and ) that indicates the amount of flow to transmit in each direction. When , we obtain the equation derived in the previous paragraph. If we set to a higher value, we simulate an upward movements of the smoke. By using this improved equation we can simulate obstacles, for instance by setting certain coefficients to zero, preventing the diffusion of the flow in certain regions. Updating these velocities at runtime can also simulate winds and other turbulences in the fluid medium where the smoke is moving.

All the four coefficients can be baked into an additional texture, called _VelocityTex, which accommodates , , and in its RGBA components, respectively.

The technique shown in this tutorial is perfect to simulate diffusion phenomenon. Both smoke, water and temperature follows a similar pattern. The series Creeper World bases its gameplay entirely on a similar approach; a sentient blob is expanding, following exactly the diffusion algorithm explored in this tutorial.

What is really missing from this effect is the relationship between the float and the velocity. Our solution keeps these two entities separate, but in a real scenario this is not the case. The state of the art solution when it comes to fluid simulation is given by the Navier-Stoke equations, which described fluid dynamics with incredible precision. Unfortunately, they are rather complicated and GPU intensive. For a primer on this technique, you can refer to Fast Fluid Dynamics Simulation on the GPU.

The next part of this tutorial (How to Simulate Cellular Automata with Shaders) will iterate on grid-based technique to implement one of the most interesting cellular automata: Conway’s Game of Life. Cellular automata will be used in a later tutorial to simulate the flow of water.

#### Other resources

##### 💖 Support this blog

This websites exists thanks to the contribution of patrons on Patreon. If you think these posts have either helped or inspired you, please consider supporting this blog. You will be notified when a new tutorial is relesed!

##### 📝 Licensing

You are free to use, adapt and build upon this tutorial for your own projects (even commercially) as long as you credit me.

You are not allowed to redistribute the content of this tutorial on other platforms. Especially the parts that are only available on Patreon.

If the knowledge you have gained had a significant impact on your project, a mention in the credit would be very appreciated. ❤️🧔🏻

1. pete nicholson

Hi Alan, I’m getting the error (srcAttach < m_CurrentFramebuffer.colorCount && "We should always resolve only current RT") when running the project.

• pete nicholson

I did some investigating and a colleague told me this is a Mac OS specific bug and to get around you need a RenderTexture.active = null (or turn off anti-aliasing on the RT).

{
public Material material;
public RenderTexture texture;
private RenderTexture buffer;

public Texture initialTexture; // first texture

void Start ()
{
Graphics.Blit(initialTexture, texture);
RenderTexture.active = null;

buffer = new RenderTexture(texture.width, texture.height, texture.depth, texture.format);
}

// Postprocess the image
public void UpdateTexture()
{
Graphics.Blit(texture, buffer, material);
Graphics.Blit(buffer, texture);
RenderTexture.active = null;

}

private float lastUpdateTime = 0;
public float updateInterval = 0.1f; // s
public void Update ()
{
if (Time.time > lastUpdateTime + updateInterval)
{
UpdateTexture();
lastUpdateTime = Time.time;
}

}
}

• pete nicholson

I did a slight hack to get the smoke to match up with the mouse point also in the SmokeShader.shader

Line 95: if (distance(i.wPos, (_SmokeCentre+0.5)) < _SmokeRadius)

• Alan Zucconi

Thank you!

2. Lokomatov

Hey Alan
the link to “How to Use Shaders for Simulations” isn’t working, same on cellular tutorial. But if you use google you’ll find another page in the website with the same title.
Thanks for the cool stuff.

• Alan Zucconi

Hey!
What happens if you visit the page? Broken link?
I tried and seems they are working on the machine I am using!

3. Daniel

Hey, Alan, great tutorial.
Could you explain what exactly is _Pixels ? What type it is? How should I declare it in Properties and in Pass scopes? In Properties, I put
_Pixels(“Base (RGB) Trans (A))”, 2D) = “white” {}
and in Pass I put
sampler2D _Pixels
but the line fixed2 uv = round( i.uv * _Pixels ) / _Pixels; didn’t work.
Thank you!

### Webmentions

• How to Simulate Cellular Automata with Shaders - Alan Zucconi July 17, 2020

[…] Part 2. How to Simulate Smoke with Shaders […]

• How to Use Shaders for Simulations - Alan Zucconi July 17, 2020

[…] next part of this tutorial (How to Simulate Smoke with Shaders) will focus on how this technique can be used to simulate the diffusion component of particles […]