Team Fortress 2 / Toon Shader in Unity Free Version

I ll be very quick, is it possible to make a custom shader like that one used in team Fortress 2 (and render the related effect) in Unity Free Version?

As reference, the effect I would like to achieve is pretty much like this: http://forum.unity3d.com/threads/16753-Illustrative-Cartoon-Shader-Team-Fortress-Style and the code for the rim light and ramp texture sampling should be something like:

sampler2D _LightingRamp;
			inline half4 LightingBlinnPhongEditor_PrePass (EditorSurfaceOutput s, half4 light)
			{
				float lum = Luminance( light.rgb );
				float newIllumination = tex2D(_LightingRamp, float2( lum, 0)).r;
				half3 spec = light.a * s.Gloss;
				half4 c;
				c.rgb = (s.Albedo * newIllumination + newIllumination * spec);
				c.a = s.Alpha + Luminance(spec);
				return c;
			}

considering the rim light as view dir independent as valve says in his article:

http://www.valvesoftware.com/publications/2007/NPAR07_IllustrativeRenderingInTeamFortress2.pdf
Thanks very much for your help guys.

Regards.

Yeah, it’s very easily done.

The ambient cubemapping that Valve uses for the ambient lighting is a little harder to implement, though.

Thanks very much for the quick reply!

Please feel free to post any reference you have guys on this kind of shaders. I would really appreciate that. Thank you very much again!

I have one made somewhere (without the ambient cubemap) I’d just need to port it to Unity.

I’ll knock it up over lunch time and post it up here.

TF2 Shader Code

Pre-made Toon Ramps (including TF2’s) can be found on Rollin’s site;
http://blog.maginot.eu/dl.php?v=A15B01X103

Example of shader working;

Shader "Toon/Team Fortress 2" {
	Properties {
		_Color ("Main Color", Color) = (1,1,1,1)
		_RimColor ("Rim Color", Color) = (0.97,0.88,1,0.75)
		_RimPower ("Rim Power", Float) = 2.5
		_MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
		_BumpMap ("Normal (Normal)", 2D) = "bump" {}
		_SpecularTex ("Specular Level (R) Gloss (G) Rim Mask (B)", 2D) = "gray" {}
		_RampTex ("Toon Ramp (RGB)", 2D) = "white" {}
		_Cutoff ("Alphatest Cutoff", Range(0, 1)) = 0.5
	}

	SubShader{
		Tags { "RenderType" = "Opaque" }
		
		CGPROGRAM
			#pragma surface surf TF2 alphatest:_Cutoff
			#pragma target 3.0

			struct Input
			{
				float2 uv_MainTex;
				float3 worldNormal;
				INTERNAL_DATA
			};
			
			sampler2D _MainTex, _SpecularTex, _BumpMap, _RampTex;
			float4 _RimColor;
			float _RimPower;

			inline fixed4 LightingTF2 (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
			{
				fixed3 h = normalize (lightDir + viewDir);
				
				fixed NdotL = dot(s.Normal, lightDir) * 0.5 + 0.5;
				fixed3 ramp = tex2D(_RampTex, float2(NdotL * atten)).rgb;
				
				float nh = max (0, dot (s.Normal, h));
				float spec = pow (nh, s.Gloss * 128) * s.Specular;
				
				fixed4 c;
				c.rgb = ((s.Albedo * ramp * _LightColor0.rgb + _LightColor0.rgb * spec) * (atten * 2));
				c.a = s.Alpha;
				return c;
			}

			void surf (Input IN, inout SurfaceOutput o)
			{
				o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
				o.Alpha = tex2D(_MainTex, IN.uv_MainTex).a;
				o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
				float3 specGloss = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
				o.Specular = specGloss.r;
				o.Gloss = specGloss.g;
				
				half3 rim = pow(max(0, dot(float3(0, 1, 0), WorldNormalVector (IN, o.Normal))), _RimPower) * _RimColor.rgb * _RimColor.a * specGloss.b;
				o.Emission = rim;
			}
		ENDCG
	}
	Fallback "Transparent/Cutout/Bumped Specular"
}

Thanks very Much!!! This is REALLY helpful! Thax Again.

Regards.

Does this happen to work in a iOS environment, too ?
Cause I was meaning to implement something like this in a IPhone project for a long time :o

Uhh, not sure. I haven’t touched anything related to iOS before.

I’m pretty sure ramp and rim light should be possible, though.

should work, see nothing that springs out that would imply that it would not work

but pixel lights are rather intense in fillrate if you don’t make them very low range, which performance plagues especially ipad1 and iphone4 / itouch4 which with their native resolution just are totally underpowered on the GPU if you really go with OpenGL ES 2.0 (which you have to if you want to use surface shaders / programmable pipeline)

Yeah, I’d push the rim light up into the vertex shader and ditch the normal maps in the fragment shader.

On a screen that size and modles that low res, you’re not likely to notice much of a difference anyway.

hey, i am still here :stuck_out_tongue:

Thanks again for the shader, I have slightly changed it but it has been very helpdul! However, my artist is trying to do the rim mask and the ramp texture, but i m not sure what ask him for. I mean, I actually do not know what a rim mask his (looking at the rim light calculation and at the name, it should be obvious, it is a mask who rules the rim light colour intensity, and I guess it should be a a grey scale (from black to white, from 0 to 1), but apparently we cannot still achieve the rim light “effect”. In addition, I have some doubts also on the Ramp Texture, how it should be made? Any valid reference? I looked at the BumpSpecRim light shader on the wiki, and it seeams like the Ramp Light it s a gray scale… isn it? Actually I was thinking it was something different (like a texture covered by a mask or map).

Anyway thanks for everything, althought I am not new to shader programming, I have some doubts on how these textures should be.

EDIT: I think we solved, now the character looks pretty cool, but i appreciate anyway some explanations on the ramp texture and rim mask, if someone of you guys would like to teach me :stuck_out_tongue:

The TF2 rim light is actually vertical direction (rather than the usual fresnel style, where it’s based on view direction), which I’ve replicated in the shader.

The mask is, yes, just the blue channel of the spec/gloss map. It wasn’t getting used for anything else, so I figured why not :stuck_out_tongue: White means full rim strength, black means no rim at all. If in doubt, just leave the blue channel completely white.

As for the ramp texture, it goes from “dark” to “bright” from the left to right of the texture. So put anything you like in there, really. There isn’t any traditional shading applied to the model - it’s all taken from the ramp texture. So you’ll probably want to ensure that it gets pretty dark towards the left hand side. Any of the ramp textures in that link I posted should work fine.

You’ll probably want to set them to “clamp” wrap mode in the Inspector, though.

yeah thank you very much now I perfectly understand how rim works! Cheers!

Really useful thanks guys :slight_smile:

Added to wiki;

awesome cool guys thx again

Can Unity free version display the same shader with an ambient cubemapping ? Or in other, if I add it to the shader, will unity free version allow to render it? I think there should not be problems, but i m not sure.

Edit: I added a simple reflection Cube Map based on Normals and the result its pretty cool. It effectively improves the overall effect.

I ll do more researches on the ambient cube mapping stuff

Sorry to dig up this thread. I just tried this shader, and I think is awesome.
I’ve got just a question: the color field is not working for me: no matter what color I set up, I always need a texture. The color has no effect whatsoever. Since I’m working with monochromatic models, is there a way to actually use color instead of creating a texture?
Thank you!

Yeah, it’s there for the fallbacks.

This should have the colour value apply to the diffuse (if there is one) or simply use the colour (if there is no diffuse).

Shader "Toon/Team Fortress 2" {
    Properties {
        _Color ("Main Color", Color) = (1,1,1,1)
        _RimColor ("Rim Color", Color) = (0.97,0.88,1,0.75)
        _RimPower ("Rim Power", Float) = 2.5
        _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
        _BumpMap ("Normal (Normal)", 2D) = "bump" {}
        _SpecularTex ("Specular Level (R) Gloss (G) Rim Mask (B)", 2D) = "gray" {}
        _RampTex ("Toon Ramp (RGB)", 2D) = "white" {}
        _Cutoff ("Alphatest Cutoff", Range(0, 1)) = 0.5
    }

    SubShader{
        Tags { "RenderType" = "Opaque" }
        
        CGPROGRAM
            #pragma surface surf TF2 alphatest:_Cutoff
            #pragma target 3.0

            struct Input
            {
                float2 uv_MainTex;
                float3 worldNormal;
                INTERNAL_DATA
            };
            
            sampler2D _MainTex, _SpecularTex, _BumpMap, _RampTex;
            float4 _RimColor, _Color;
            float _RimPower;

            inline fixed4 LightingTF2 (SurfaceOutput s, fixed3 lightDir, fixed3 viewDir, fixed atten)
            {
                fixed3 h = normalize (lightDir + viewDir);
                
                fixed NdotL = dot(s.Normal, lightDir) * 0.5 + 0.5;
                fixed3 ramp = tex2D(_RampTex, float2(NdotL * atten)).rgb;
                
                float nh = max (0, dot (s.Normal, h));
                float spec = pow (nh, s.Gloss * 128) * s.Specular;
                
                fixed4 c;
                c.rgb = ((s.Albedo * _Color.rgb * ramp * _LightColor0.rgb + _LightColor0.rgb * spec) * (atten * 2));
                c.a = s.Alpha * _Color.a;
                return c;
            }

            void surf (Input IN, inout SurfaceOutput o)
            {
                o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
                o.Alpha = tex2D(_MainTex, IN.uv_MainTex).a;
                o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex));
                float3 specGloss = tex2D(_SpecularTex, IN.uv_MainTex).rgb;
                o.Specular = specGloss.r;
                o.Gloss = specGloss.g;
                
                half3 rim = pow(max(0, dot(float3(0, 1, 0), WorldNormalVector (IN, o.Normal))), _RimPower) * _RimColor.rgb * _RimColor.a * specGloss.b;
                o.Emission = rim;
            }
        ENDCG
    }
    Fallback "Transparent/Cutout/Bumped Specular"
}

This Shader is pretty cool.
I would like to add some outlines to this shader, and tried using the built-in toonshaders for it, but this destroys the shader colors, etc.

How would the Code look like, if you add “Outline-Color” and “Outline-thickness” ?

would be pretty awesome if you could improve the code with a outline feature!

I have license for Unity 2.6.1, any chance to rewrite this shader for my version?