效果如下(头部无轮廓):
原理:使用2个pass来渲染。
第一个pass中,使用轮廓线颜色渲染整个背面,并在视角空间下把模型顶点沿着法线方法向外扩张一段距离,来让轮廓线可见,公式为:
viewPos += viewNormal * _Outline;
注意:如果直接使用顶点法线进行扩展,对于一些内凹的模型,就可能发生背面面片遮挡正面面片的情况。为了尽可能防止出现这样的情况,在扩张背面顶点之前,我们首先对顶点法线的z分量进行处理,使它们等于一个定值,然后把法线归一化后再对顶点进行扩张。这样的好处在于,扩展后的背面更加扁平化,从而降低了遮挡正面面片的可能性。代码如下:
viewNormal.z = -0.5;
viewNormal = normalize(viewNormal);
viewPos += viewNormal * _Outline;
第二个pass中,正常渲染物体。
参考shader代码如下:
1 Shader "America/Character/America-Diffuse-AlphaTest-Face" 2 { 3 Properties 4 { 5 _Color ("Main Color", Color) = (1,1,1,1) 6 _MainTex ("Base (RGB)", 2D) = "white" {} 7 _NormalMap("Normal Map", 2D) = "bump" {} 8 9 _DiffuseRate ("Diffuse Rate", float) = 2 10 _Ramp ("Toon Ramp (RGB)", 2D) = "gray" {} 11 _RampRate ("Ramp Rate", float) = 1 12 13 _OverlyingColor("Overlying Color", Color) = (0.5, 0.5, 0.5, 1) 14 15 } 16 17 SubShader 18 { 19 Tags 20 { 21 "RenderType" = "Transparent" 22 "IgnoreProjector" = "True" 23 "Queue" = "Transparent+100" 24 } 25 LOD 200 26 27 28 Pass 29 { 30 NAME "OUTLINE" 31 32 Cull Front 33 34 CGPROGRAM 35 #pragma vertex vert 36 #pragma fragment frag 37 38 struct appdata 39 { 40 float4 vertex : POSITION; 41 float4 normal : NORMAL; 42 }; 43 44 struct v2f 45 { 46 float4 pos : SV_POSITION; 47 }; 48 49 v2f vert(appdata v) 50 { 51 v2f o; 52 53 float4 viewPos = mul(UNITY_MATRIX_MV, v.vertex); 54 float4 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal); 55 56 viewNormal.z = -0.5; 57 o.pos = mul(UNITY_MATRIX_P, viewPos + normalize(viewNormal) * 0.01); 58 59 return o; 60 }; 61 62 float4 frag(v2f i) : SV_TARGET 63 { 64 return float4(0, 0, 0, 1); 65 }; 66 67 ENDCG 68 } 69 70 Pass 71 { 72 Tags 73 { 74 "LightMode" = "ForwardBase" 75 } 76 Cull Off 77 78 CGPROGRAM 79 #pragma vertex vert 80 #pragma fragment frag 81 #pragma multi_compile_fwdbase 82 83 #include "UnityCG.cginc" 84 #include "AutoLight.cginc" 85 #include "Lighting.cginc" 86 87 #include "../AmericaCG.cginc" 88 89 fixed4 _Color; 90 sampler2D _MainTex; 91 sampler2D _NormalMap; 92 float _DiffuseRate; 93 sampler2D _Ramp; 94 float _RampRate; 95 fixed4 _OverlyingColor; 96 97 struct appdata 98 { 99 float4 vertex : POSITION; 100 float2 uv : TEXCOORD0; 101 float3 normal : NORMAL; 102 float4 tangent : TANGENT; 103 }; 104 105 struct v2f 106 { 107 float4 pos : SV_POSITION; 108 float2 uv : TEXCOORD0; 109 float4 T2W1 : TEXCOORD1; 110 float4 T2W2 : TEXCOORD2; 111 float4 T2W3 : TEXCOORD3; 112 }; 113 114 v2f vert(appdata v) 115 { 116 v2f o; 117 o.pos = UnityObjectToClipPos(v.vertex); 118 o.uv = v.uv; 119 120 float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); 121 float3 worldNormal = UnityObjectToWorldNormal(v.normal); 122 float3 binormal = cross(normalize(worldNormal), normalize(worldTangent)) * v.tangent.w; 123 float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; 124 o.T2W1 = float4(worldTangent.x, binormal.x, worldNormal.x, worldPos.x); 125 o.T2W2 = float4(worldTangent.y, binormal.y, worldNormal.y, worldPos.y); 126 o.T2W3 = float4(worldTangent.z, binormal.z, worldNormal.z, worldPos.z); 127 return o; 128 } 129 130 fixed4 frag(v2f i) : SV_TARGET 131 { 132 float3 worldPos = float3(i.T2W1.w, i.T2W2.w, i.T2W3.w); 133 float3 worldLight = normalize(UnityWorldSpaceLightDir(worldPos)); 134 //float3 worldView = normalize(UnityWorldSpaceViewDir(worldPos)); 135 float3 tangentNormal = UnpackNormal(tex2D(_NormalMap, i.uv)); 136 float3x3 tanToWorld = float3x3(i.T2W1.xyz, i.T2W2.xyz, i.T2W3.xyz); 137 float3 worldNormal = mul(tanToWorld, tangentNormal); 138 139 fixed4 albedo = tex2D(_MainTex, i.uv) * _Color; 140 fixed3 diffuse = CalcDiffuse(albedo, worldLight, worldNormal, _Ramp, _RampRate); 141 142 fixed4 col = fixed4(diffuse * _DiffuseRate, albedo.a); 143 col.rgb = Overlay(col, _OverlyingColor); 144 145 return col; 146 } 147 148 ENDCG 149 } 150 151 } 152 153 Fallback "Diffuse" 154 }