Easy volumetric explosion in Unity3D

When Unity3D realeased their Unity4 update, they realeased a tech demo along with it: The Butterfly Effect

Unity4 tech demo: The Butterfly Effect

After seeing that, it made me want to make a volumetric explosion like the one you can see at the end.
So that’s what I did.

No DirectX11

Now they made that with directx 11, and my graphics card doesn’t support that, so I can’t really do the same.
So instead I tried to develop a simple way to create something that looks similar, without using any fancy stuff.

Here’s what the end result looks like:

explosions  Capture72
(ignore the weird scene-setup please)

Now I’m not saying the explosion I made looks as good as the one in the butterfly effect,
but it is volumetric, fairly inexpensive, doesn’t use alpha blending (so no sorting issues), and it writes correctly to the depth buffer.

And I like it, it looks kinda cartoony, and maybe too round, but I think it can work.

So How does it work?

In essence, it’s a sphere with displacement maps that interpolate over time,
And the color (and emission) is in relation to the extrusion amount (displacement), by using a ramp texture.

How to make it:

I’ll explain it step by step, but keep in mind that I assume Unity3D and basic¬†game development and programming knowledge. (as well as basic Unity shader knowledge)

Step 1, making a sphere

I started of in Blender, but I’m sure most 3D modelling tools can do the same.

First create a sphere, with enough subdivisions for the displacement to work.
I used an Icosphere, instead of the default UV sphere, because vertices are more evenly spread across the surface.

Unwrap it, and make sure all vertices are within bounds.
Default automatic unwrapping should be sufficient, all 3D modelling tools should be able to unwrap a sphere properly.

Step 2, creating the displacement maps

Apply a material on the sphere with a couple of procedural textures (grayscale).
Bake these into a texture.
Make 2 more variations of these procedural textures and bake these as well.

Using Gimp or Photoshop (or anything simmilar), combine these grayscale textures into 1 texture, by placing each into a different channel (RGB), creating something like this:
You could make a 4th texture and place it into the Alpha channel if you want, but I didn’t bother.

Step 3, creating the ramp texture

My ramp texture looks like this:

The ramp texture is used to determine the colors of the explosion, the alpha value is used for emission.
The left pixels are used for the areas of the explosion that are only slightly extruded, the right pixels are used for the areas that are most extruded.

Step 4, creating the shader (partially)

The shader I made is a surface shader, that way the inemissive parts are properly lit, but If you don’t want that, you can write it as a simple vertex/fragment shader.

Start off with 4 properties:
_RampTex (“Color Ramp”, 2D) = “white” {}
_DispTex (“Displacement Texture”, 2D) = “gray” {}
_Displacement (“Displacement”, Range(0, 1.0)) = 0.1
_ChannelFactor (“ChannelFactor (r,g,b)”, Vector) = (1,0,0)

In the Vertex shader we offset the vertices based on a texture lookup using the displacement texture.
The color we get from this contains the displacement value according to all 3 displacement textures (RGB).
By using the float3 property I called _ChannelFactor (that is set in a script each frame, see lower), we determine the actual displacement, by taking the sum of the 3 values multiplied by the corresponding RGB value.

In the Surface shader, we do the same texture lookup,
using that value as the UV.x (UV.y can be anything), we do another lookup using the ramp texture.
We assign that color as the Albedo color, and the color multiplied by the alpha as Emission.
We could also assign the color in the Vertex shader, but then the colors would be interpolated between the vertices, whereas if we calculate the color in the surface/fragment shader, we get a correcter result.
But assigning it there is faster, as it would discard the need to do the displacement texture lookup in the surface shader again, so decide yourself what’s more important.

Step 5, animating the displacement

Now create a script, attached to the explosion, with a single public variable, the loopduration,
in the update loop we will determine the ChannelFactor, and assign it to the shader.
I used 3 sin functions (1 for each channel), and made sure their sum is 1 (though this is not necessary).

float r = Mathf.Sin((Time.time / loopduration) * (2 * Mathf.PI)) * 0.5f + 0.25f;
float g = Mathf.Sin((Time.time / loopduration + 0.33333333f) * 2 * Mathf.PI) * 0.5f + 0.25f;
float b = Mathf.Sin((Time.time / loopduration + 0.66666667f) * 2 * Mathf.PI) * 0.5f + 0.25f;
float correction = 1 / (r + g + b);
r *= correction;
g *= correction;
b *= correction;
renderer.material.SetVector("_ChannelFactor", new Vector4(r,g,b,0));

It should look something like this now:


Step 6, animating the explosion

Now the explosion loops endlessly, but we want it to grow and change color over time, and it needs to “fade out” somehow.

Add 2 more properties to the shader: the minimum value and maximum value for the ramp texture.
They determine the range of the ramp texture to use, by animating this (in an AnimationClip, or in code), you can change the look of the explosion over time.

Add another property to the shader: the clipping value.
The clipping value determines at what displacement amount to clip (cut holes in the mesh), by animating this (in an AnimationClip, or in code), you can fade out the explosion over time.
Unfortunately, this is a sharp cut, but because the displacement is still animated, the holes constantly change shape, making it not that big of an issue (IMO).

To make the explosion grow, simply animate it’s scale in an AnimationClip or in code.


Step 7, finishing touches

Add a point light inside the explosion (and animate this aswell).

You can also place a smoke particle system inside it, so that when the explosion starts clipping away, it looks like it fades into smoke.

I also made the shader 2-sided (clipping off), so that when the camera is inside the shell, you still see the explosion.

Using a Bloom or Glow post effect also helps sell the effect.

And that’s it, all done!

Full shader code:

Shader "Custom/Explosion" 
		_RampTex ("Color Ramp", 2D) = "white" {}
		_DispTex ("Displacement Texture", 2D) = "gray" {}
		_Displacement ("Displacement", Range(0, 1.0)) = 0.1
		_ChannelFactor ("ChannelFactor (r,g,b)", Vector) = (1,0,0)
		_Range ("Range (min,max)", Vector) = (0,0.5,0)
		_ClipRange ("ClipRange [0,1]", float) = 0.8

		Tags { "RenderType"="Opaque" }
		Cull Off
		LOD 300

		#pragma surface surf Lambert vertex:disp nolightmap
		#pragma target 3.0
		#pragma glsl

		sampler2D _DispTex;
		float _Displacement;
		float3 _ChannelFactor;
		float2 _Range;
		float _ClipRange;

		struct Input 
			float2 uv_DispTex;

		void disp (inout appdata_full v)
			float3 dcolor = tex2Dlod (_DispTex, float4(v.texcoord.xy,0,0));
			float d = (dcolor.r*_ChannelFactor.r + dcolor.g*_ChannelFactor.g + dcolor.b*_ChannelFactor.b);
			v.vertex.xyz += v.normal * d * _Displacement;

		sampler2D _RampTex;

		void surf (Input IN, inout SurfaceOutput o) 
			float3 dcolor = tex2D (_DispTex, IN.uv_DispTex);
			float d = (dcolor.r*_ChannelFactor.r + dcolor.g*_ChannelFactor.g + dcolor.b*_ChannelFactor.b) * (_Range.y-_Range.x) + _Range.x;
			clip (_ClipRange-d);
			half4 c = tex2D (_RampTex, float2(d,0.5));
			o.Albedo = c.rgb;
			o.Emission = c.rgb*c.a;
	FallBack "Diffuse"