• 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 }

    效果如下:

  • 相关阅读:
    GitHub转华为软件开发云详细教程
    如何将项目管理从禅道迁移到华为软件开发云
    华为CloudIDE免费公测,带你出坑带你飞
    闲谈 | 敏捷宣言说了什么
    Eclipse安装Git插件以及通过Git导入华为软件开发云项目
    终于等到你!MobileTest免费公测,华为带你走出安卓适配大坑
    华为软件开发云测评报告三:测试管理
    华为软件开发云发布管理测评报告
    你真的知道敏捷和迭代吗?
    ThoughtWorks、Teambition、Trello、Slack、DevCloud 主流敏捷软件开发工具平台比较
  • 原文地址:https://www.cnblogs.com/koshio0219/p/11436702.html
Copyright © 2020-2023  润新知