Let’s face it, I’m a sucker for stylised look more than realism and I have to admit Half Lambert is one my favourite. You can read about it in Valve’s wiki. Even though it has been used since Half Life, I fell in love with Team Fortress’s half lambert style.  We had the luxury of working in toon style in one of our projects and we didn’t like the default toon shader that comes along with Unity.

So I made up my mind to replicate TF2’s shader inside Unity, I started with Shader Forge and used Unreal material editor mod by a saint. Even though I got something close to what I wanted, it was pretty heavy as a shader and Forge just pumped out too many instructions for the GPU. Is it mobile friendly? I’m not quiet sure still have to do a lot of tweaks to make it work on mobile devices. Here’s the code so far after cleaning up and tweaking.

Shader "TF2"
{
	Properties
	{
		_MainTex("Main Texture", 2D) = "white" {}	
		_BumpMap("Bump Map", 2D) = "normal" {}
		_Ramp("Ramp", 2D) = "white" {}
		_Color("Color", Color) = (1.0, 1.0, 1.0, 1.0)
		_DiffuseScale("Diffuse Scale", Float) = 1
		_DiffuseBias("Diffuse Bias", Float) = 0
		_DiffuseExponent("Diffuse Exponent", Float) = 1
		_RimColor("Rim Color", Color) = (0.26,0.19,0.16,0.0)
		_RimPower("Rim Power", Range(0.5, 10.0)) = 3.0
		_SpecColor("Specular Color", Color) = (0.0, 0.0, 0.0, 1.0)
		_SpecPower("Specular Power", Range(0.5, 126.0)) = 3.0
		_OutlineWidth ("Outline Width", Float ) = 0.05
        _OutlineColor ("Outline Color", Color) = (0.5,0.5,0.5,1)
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }
		
		Pass {
            Name "Outline"
            Tags {
            }
            Cull Front
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #pragma fragmentoption ARB_precision_hint_fastest
            #pragma multi_compile_shadowcaster
            #pragma exclude_renderers xbox360 ps3 flash d3d11_9x 
            #pragma target 2.0
            uniform float _OutlineWidth;
            uniform float4 _OutlineColor;
            struct VertexInput {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };
            struct VertexOutput {
                float4 pos : SV_POSITION;
            };
            VertexOutput vert (VertexInput v) {
                VertexOutput o;
                o.pos = mul(UNITY_MATRIX_MVP, float4(v.vertex.xyz + v.normal*_OutlineWidth,1));
                return o;
            }
            fixed4 frag(VertexOutput i) : COLOR {
                return fixed4(_OutlineColor.rgb,0);
            }
            ENDCG
        }
		LOD 200

		CGPROGRAM
		#include "UnityCG.cginc"
		#pragma surface surf NPR noambient

		// These match the shader properties
		uniform sampler2D _MainTex, _BumpMap, _Ramp;
		uniform float4 _Color, _RimColor;
		uniform float _DiffuseScale, _DiffuseBias, _DiffuseExponent;
		uniform float _RimPower, _SpecPower;

		half4 LightingNPR(SurfaceOutput o, half3 lightdir, half3 viewdir, half atten)
		{
			// Half Lambert
			float lambert = saturate(dot(o.Normal, lightdir));
			lambert = pow(lambert*_DiffuseScale + _DiffuseBias, _DiffuseExponent);
			half4 diffuse = half4(_LightColor0.rgb * atten * o.Albedo.rgb, 1.0);
			diffuse *= tex2D(_Ramp, float2(lambert, 0.0));
			
			// Crazy Specularlity
			half3 r = reflect(-lightdir, o.Normal);
			float phong = pow(saturate(dot(r, viewdir)), _SpecPower);
			half4 specular = half4(phong * _SpecColor * atten);

			// Rim Lighting based on Normals (bump)
			float rim_term = 1.0 - saturate(dot(viewdir, o.Normal));
			rim_term = pow(rim_term, _RimPower);
			half4 rim = half4(_RimColor.rgb * rim_term, 1.0); 

			return diffuse + rim + specular;
		}

		struct Input
		{
			float2 uv_MainTex;
		};

		void surf(Input IN, inout SurfaceOutput o)
		{
			
			half4 c = tex2D(_MainTex, IN.uv_MainTex);
			o.Albedo = _Color.rgb * c;					
			o.Alpha = 1.0;								

			o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
		}
		ENDCG
	}
	FallBack "Diffuse"	// Shader to use if the user's hardware cannot incorporate this one
}

I downloaded a TF2 model from tf3dm.com, here’s the link. The user has given some really good set of textures. I set the rig as Humanoid while importing, so that I can apply some sample mecanim animations.

Set the Ambient Light to black, usually it’s a good idea because you’ll get full control over the lights you have in the scene.

AmbientLight

 

Select the shader and give these properties, I have used a black and white gradient as ramp, TF2 uses a different one, make sure you go through their wiki.

ShaderSettings

 

TF_Unity

Here’s a web build.

[unity src=”90″]

If you guys have any suggestions or questions, post a comment below.