Unity Shader学习笔记(27)渲染轮廓线(描边)方法、卡通风格渲染、素描风格渲染

发表于2018-01-02
评论1 9.3k浏览
Unity自带了很多shader,其中就包含描边、卡通风格和素描风格的shade,考虑到有些人对这积累风格渲染还不是很熟悉,下面就一一给大家介绍下渲染轮廓线(描边)、卡通风格渲染和素描风格渲染。

渲染轮廓线(描边)

五种方法: 
- 基于观察角度和表面法线。即视角方向和表面法线点乘结果判断(接近0,表示面接近垂直与视角)。 
- 过程式几何。两个Pass:第一个Pass渲染背面并且让轮廓可见(顶点外扩);第二个Pass正常渲染正面。适应于大多数表面平滑的模型,不适合立方体等平整模型。 
- 基于图像处理。前面两种边缘检测就是了:直接用算子判断使用深度和法线纹理。但如果一些深度和法线变化很小的无法检测出来,如桌子上一张纸。 
- 基于轮廓边。检测方法:相邻两个三角是否一个面向视角,另一个背向视角,就是边。即 (n0·v > 0) ≠ (n1·v > 0)。 缺点是轮廓是逐帧单独提取的,帧与帧之间会出现跳跃性。 
- 混合上述方法。首先找到轮廓线(方法4),把模型和轮廓边渲染到纹理中,再使用图像处理识别轮廓线(方法3),在图像空间进行风格化渲染。


卡通风格渲染

使用描边方法2,以及卡通风格的高光反射光照模型实现。 
描边注意:在背面顶点扩展时,可能有正面的面,这时会出错。可想象一个半实心球,内部有个面向视角的面,顶点外扩时,内部的面会超出外表面,遮挡了外表面。解决办法是先让背面法线z值减0.5再归一化(背面压扁)。 
高光实现:镜面反射使用Blinn模型,计算(n·h)时,做阈值比较,小于的就是高光部分。 

Shader "Custom/Chapter 14/Toon Shading" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _MainTex ("Main Tex", 2D) = "white" {}
        _Ramp ("Ramp Texture", 2D) = "white" {}                     // 漫反射渐变纹理
        _Outline ("Outline", Range(0, 1)) = 0.1                     // 轮廓线宽度
        _OutlineColor ("Outline Color", Color) = (0, 0, 0, 1)       // 轮廓线颜色
        _Specular ("Specular", Color) = (1, 1, 1, 1)
        _SpecularScale ("Specular Scale", Range(0, 0.1)) = 0.01     // 高光反射的阈值
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        // 第一个Pass,延展背面顶点,实现描边。
        Pass {
            NAME "OUTLINE"
            Cull Front      // 剔除正面,只渲染正面
            ...
            v2f vert (a2v v) {
                v2f o;
                // 顶点和法线变换到视角空间下,让描边可以在观察空间达到最好的效果
                float4 pos = mul(UNITY_MATRIX_MV, v.vertex); 
                float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);  
                normal.z = -0.5;    // 让法线向视角方向外扩,避免物体有背面遮挡正面
                pos = pos + float4(normalize(normal), 0) * _Outline;        //对外扩展,出现轮廓
                o.pos = mul(UNITY_MATRIX_P, pos);
                return o;
            }
            ...
        }
        // 第二个Pass,基于Blinn光照模型,使用自定义高光光照模型,实现高光。
        Pass {
            Tags { "LightMode"="ForwardBase" }
            Cull Back
            ...
            float4 frag(v2f i) : SV_Target { 
                ...
                fixed spec = dot(worldNormal, worldHalfDir);
                // 高光区域抗锯齿操作,fwidth:邻域像素的近似导数。
                fixed w = fwidth(spec) * 2.0;       
                // _SpecularScale为0会完全消除高光反射,smoothstep:小于-w为0,大于w为1,否则在0到1之间插值。
                fixed3 specular = _Specular.rgb * lerp(0, 1, smoothstep(-w, w, spec + _SpecularScale - 1)) * step(0.0001, _SpecularScale);    
                return fixed4(ambient + diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

素描风格渲染

通过判断不同光照下漫反射值,使用不同的渲染纹理。纹理组成一个色调艺术映射(Tonal Art Map, TAM)。 

// 素描风格
Shader "Custom/Chapter 14/Hatching" {
    Properties {
        _Color ("Color Tint", Color) = (1, 1, 1, 1)
        _TileFactor ("Tile Factor", Float) = 1  // 平铺系数
        _Outline ("Outline", Range(0, 1)) = 0.1
        _Hatch0 ("Hatch 0", 2D) = "white" {}    // 6张素描纹理,模拟不同阴影使用的纹理
        _Hatch1 ("Hatch 1", 2D) = "white" {}
        _Hatch2 ("Hatch 2", 2D) = "white" {}
        _Hatch3 ("Hatch 3", 2D) = "white" {}
        _Hatch4 ("Hatch 4", 2D) = "white" {}
        _Hatch5 ("Hatch 5", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry"}
        UsePass "Custom/Chapter 14/Toon Shading/OUTLINE"    // 使用前面的描边渲染Pass
        Pass {
            ...
            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                fixed3 hatchWeights0 : TEXCOORD1;   // 6张纹理存在两个fixed3中
                fixed3 hatchWeights1 : TEXCOORD2;
                float3 worldPos : TEXCOORD3;
                SHADOW_COORDS(4)
            };
            v2f vert(a2v v) {
                ...
                o.hatchWeights0 = fixed3(0, 0, 0);
                o.hatchWeights1 = fixed3(0, 0, 0);
                float hatchFactor = diff * 7.0; // 放大7倍,便于分成7块,混合权重。
                if (hatchFactor > 6.0) {
                    // 纯白
                } else if (hatchFactor > 5.0) {
                    o.hatchWeights0.x = hatchFactor - 5.0;
                } else if (hatchFactor > 4.0) {
                    o.hatchWeights0.x = hatchFactor - 4.0;
                    o.hatchWeights0.y = 1.0 - o.hatchWeights0.x;
                } else if (hatchFactor > 3.0) {
                    o.hatchWeights0.y = hatchFactor - 3.0;
                    o.hatchWeights0.z = 1.0 - o.hatchWeights0.y;
                } else if (hatchFactor > 2.0) {
                    o.hatchWeights0.z = hatchFactor - 2.0;
                    o.hatchWeights1.x = 1.0 - o.hatchWeights0.z;
                } else if (hatchFactor > 1.0) {
                    o.hatchWeights1.x = hatchFactor - 1.0;
                    o.hatchWeights1.y = 1.0 - o.hatchWeights1.x;
                } else {
                    o.hatchWeights1.y = hatchFactor;
                    o.hatchWeights1.z = 1.0 - o.hatchWeights1.y;
                }
                o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                TRANSFER_SHADOW(o);
                return o; 
            }
            fixed4 frag(v2f i) : SV_Target {            
                fixed4 hatchTex0 = tex2D(_Hatch0, i.uv) * i.hatchWeights0.x;
                fixed4 hatchTex1 = tex2D(_Hatch1, i.uv) * i.hatchWeights0.y;
                fixed4 hatchTex2 = tex2D(_Hatch2, i.uv) * i.hatchWeights0.z;
                fixed4 hatchTex3 = tex2D(_Hatch3, i.uv) * i.hatchWeights1.x;
                fixed4 hatchTex4 = tex2D(_Hatch4, i.uv) * i.hatchWeights1.y;
                fixed4 hatchTex5 = tex2D(_Hatch5, i.uv) * i.hatchWeights1.z;
                // 全白和最浅的纹理混合。
                fixed4 whiteColor = fixed4(1, 1, 1, 1) * (1 - i.hatchWeights0.x - i.hatchWeights0.y - i.hatchWeights0.z - 
                            i.hatchWeights1.x - i.hatchWeights1.y - i.hatchWeights1.z);
                fixed4 hatchColor = hatchTex0 + hatchTex1 + hatchTex2 + hatchTex3 + hatchTex4 + hatchTex5 + whiteColor;
                UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
                return fixed4(hatchColor.rgb * _Color.rgb * atten, 1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}

Unity Shader学习笔记系列教程:

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引