Unity Shader NPR 卡通渲染


卡通渲染的主要原理包含兩個方面:

1.輪廓線的描邊效果

2.模型漫反射離散和純色高光區域的模擬

 

描邊

描邊的實現方法采用將模型的輪廓線頂點向法線(或頂點)的方向擴展一定的像素得到。也可通過邊緣檢測(基於法線和深度)來實現。

 

漫反射離散

 利用離散的Ramp紋理對漫反射光照效果進行采樣,可以實現不同效果梯度的卡通渲染效果,例如:

 

注意此紋理的灰度變化並非均勻變化,而是類似於一種突變,僅在灰度變化的交界處進行了平滑過渡。這樣的Ramp紋理正是卡通渲染所需要的顏色過渡模式,也是卡通渲染實現的核心內容。

也可增加階度的個數實現更多層次的卡通渲染效果。

 

純色高光區域

不同於真實渲染,卡通渲染的高光部分通常就是一個色塊,這里主要的問題是處理高光邊緣的鋸齒問題。

這里可以利用smoothstep(-w,w,spec-threshold);在邊緣范圍[-w,w]進行平滑插值處理,其中w可以通過fwidth(spec);得到。

fwidth(spec);用於得到鄰域像素的近似導數值。

 

Shader腳本如下,光照模型采用半蘭伯特:

  1 Shader "MyUnlit/CartoonShading"
  2 {
  3     Properties
  4     {
  5         _Color("Color Tint",Color)=(1,1,1,1)
  6         _MainTex ("Texture", 2D) = "white" {}
  7         _Ramp("Ramp Texture",2D)="white"{}
  8         _Outline("Outline",Range(0,0.1))=0.02
  9         _Factor("Factor of Outline",Range(0,1))=0.5
 10         _OutlineColor("Outline Color",Color)=(0,0,0,1)
 11         _Specular("Specular",Color)=(1,1,1,1)
 12         _SpecularScale("Specular Scale",Range(0,0.1))=0.01
 13     }
 14     SubShader
 15     {
 16         Tags { "RenderType"="Opaque" }
 17         //此Pass渲染描邊
 18         Pass
 19         {
 20             //命名用於之后可重復調用
 21             NAME "OUTLINE"
 22             //描邊只用渲染背面,擠出輪廓線,所以剔除正面
 23             Cull Front
 24             //開啟深度寫入,防止物體交疊處的描邊被后渲染的物體蓋住
 25             ZWrite On
 26             CGPROGRAM
 27             #pragma vertex vert
 28             #pragma fragment frag
 29 
 30             #include "UnityCG.cginc"
 31 
 32             float _Outline;
 33             float _Factor;
 34             fixed4 _OutlineColor;
 35 
 36             struct appdata
 37             {
 38                 float4 vertex : POSITION;
 39                 float3 normal:NORMAL;
 40             };
 41 
 42             struct v2f
 43             {
 44                 float4 vertex : SV_POSITION;
 45             };
 46 
 47             v2f vert (appdata v)
 48             {
 49                 v2f o;
 50                 float3 pos=normalize(v.vertex.xyz);
 51                 float3 normal=normalize(v.normal);
 52 
 53                 //點積為了確定頂點對於幾何中心的指向,判斷此處的頂點是位於模型的凹處還是凸處
 54                 float D=dot(pos,normal);
 55                 //校正頂點的方向值,判斷是否為輪廓線
 56                 pos*=sign(D);
 57                 //描邊的朝向插值,偏向於法線方向還是頂點方向
 58                 pos=lerp(normal,pos,_Factor);
 59                 //將頂點向指定的方向擠出
 60                 v.vertex.xyz+=pos*_Outline;
 61                 o.vertex=UnityObjectToClipPos(v.vertex);
 62                 return o;
 63             }
 64 
 65             fixed4 frag (v2f i) : SV_Target
 66             {
 67                 return fixed4(_OutlineColor.rgb,1);
 68             }
 69             ENDCG
 70         }
 71         //此Pass渲染卡通着色效果,主要運用半蘭伯特光照模型配合漸變紋理
 72         Pass
 73         {
 74             Tags{"LightMode"="ForwardBase"}
 75             Cull Back
 76             CGPROGRAM
 77             
 78             #pragma vertex vert
 79             #pragma fragment frag
 80             #pragma multi_compile_fwdbase
 81 
 82             #include "UnityCG.cginc"
 83             //引入陰影相關的宏
 84             #include "AutoLight.cginc"
 85             //引入預設的光照變量,如_LightColor0
 86             #include "Lighting.cginc"
 87 
 88             fixed4 _Color;
 89             sampler2D _MainTex;
 90             sampler2D _Ramp;
 91             fixed4 _Specular;
 92             fixed _SpecularScale;
 93             float4 _MainTex_ST;
 94 
 95             struct appdata
 96             {
 97                 float4 vertex:POSITION;
 98                 float2 uv:TEXCOORD0;
 99                 float3 normal:NORMAL;
100                 float4 tangent:TANGENT;
101             };
102 
103             struct v2f
104             {
105                 float4 pos:SV_POSITION;
106                 float2 uv:TEXCOORD0;
107                 float3 worldNormal:TEXCOORD1;
108                 float3 worldPos:TEXCOORD2;
109                 SHADOW_COORDS(3)
110             };
111 
112             v2f vert(appdata v)
113             {
114                 v2f o;
115                 o.pos=UnityObjectToClipPos(v.vertex);
116                 o.uv=TRANSFORM_TEX(v.uv,_MainTex);
117                 o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
118                 o.worldPos=mul(unity_ObjectToWorld,v.vertex);
119                 TRANSFER_SHADOW(o);
120 
121                 return o;
122             }
123 
124             fixed4 frag(v2f i):SV_Target
125             {
126                 fixed3 worldNormal=normalize(i.worldNormal);
127                 fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
128                 fixed3 worldViewDir=normalize(UnityWorldSpaceViewDir(i.worldPos));
129                 fixed3 worldHalfDir=normalize(worldLightDir+worldViewDir);
130 
131                 //計算材質反射率
132                 fixed4 c=tex2D(_MainTex,i.uv);
133                 fixed3 albedo=c.rgb*_Color.rgb;
134 
135                 //計算環境光
136                 fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
137 
138                 //處理陰影
139                 UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
140 
141                 //計算半蘭伯特漫反射系數,亮化處理,將結果從[-1,1]映射到[0,1],以便作為漸變紋理的采樣uv
142                 fixed diff=dot(worldNormal,worldLightDir);
143                 diff=(diff*0.5+0.5)*atten;
144 
145                 //卡通渲染的核心內容,對漫反射進行區域色階的離散變化
146                 fixed3 diffuse=_LightColor0.rgb*albedo*tex2D(_Ramp,float2(diff,diff)).rgb;
147 
148                 //計算半蘭伯特高光系數,並將高光邊緣的過渡進行抗鋸齒處理,系數越大,過渡越明顯
149                 fixed spec=dot(worldNormal,worldHalfDir);
150                 fixed w=fwidth(spec)*3.0;
151 
152                 //計算高光,在[-w,w]范圍內平滑插值
153                 fixed3 specular=_Specular.rgb*smoothstep(-w,w,spec-(1-_SpecularScale))*step(0.0001,_SpecularScale);
154 
155                 return fixed4(ambient+diffuse+specular,1.0);
156             }
157             ENDCG    
158         }
159     }
160     FallBack "Diffuse" 
161 }

效果如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM