VFX | Let’s Make a Flamethrower!

Introduction

Today I decided I wanted to make a flamethrower, because why not? I’ll be creating this effect to look as real as possible inside of Unity. I’ll then give anyone reading all the code/textures to implement this themselves.

Warning: This will not be a full tutorial, anyone that wishes to learn more about how to create the flamethrower from scratch can either contact me or needs to have moderate knowledge on particle creation to follow along.

Particle Shape

Flamethrowers have a cone-like shape that emits from a central point and expands in all directions, like a funnel. If you create a particle effect and mess with the “Shape” settings a little bit you should get something like this.

ParticleShape.gif

Material & Animation

Once you get the shape down to where you like it we’ll need it to actually look like fire. I find an additive shader makes for a nice blend, plus realistic fire helps a lot!

Create a Material inside your project and add this shader with this texture to it! I’ve also added some properties that you can tweak to your liking.

Shader "Effects/ParticleAdd" {
Properties {
	_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
	_MainTex ("Particle Texture", 2D) = "white" {}
	_ColorStrength ("Color strength", Float) = 1.0
	_InvFade ("Soft Particles Factor", Range(0.01,3.0)) = 1.0
}

Category {
	Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
	Blend SrcAlpha One
	Cull Off 
	Lighting Off 
	ZWrite Off 
	Fog { Mode Off}
	
	SubShader {
		Pass {
		
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_particles

			#include "UnityCG.cginc"

			sampler2D _MainTex;
			fixed4 _TintColor;
			fixed _ColorStrength;
			
			struct appdata_t {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				#ifdef SOFTPARTICLES_ON
				float4 projPos : TEXCOORD1;
				#endif
			};
			
			float4 _MainTex_ST;

			v2f vert (appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				#ifdef SOFTPARTICLES_ON
				o.projPos = ComputeScreenPos (o.vertex);
				COMPUTE_EYEDEPTH(o.projPos.z);
				#endif
				o.color = v.color;
				o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
				return o;
			}

			sampler2D _CameraDepthTexture;
			float _InvFade;
			
			fixed4 frag (v2f i) : COLOR
			{
				#ifdef SOFTPARTICLES_ON
				float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
				float partZ = i.projPos.z;
				float fade = saturate (_InvFade * (sceneZ-partZ));
				i.color.a *= fade;
				#endif
				
				return 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord) * _ColorStrength;
			}
			ENDCG 
		}
	}	
}
}

Fire texture sheet 8×8

Fire.png

Applying The Heat

Now that you have your material created go ahead and add it to the “Material” slot under “Renderer”.

Whoa! wait that doesn’t look good at all what gives?

You’re right, you see the texture above is a sprite sheet, in other words, it’s a series of images or collection of “frames” that make up an animated image, in our case fire, we have a total of 64 frames (8×8).

Let’s turn on the “Texture Sheet Animation” toggle on our particle system. Then in the Tiles property place X: 8 and Y: 8. Looks much better right?

You should now have something similar to this!

MaterialApplication.gif

Adding The Core

Now our particle is really starting to look like fire, it feels a bit too cold to be fire, though. You see fire usually has an extremely hot core, with the outside flickering out.

Let’s go ahead and duplicate our fire particle, change the radius of the shape to 0 create a new material, this time we’ll use an alpha blended shader, this will add the bright look to our core.

Here’s the shader:

Shader "Effects/AlphaBlended" {
	Properties {
	_TintColor ("Tint Color", Color) = (0.5,0.5,0.5,1)
	_ColorStrength ("Color strength", Float) = 1.0
	_MainTex ("Particle Texture", 2D) = "white" {}
	_InvFade ("Soft Particles Factor", Range(0.01,7.0)) = 1.0
}

Category {
	Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
				Blend SrcAlpha OneMinusSrcAlpha
				Cull Off 
				Lighting Off 
				ZWrite Off

	SubShader {
		Pass {
				
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_particles
			
			#include "UnityCG.cginc"

			sampler2D _MainTex;
			fixed4 _TintColor;
			fixed _ColorStrength;
			
			struct appdata_t {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
			};

			struct v2f {
				float4 vertex : POSITION;
				fixed4 color : COLOR;
				float2 texcoord : TEXCOORD0;
				#ifdef SOFTPARTICLES_ON
				float4 projPos : TEXCOORD1;
				#endif
			};
			
			float4 _MainTex_ST;

			v2f vert (appdata_t v)
			{
				v2f o;
				o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
				#ifdef SOFTPARTICLES_ON
				o.projPos = ComputeScreenPos (o.vertex);
				COMPUTE_EYEDEPTH(o.projPos.z);
				#endif
				o.color = v.color;
				o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
				return o;
			}

			sampler2D _CameraDepthTexture;
			float _InvFade;
			
			fixed4 frag (v2f i) : COLOR
			{
				#ifdef SOFTPARTICLES_ON
				float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
				float partZ = i.projPos.z;
				float fade = saturate (_InvFade * (sceneZ-partZ));
				i.color.a *= fade;
				#endif

				fixed4 tex = tex2D(_MainTex, i.texcoord);
				fixed4 res = 2 * i.color * tex * _TintColor * _ColorStrength;
				res.a = saturate(res.a);
				return res;
			}
			ENDCG 
		}
	}	
}

}

You should get something like this 🙂

CoreAdded.gif

Ah, much more full! This is starting to really come together. But what’s fire without smoke right? Let’s go ahead and add that next!

Time To Smoke

The best thing about the smoke particles is that they’re basically an exact copy of the core. The only difference is the Texture sheet you’ll be using! Go ahead and duplicate the core and create a new Material and add this texture to it and change the “Texture Sheet Animation” Tiles property to X:12 Y:7:

Smoke2.png

You should end up with something like this 🙂

SmokeParticles.gif

Nice and smoky right? This is definitely looking like real fire now. We just need to add a couple more finishing touches before we call it a day.

Finishing Touches: Distortion

Let’s Bill Nye real quick and talk about why we’re putting the final touches on this fire. Fire creates heat, heat rises. Now, heat causes the particles in the air to move faster, called particle acceleration. The acceleration causes things to get bigger or expand, which is why cakes rise in the oven, water can boil over, this expansion decreases density.

Now, onto my point! When you look at something, you’re actually just seeing reflected light from that object. When you look through a flamethrower and see the wall behind it, you’re seeing the light that was reflected off the wall, that light then traveled through the cold air behind the flamethrower through the hot air produced by the flamethrower back through cold air to hit your eyes, so when the light travels through different densities it gets refracted, exactly like looking at something through water.

Okay enough of that, let’s make things distorted!

The way I went about creating this shader wasn’t something trivial and took a lot of time to implement. Not to get too complex and wordy, I’m essential using the normal map, and the main texture to distort what’s behind it, this is done using the depth buffer. I have the shader calculate the distance from the depth texture to the camera this allows me to grab a position and then warp what’s rendered to the screen using a normal map.

Your distortion particle can be a duplicate of any particles you just created. Next, we should change the speed to be much slower than our fire particle, and change the radius to be a little bit bigger as this should just be the heat coming off of the fire.

Next, we create a Material and add our distortion shader to it along with another texture sheet with our Texture Sheet Animation property set to X:8 Y:4. (This feels extremely repetitive right now).

Here’s the shader

Shader "Effects/Distortion/ParticlesCutOut" {
Properties {
        _TintColor ("Tint Color", Color) = (1,1,1,1)
		_MainTex ("Base (RGB) Gloss (A)", 2D) = "black" {}
		_CutOut ("CutOut (A)", 2D) = "black" {}
        _BumpMap ("Normalmap", 2D) = "bump" {}
		_ColorStrength ("Color Strength", Float) = 1
		_BumpAmt ("Distortion", Float) = 10
		_InvFade ("Soft Particles Factor", Float) = 1.0
}

Category {

	Tags { "Queue"="Transparent"  "IgnoreProjector"="True"  "RenderType"="Transparent" }
	Blend SrcAlpha OneMinusSrcAlpha
	Cull Off 
	ZWrite Off 
	Fog { Mode Off}

	SubShader {
		GrabPass {							
			Name "_GrabTexture"
 		}
		Pass {
			Name "BASE"
			Tags { "LightMode" = "Always" }
			
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#pragma multi_compile_particles
#include "UnityCG.cginc"

struct appdata_t {
	float4 vertex : POSITION;
	float2 texcoord: TEXCOORD0;
	fixed4 color : COLOR;
};

struct v2f {
	float4 vertex : POSITION;
	float4 uvgrab : TEXCOORD0;
	float2 uvbump : TEXCOORD1;
	float2 uvmain : TEXCOORD2;
	float2 uvcutout : TEXCOORD3;
	fixed4 color : COLOR;
	#ifdef SOFTPARTICLES_ON
		float4 projPos : TEXCOORD4;
	#endif
};

sampler2D _MainTex;
sampler2D _CutOut;
sampler2D _BumpMap;

float _BumpAmt;
float _ColorStrength;
sampler2D _GrabTexture;
float4 _GrabTexture_TexelSize;
fixed4 _TintColor;

float4 _BumpMap_ST;
float4 _MainTex_ST;
float4 _CutOut_ST;

v2f vert (appdata_t v)
{
	v2f o;
	o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
	#ifdef SOFTPARTICLES_ON
		o.projPos = ComputeScreenPos (o.vertex);
		COMPUTE_EYEDEPTH(o.projPos.z);
	#endif
	o.color = v.color;
	#if UNITY_UV_STARTS_AT_TOP
	float scale = -1.0;
	#else
	float scale = 1.0;
	#endif
	o.uvgrab.xy = (float2(o.vertex.x, o.vertex.y*scale) + o.vertex.w) * 0.5;
	o.uvgrab.zw = o.vertex.zw;
	o.uvbump = TRANSFORM_TEX( v.texcoord, _BumpMap );
	o.uvmain = TRANSFORM_TEX( v.texcoord, _MainTex );
	o.uvcutout = TRANSFORM_TEX( v.texcoord, _CutOut );
	
	return o;
}

sampler2D _CameraDepthTexture;
float _InvFade;

half4 frag( v2f i ) : COLOR
{
	#ifdef SOFTPARTICLES_ON
		if(_InvFade > 0.0001)	{
		float sceneZ = LinearEyeDepth (UNITY_SAMPLE_DEPTH(tex2Dproj(_CameraDepthTexture, UNITY_PROJ_COORD(i.projPos))));
		float partZ = i.projPos.z;
		float fade = saturate (_InvFade * (sceneZ-partZ));
		i.color.a *= fade*fade;
	}
	#endif

	half2 bump = UnpackNormal(tex2D( _BumpMap, i.uvbump )).rg;
	float2 offset = bump * _BumpAmt * _GrabTexture_TexelSize.xy * i.color.a;
	i.uvgrab.xy = offset * i.uvgrab.z + i.uvgrab.xy;
	
	half4 col = tex2Dproj( _GrabTexture, UNITY_PROJ_COORD(i.uvgrab));
	half4 tex = tex2D(_MainTex, i.uvmain);
	half cut = tex2D(_CutOut, i.uvcutout).a;
	half4 emission = col * i.color + tex * _ColorStrength * _TintColor * i.color * i.color.a;
    emission.a = _TintColor.a * cut;
	return emission;
}
ENDCG
		}
	}

	SubShader {
		Blend DstColor Zero
		Pass {
			Name "BASE"
			SetTexture [_MainTex] {	combine texture }
		}
	}
}

}

Main Texture

Aura2.png

Cutout

SmokeTileCutOut.png

Normal Map

Aura2_Normal.png

You’re all done! Look at your pretty new flamethrower! Now go burn some people! 🙂

Final Result

Flamethrower_Final.gif

Happy coding! 🙂

Shader HLSL | Outline Pixel Art Shader

Introduction

This shader was made for Zeoland, a 2D retro style tactics game, still in development. I’m in charge of all the programming and effects. Raul, the game’s artist, wanted each character when selected to have a white outline, this both has a unique look and allows for a clearer visual effect for the player.

The problem was the outline was going to be done by hand and an additional animation would need to be provided for every character. This would cost a lot of resources, I decided that a shader would be necessary to reduce the extra asset load.

As I started thinking about solving the problem with a shader, I realized it would be fairly simple to create this effect. I just needed all the frames of the animation to have a 1-pixel alpha border. This would allow me to return a color (white) in the frag function instead of the alpha.

Here you can see the finished product of the outline shader.

tumblr_inline_olc2y4QmMP1toto34_500.gif

Here’s the simple shader, to use it in Unity create a material called “SpriteOutline”, then apply the material to any sprite you wish to have an outline.

Shader "Sprite/Outline" 
{
	Properties 
	{
	  	_MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)

        _Outline ("Outline", Float) = 0
        _OutlineColor ("Outline Color", Color ) = (1,1,1,1)
	}

	SubShader 
    {
 
        Tags 
        { 
        	"Queue"="Transparent" 
        	"IgnoreProjector"="True" 
        	"RenderType" = "Transparent" 
        	"PreviewType"="Plane"
        	"CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
 
            #include "UnityCG.cginc"

             sampler2D _MainTex;
             float4 _MainTex_ST;
             float4 _MainTex_TexelSize;
             float4 _Color;

             float _Outline;
             float4 _OutlineColor;

			struct v2f 
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
            };

            v2f vert (appdata_base IN)
            {
                v2f OUT;

                OUT.pos = mul (UNITY_MATRIX_MVP, IN.vertex);
                OUT.uv = TRANSFORM_TEX(IN.texcoord, _MainTex);

                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
            	fixed4 col = tex2D(_MainTex, IN.uv) * _Color;

            	if ( _Outline > 0 && col.a == 0)
            	{
            		fixed4 pixelUp = tex2D( _MainTex, IN.uv + fixed2(0,_MainTex_TexelSize.y));
                	fixed4 pixelDown = tex2D( _MainTex, IN.uv - fixed2(0,_MainTex_TexelSize.y));
                	fixed4 pixelRight = tex2D( _MainTex, IN.uv + fixed2(_MainTex_TexelSize.x,0));
                	fixed4 pixelLeft = tex2D( _MainTex, IN.uv - fixed2(_MainTex_TexelSize.x,0));
                		 
                	if ( pixelUp.a != 0 || pixelDown.a != 0  || pixelRight.a != 0  || pixelLeft.a != 0)
                	{
                		col.rgba = _OutlineColor;
                	}
            	}

            	return col;
            }
            ENDCG
         }
		
	}
	FallBack "Diffuse"
}

 

To add and remove the outline from your sprite you need to modify the “_Outline” property of the shader.

Here’s a simple script that will allow you to turn on and off the sprite outline.

 

Shader HLSL | Teleport Effect Shader

Introduction:

Ah yes, now for another fun shader that reduced the size of all of our assets.

As we were playing around with the game we hated the idea that the character would have to walk like in Final Fantasy Tactics Advanced or Fire Emblem. This would cause our asset list to grow quite large, every unit would have a walk animation and have to be facing 4-5 different directions, yuck!

Instead, we decided to go with having each character “teleport” to each location. Here was the first iteration.

tumblr_inline_olc3r2NDta1toto34_500.gif

Nice! The character teleported to where he was supposed to. Seems decent right? Well I and Raul felt something was a bit off, it was too sudden and the effect seemed very dull. How do we make it more fun?

We tried some ideas with some custom art but felt that it would take too many resources to have every unit have their own teleport animation, we wanted to achieve the below effect for every unit no matter which way that unit was facing.

tumblr_inline_olc40hxtRP1toto34_500.gif

The shader was easy enough to make, I just returned no color for every odd x pixel to make the image look like it was in strips. The effect wasn’t quite complete, though. In the effect above the image looks like it’s getting stretched on the y-axis to simulate a teleport animation.

In code, I decided to stretch the y-axis myself. But now I couldn’t apply this shader and the shader I had originally used for the sprite, so I added a “ghost” object sprite that is set active when the AI or Player is trying to move, the ghost sprite is just an object with a sprite renderer that takes the current sprite from the moving object.

The effect turned out like this.

tumblr_inline_olc4i44d7W1toto34_500.gif

Here’s the final shader:

Shader "Sprite/Teleport" 
{
	Properties 
	{
	  	_MainTex ("Texture", 2D) = "white" {}
        _Color ("Color", Color) = (1,1,1,1)
        _PixelsPerUnit("Pixels Per Unit", Float)= 16
	}

	SubShader 
    {
 
        Tags 
        { 
        	"Queue"="Transparent" 
        	"IgnoreProjector"="True" 
        	"RenderType" = "Transparent" 
        	"PreviewType"="Plane"
        	"CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma target 2.0
 
            #include "UnityCG.cginc"

             sampler2D _MainTex;
             float4 _MainTex_ST;
             float4 _MainTex_TexelSize;
             float4 _Color;
             float _PixelsPerUnit;

            float4 AlignToPixelGrid(float4 vertex)
			{
				float4 worldPos = mul(unity_ObjectToWorld, vertex);

				worldPos.x = floor(worldPos.x * _PixelsPerUnit + 0.5) / _PixelsPerUnit;
				worldPos.y = floor(worldPos.y * _PixelsPerUnit + 0.5) / _PixelsPerUnit;

				return mul(unity_WorldToObject, worldPos);
			}

			struct v2f 
            {
                float4  pos : SV_POSITION;
                float2  uv : TEXCOORD0;
            };

            v2f vert (appdata_base IN)
            {
                v2f OUT;

                float4 alignedPos = AlignToPixelGrid(IN.vertex);

                OUT.pos = mul (UNITY_MATRIX_MVP, alignedPos);
                OUT.uv = TRANSFORM_TEX(IN.texcoord, _MainTex);

                return OUT;
            }

            fixed4 frag(v2f IN) : SV_Target
            {
           		float2 texSize = _MainTex_TexelSize.zw;
            	fixed4 col = fixed4(0,0,0,0);

            	float2 vpos = texSize  * IN.uv;

            	if ( vpos.x % 2.0 >= 1.0 )
            	{
            		col = tex2D(_MainTex, IN.uv) * _Color;
            	}

            	return col;

            }
            ENDCG
         }
		
	}
	FallBack "Diffuse"
}

It has an extra pixel per unit code for displaying pixel perfect sprites that you may or may not want to take out 🙂

Hope you enjoy your day! 🙂