How does Unity's Toon Shader work?

Hey guys,

I’m currently comparing and implementing different toon shading techniques for a university assignment, which is why I also had a look at how it is implemented in Unity. However, there is a part in the shader code that I don’t quite understand. I’m pasting the shader code below.

Shader "Toon/Basic Outline" {
	Properties {
		_Color ("Main Color", Color) = (.5,.5,.5,1)
		_OutlineColor ("Outline Color", Color) = (0,0,0,1)
		_Outline ("Outline width", Range (.002, 0.03)) = .005
		_MainTex ("Base (RGB)", 2D) = "white" { }
		_ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { Texgen CubeNormal }
	}
	
	CGINCLUDE
	#include "UnityCG.cginc"
	
	struct appdata {
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};

	struct v2f {
		float4 pos : POSITION;
		float4 color : COLOR;
	};
	
	uniform float _Outline;
	uniform float4 _OutlineColor;
	
	v2f vert(appdata v) {
		v2f o;
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

		float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
		float2 offset = TransformViewToProjection(norm.xy);

		o.pos.xy += offset * o.pos.z * _Outline;
		o.color = _OutlineColor;
		return o;
	}
	ENDCG

	SubShader {
		Tags { "RenderType"="Opaque" }
		UsePass "Toon/Basic/BASE"
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			half4 frag(v2f i) :COLOR { return i.color; }
			ENDCG
		}
	}
	
	SubShader {
		Tags { "RenderType"="Opaque" }
		UsePass "Toon/Basic/BASE"
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma exclude_renderers shaderonly
			ENDCG
			SetTexture [_MainTex] { combine primary }
		}
	}
	
	Fallback "Toon/Basic"
}

It’s the part around line 31. I don’t really understand how these offsets are created and especially what the function “TransformViewToProjection” does. My implementation has to be in DirectX11/HLSL but I haven’t been able to replicate this shader. So I hoped you guys can tell me how this part works so I can try to rebuild it in HLSL.
Thank you very much in advance!

The TransformViewToProjection code is taking the part of the normal that is perpendicular to the view direction and projecting it into clip space. It doesn’t do a full matrix multiply, it just assumes that there is a standard projection matrix with element (0, 0) = cot(fovW/2) and (1,1) = cot(fovH/2). The multiply by z ensures that this value scales up with distance so the outline stays a fixed size in screen space, this is assuming a standard perspective projection. You’ll notice that if you use an orthographic projection the outline breaks because it’s assumptions will be incorrect :-/

3 Likes