​Creating Stylised Explosions

For my game Adrift Arena (made in Unity), I’m aiming for a certain look. My visual style naturally gravitates towards solid colours with soft lighting. So it makes sense that things that go boom should have a similar look and feel too!

Here is what an explosion looks like my game!





Goals when making this effect:

Make each explosion look unique.

The effect must be relatively cheap to run since there can be a lot of explosions

Be stylised and interesting to look at!

Make something that different to your usual particle based explosions

So here is a summary of how I created this effect!



To be able to follow, I assume you have a basic understanding of rendering in computer games and how to work with pixel and vertex shaders.

(If you want to cut to the chase, you can see the unity project here)



The Mesh

First, I need a sphere mesh to form the basis of the explosion.





Here are three possible types of sphere meshes. Left is a UV sphere, center is a geodesic sphere and right is a cube sphere (or a normalised/spherised cube).



Either a geodesic sphere or a “cube” sphere are good choices since both have fairly even distribution of vertices.

A UV sphere is not a good fit since all of the vertices clump around the poles. This makes smoke distortion look good around the poles and crap everywhere else.

I went with a “cube” sphere (to be specific, a “normalised cube” in my case) since its simple to procedurally generate and good enough for my use case.

Some experimentation was required to know how many subdivisions were needed to make the effect look good.

The Explosion

The actual explosion is nothing special, just an expanding sphere which has its color change over time according to a user defined gradient.

(The explosion color roughly mimics color temperture)







My custom bloom solution is enough to give the explosion enough of a visual pop. Determining how much bloom is a balancing act between looking cool and not obscuring the gameplay.





Bloom, just right.







Bloom, TOO MUCH AGH.



I also apply an “ease out” transformation to the rate of expansion to make the effect more believable. (This "ease out" transformation makes the explosion expansion start fast, but slow down towards the end of the animation). However, I cut off the end of the easing curve to prevent it coming to a full stop. This makes the transition into expanding smoke seamless.







The Smoke

The most interesting part of this effect is the smoke! I keep the same mesh as before, but I swap out the material so I can use a different shader.

Noise

Before I get into the details, let us talk about noise.

Random noise is the basis of many graphical effects and this smoke effect is no different.

The specific variant of noise required for this smoke effect is 3D value noise.

There are purely procedural approaches, such as this one, that make use of a hash function in the shader:

https://www.shadertoy.com/view/4dS3Wd

Or using 3D value noise encoded into a texture in a clever way (interpolating between adjacent planes of noise requires only one texture fetch)

https://www.shadertoy.com/view/4sfGzS

Distortion within the Vertex Shader

I need to make the explosion lose its spherical shape as it becomes smoke. I achieved this by distorting each vertex using 3D value noise within the vertex shader.

Vertex shader distortion pseudo code:

localPosition.xyz += pow(1.0+localPosition.y,3)* 0.25*_ElapsedTime*localPosition.xyz* ValueNoise3D(_VortexAxis0 + 2*localPosition.xyz);

pow(1.0+localPosition.y,3)

This part makes the smoke expand faster at the top of the sphere. This creates the illusion that the smoke is rising up into the air.



0.25*_ElapsedTime*localPosition.xyz

This part makes the smoke expand in the direction of the vertex over time. The mesh is a unit sphere, so localPosition is equivalent to the normal direction. _ElapsedTime is between the values 0 and 1.

ValueNoise3D(_VortexAxis0 + 2*localPosition.xyz);

This is where noise is added to create the distortion. I scale the localPosition by 2 to make the distortion more detailed. _VortexAxis0 is just a random direction to make each explosion distortion unique. You can think of it as a seed for a random number generator.



The image above shows the sphere being distorted by the vertex shader. White means more distortion (moved away from its orignal sphere position), black means less distortion (original sphere position).



Dissolve away

The smoke needs to dissipate in a believable way. Alpha blending can get expensive and has a bunch of annoying edge cases and issues I’d rather avoid. So I keep the material opaque and simply clip the pixel shader depending on a value of noise. But rather than just sampling a single value of noise, multiple values of noise are combined into one using a method is called fractal value noise.

This transforms the pixelated noise into something more smoke like and interesting. I tweaked the coefficients until I had something I liked the look of.

Pixel shader dissolve pseudo code here:

float thickness= 0.3*ValueNoise3D(2*localPos.xyz + _VortexAxis0); thickness += 0.3*ValueNoise3D(4*localPos.xyz + _VortexAxis0); thickness += 0.3*ValueNoise3D(6*localPos.xyz + _VortexAxis0); clip(0.15 + thickness - _ElapsedTime);

Smoke Swirls

Smoke is a fluid, and fluids like to swirl and flow. But rather than trying to run navier stokes equations to get that realistic fluid motion, I did something simpler, faster and hackier :P

Before clipping and calculating smoke “thickness”, I rotate the local position around 3 random axes within the pixel shader. The rotation increases over time to imitate the motion of the gas.

Swirl pseudo code here:

float AxisDistance(float3 pos, float3 axis) { return length(pos - dot(pos, axis)*axis); }

float3 ApplyVortex(float3 axis, float3 pos) { float l = smoothstep(0.2, 1.0, AxisDistance(pos, axis)); return mul(AxisAngleRotation(axis, 0.2*l*( 11.5 + 4*_ElapsedTime.x )), pos); }

localPos.xyz = ApplyVortex(_VortexAxis0,localPos.xyz); localPos.xyz = ApplyVortex(_VortexAxis1,localPos.xyz); localPos.xyz = ApplyVortex(_VortexAxis2,localPos.xyz);

Each random axis is unique to each explosion, so each explosion is swirls in a different way.

Left shows smoke thickness without any swirls. Right shows the above code distorting the smoke thickness to create swirls.



Lighting

I won't go into too much detail here, but I will mention something I've added recently.

Its a hack that approximates a sort of ambient occlusion effect. The more distorted a vertex is, the more likely it is to be a peak (highest point of a curve). If it is left alone, it is more likely to be in a valley (lowest point on a curve). If it is a peak, it would have less smoke surrounding it so more light can get to it, the opposite is true for valleys/minimas.



The left image shows what the smoke cloud looks like without this approximation, the right side shows it when applied. Notice how the peaks in the right smoke cloud are lighter. Its subtle, but adds a bit more depth to the smoke. Left image just looks flat by comparison.

You can see a working example of this explosion effect in this unity project here.



