UnityShader : NPR卡通渲染基础

NPR 是 Non-Photorealistic Rendering 的简称,也就是图形渲染中的非真实感渲染,常见的 NPR 渲染包括卡通渲染、油画渲染、像素感渲染、素描画、水墨画等类型,

卡通渲染 是非真实感渲染中应用最广的渲染技术,在游戏和影视领域都是非常常见的。它主要是通过简化并剔除画面原本所包含的混杂部分,给人以独特的感染力和童趣,通常来说卡通渲染有4个要素 轮廓描边、色阶、高光、边缘光

轮廓描边:

渲染轮廓线的方式有很多种, 在这里带大家熟悉其中最简单的一种, 对物体做两次渲染, 第二次渲染时开启正面剔除,将顶点沿法线向外延深一段距离,(放大物体),实现轮廓线,这里就用到我们之前提到的多Pass渲染。

打开Shader,首先在Properties语块中声明轮廓线相关的两个属性,方便我们进行之后的调整。原本的pass我们暂时先不用动,直接新增一个Pass来做轮廓线的渲染,记得定义一下我们刚刚声明的宽度和颜色


                                                        o.vertex = UnityObjectToClipPos(v.vertex);
                                                        o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                                                        UNITY_TRANSFER_FOG(o,o.vertex);
                                                        return o;
                                                    }
                                        
                                                    fixed4 frag (v2f i) : SV_Target
                                                    {
                                                        // sample the texture
                                                        fixed4 col = tex2D(_MainTex, i.uv);
                                                        // apply fog
                                                        UNITY_APPLY_FOG(i.fogCoord, col);
                                                        return col;
                                                    }
                                                    ENDCG
                                                }
                                                
                                                Pass
                                                {	
                                        	    // 开启前向剔除 表示剔除前面 只显示背面
                                                    Cull Front
                                                    
                                                    CGPROGRAM
                                                    #pragma vertex vert
                                                    #pragma fragment frag
                                                    
                                                    // 线条宽度
                                                    float _OutlineWidth;
                                                    // 线条颜色
                                                    float4 _OutLineColor;
                                        
                                                    struct appdata 
                                                    {
                                                        float4 vertex : POSITION;
                                                        float2 uv : TEXCOORD0;
                                                        // 法线
                                                        float3 normal : NORMAL;
                                                    };
                                        
                                                    struct v2f
                                                    {
                                        		float2 uv : TEXCOORD0;
                                                        float4 vertex : SV_POSITION;
                                                    };
                                        
                                                    v2f vert (appdata v) 
                                                    {
                                                        v2f o;
                                        		// 顶点沿着法线方向外扩(放大模型)
                                        		float4 newVertex = float4(v.vertex.xyz + v.normal * _OutlineWidth * 0.01 ,1);
                                        		// UnityObjectToClipPos(v.vertex) 将模型空间下的顶点转换到齐次裁剪空间
                                                        o.vertex = UnityObjectToClipPos(newVertex);
                                                        return o;
                                                    }
                                        
                                                    half4 frag(v2f i) : SV_TARGET 
                                                    {
                                        	        // 返回线条色彩
                                                        return _OutLineColor;
                                                    }
                                                    
                                                    ENDCG
                                                }
                                            }
                                        }

色阶:

通常来说都是由它来决定画面色彩的丰富度饱满度精细度,而大部分卡通渲染习惯降低色阶,用简单的明暗关系来描述世界,使画面扁平又不失层次感,这里还是用上节讲的half Lambert光照模型,不过这看起来一点都不卡通,我们需要让它明暗分明一点

// 得到顶点法线
                                            float3 normal = normalize(i.worldNormal);
                                            // 得到光照方向
                                            float3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
                                            // NoL代表表面接受的能量大小
                                            float NoL = dot(i.worldNormal, worldLightDir);
                                            // 计算half-lambert亮度值
                                            float halfLambert = NoL * 0.5 + 0.5;
                                            
                                            // 通过亮度值计算线性ramp
                                            float ramp = linearstep(_RampStart, _RampStart + _RampSize, halfLambert);
                                            float step = ramp * _RampStep;  // 使每个色阶大小为1, 方便计算
                                            float gridStep = floor(step);   // 得到当前所处的色阶
                                            float smoothStep = smoothstep(gridStep, gridStep + _RampSmooth, step) + gridStep;
                                            ramp = smoothStep / _RampStep;  // 回到原来的空间
                                            // 得到最终的ramp色彩
                                            float3 rampColor = lerp(_DarkColor, _LightColor, ramp);
                                            rampColor *= col;

高光:

用相机的位置减去世界位置得到视向量,也就是当前物体表面指向摄像机的方向,由于反射不太好算,所以这里通过 视向量 和 光照方向 得到角平分线,也就是半程向量。通过 法线方向 点乘 半程向量 就可以得到 法线 和 半程向量 的 夹角,由此就 可以推断出 视向量 和 反射向量 的 接近程度,用 noh 来 计算高光 的 亮度值,而这个参数 SpecPow 则是 控制高光的 光泽度,也就是 高光 亮斑的 范围,和色阶同样,用smoothStep来做个柔边的效果再把高光颜色和强度值加上,最后我们把漫反射和高光混合,就可以来调试效果了。

 // 得到视向量
                                                   float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                                                   // 计算half向量, 使用Blinn-phone计算高光
                                                   float3 halfDir = normalize(viewDir + worldLightDir);
                                                   // 计算NoH用于计算高光
                                                   float NoH = dot(normal, halfDir);
                                                   // 计算高光亮度值
                                                   float blinnPhone = pow(max(0, NoH), _SpecPow * 128.0);
                                                   // 计算高光色彩
                                                   float3 specularColor = smoothstep(0.7 - _SpecSmooth / 2, 0.7 + _SpecSmooth / 2, blinnPhone) 
                                                                                                * _SpecularColor * _SpecIntensity;

边缘光:

首先我们需要得知哪里是我们看到的边缘,当我们的视向量和法线向量的夹角越接近直角时它就越靠近边缘,先拿到视向量和法向量的夹角,就可以看到,越是接近边缘的地方越暗,但边缘光一般都是越接近边缘越亮,所以给 1- 反转一下,但正常来说阴影部分是不应该有边缘光的,所以要把漫反射加一下,那到至此边缘光就正确了

// 计算NoV用于计算边缘光
                                               float NoV = dot(i.worldNormal, viewDir);
                                               // 计算边缘光亮度值
                                               float rim = (1 - max(0, NoV)) * NoL;
                                               // 计算边缘光颜色
                                               float3 rimColor = smoothstep(_RimThreshold - _RimSmooth / 2, _RimThreshold + _RimSmooth / 2, rim) * _RimColor;

完整代码:

  Shader "Custom/ToonShader"
                                    {
                                        Properties
                                        {
                                            _MainTex ("Texture", 2D) = "white" {}
                                            
                                            _OutlineWidth ("Outline Width", Range(0.01, 1)) = 0.01
                                    		_OutLineColor ("OutLine Color", Color) = (0.5,0.5,0.5,1)
                                            
                                            _RampStart ("交界起始 RampStart", Range(0.1, 1)) = 0.3
                                            _RampSize ("交界大小 RampSize", Range(0, 1)) = 0.1
                                            [IntRange] _RampStep("交界段数 RampStep", Range(1,10)) = 1
                                            _RampSmooth ("交界柔和度 RampSmooth", Range(0.01, 1)) = 0.1
                                            _DarkColor ("暗面 DarkColor", Color) = (0.4, 0.4, 0.4, 1)
                                            _LightColor ("亮面 LightColor", Color) = (0.8, 0.8, 0.8, 1)
                                            
                                            _SpecPow("SpecPow 光泽度", Range(0, 1)) = 0.1
                                            _SpecularColor ("SpecularColor 高光", Color) = (1.0, 1.0, 1.0, 1)
                                            _SpecIntensity("SpecIntensity 高光强度", Range(0, 1)) = 0
                                            _SpecSmooth("SpecSmooth 高光柔和度", Range(0, 0.5)) = 0.1
                                            
                                            _RimColor ("RimColor 边缘光", Color) = (1.0, 1.0, 1.0, 1)
                                            _RimThreshold("RimThreshold 边缘光阈值", Range(0, 1)) = 0.45
                                            _RimSmooth("RimSmooth 边缘光柔和度", Range(0, 0.5)) = 0.1
                                        }
                                        SubShader
                                        {
                                            Tags { "RenderType"="Opaque" }
                                            LOD 100
                                    
                                            Pass
                                            {
                                                CGPROGRAM
                                                #pragma vertex vert
                                                #pragma fragment frag
                                    
                                                #include "UnityCG.cginc"
                                    
                                                struct appdata
                                                {
                                                    float4 vertex : POSITION;
                                                    float2 uv : TEXCOORD0;
                                                    float3 normal: NORMAL;  // 计算光照需要用到模型法线
                                                };
                                    
                                                struct v2f
                                                {
                                                    float2 uv : TEXCOORD0;
                                                    float4 vertex : SV_POSITION;
                                                    // 计算光照需要用到法线和世界位置
                                                    float3 worldNormal: TEXCOORD1;
                                                    float3 worldPos:TEXCOORD2;
                                                };
                                    
                                                sampler2D _MainTex;
                                                float4 _MainTex_ST;
                                                float _RampStart;
                                                float _RampSize;
                                                float _RampStep;
                                                float _RampSmooth;
                                                float3 _DarkColor;
                                                float3 _LightColor;
                                    
                                                float _SpecPow;
                                                float3 _SpecularColor;
                                                float _SpecIntensity;
                                                float _SpecSmooth;
                                    
                                                float3 _RimColor;
                                                float _RimThreshold;
                                                float _RimSmooth;
                                    
                                                float linearstep (float min, float max, float t)
                                                {
                                                    return saturate((t - min) / (max - min));
                                                }
                                    
                                                v2f vert (appdata v)
                                                {
                                                    v2f o;
                                                    o.vertex = UnityObjectToClipPos(v.vertex);
                                                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                                                    // 向下传输这些数据
                                                    o.worldNormal = normalize(UnityObjectToWorldNormal(v.normal));
                                                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                                                    return o;
                                                }
                                    
                                                fixed4 frag (v2f i) : SV_Target
                                                {
                                                    // sample the texture
                                                    fixed4 col = tex2D(_MainTex, i.uv);
                                                    
                                                    //------------------------ 漫反射 ------------------------
                                                    // 得到顶点法线
                                                    float3 normal = normalize(i.worldNormal);
                                                    // 得到光照方向
                                                    float3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
                                                    // NoL代表表面接受的能量大小
                                                    float NoL = dot(i.worldNormal, worldLightDir);
                                                    // 计算half-lambert亮度值
                                                    float halfLambert = NoL * 0.5 + 0.5;
                                    
                                                    //------------------------ 高光 ------------------------
                                                    // 得到视向量
                                                    float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                                                    // 计算half向量, 使用Blinn-phone计算高光
                                                    float3 halfDir = normalize(viewDir + worldLightDir);
                                                    // 计算NoH用于计算高光
                                                    float NoH = dot(normal, halfDir);
                                                    // 计算高光亮度值
                                                    float blinnPhone = pow(max(0, NoH), _SpecPow * 128.0);
                                                    // 计算高光色彩
                                                    float3 specularColor = smoothstep(0.7 - _SpecSmooth / 2, 0.7 + _SpecSmooth / 2, blinnPhone)
                                                                            * _SpecularColor * _SpecIntensity;
                                    
                                                    //------------------------ 边缘光 ------------------------
                                                    // 计算NoV用于计算边缘光
                                                    float NoV = dot(i.worldNormal, viewDir);
                                                    // 计算边缘光亮度值
                                                    float rim = (1 - max(0, NoV)) * NoL;
                                                    // 计算边缘光颜色
                                                    float3 rimColor = smoothstep(_RimThreshold - _RimSmooth / 2, _RimThreshold + _RimSmooth / 2, rim) * _RimColor;
                                    
                                                    //------------------------ 色阶 ------------------------
                                                    // 通过亮度值计算线性ramp
                                                    float ramp = linearstep(_RampStart, _RampStart + _RampSize, halfLambert);
                                                    float step = ramp * _RampStep;  // 使每个色阶大小为1, 方便计算
                                                    float gridStep = floor(step);   // 得到当前所处的色阶
                                                    float smoothStep = smoothstep(gridStep, gridStep + _RampSmooth, step) + gridStep;
                                                    ramp = smoothStep / _RampStep;  // 回到原来的空间
                                                    // 得到最终的ramp色彩
                                                    float3 rampColor = lerp(_DarkColor, _LightColor, ramp);
                                                    rampColor *= col;
                                                    
                                                    // 混合颜色
                                                    float3 finalColor = saturate(rampColor + specularColor + rimColor);
                                                    return float4(finalColor,1);
                                                }
                                                ENDCG
                                            }
                                            
                                            Pass
                                            {
                                                Cull Front
                                                CGPROGRAM
                                                #pragma vertex vert
                                                #pragma fragment frag
                                    
                                                #include "UnityCG.cginc"
                                    
                                                struct appdata
                                                {
                                                    float4 vertex : POSITION;
                                                    float2 uv : TEXCOORD0;
                                                    // 法线
                                                    float3 normal : NORMAL;
                                                };
                                    
                                                struct v2f
                                                {
                                                    float2 uv : TEXCOORD0;
                                                    float4 vertex : SV_POSITION;
                                                };
                                    
                                                sampler2D _MainTex;
                                                float4 _MainTex_ST;
                                    
                                                // 线条宽度
                                                float _OutlineWidth;
                                                // 线条颜色
                                                float4 _OutLineColor;
                                    
                                                v2f vert (appdata v)
                                                {
                                                    v2f o;
                                                    float4 newVertex = float4(v.vertex.xyz +  normalize(v.normal) * _OutlineWidth * 0.05,1);
                                                    o.vertex = UnityObjectToClipPos(newVertex);
                                                    return o;
                                                }
                                    
                                                fixed4 frag (v2f i) : SV_Target
                                                {
                                                    return _OutLineColor;
                                                }
                                                ENDCG
                                            }
                                        }
                                        fallback"Diffuse"
                                    }

你可能感兴趣的:(UnityShader,unity,3d)