Unity Shader NPR 卡通渲染

匿名 (未验证) 提交于 2019-12-02 23:56:01

卡通渲染的主要原理包含两个方面:

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 }

效果如下:

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!