unity屏幕特效綜述4 邊緣檢測

在這裏插入圖片描述在這裏插入圖片描述在這裏插入圖片描述

這個屏幕特效我挺喜歡的,可以用來描邊,也可以用來提取邊緣,獲得很多很棒的效果。
什麼樣的點可能是邊緣部分所在的點呢,如果該點附近的法線值或者是深度值相差的很多,那麼這個點就可以被認爲是一條邊,具體的值可以用參數來控制。法線以及深度的差值我們通過卷積來獲取,使用2x2的矩陣(-1,0,0,1)檢測x方向以及2x2矩陣(0,-1,1,0)檢測y方向,這個卷積核叫做Roberts算子。
這個屏幕特效的腳本部分沒什麼好說的,就是直接給shader的每一個變量進行賦值,我們直接從shader開始討論起,先貼一個完整代碼:

Shader "Unity Shaders Book/Chapter 13/Edge Detection Normals And Depth" {
       Properties {
              _MainTex ("Base (RGB)", 2D) = "white" {}
              _EdgeOnly ("Edge Only", Float) = 1.0
              _EdgeColor ("Edge Color", Color) = (0, 0, 0, 1)
              _BackgroundColor ("Background Color", Color) = (1, 1, 1, 1)
              _SampleDistance ("Sample Distance", Float) = 1.0
              _Sensitivity ("Sensitivity", Vector) = (1, 1, 1, 1)
       }
       SubShader {
              CGINCLUDE
              
              #include "UnityCG.cginc"
              
              sampler2D _MainTex;
              half4 _MainTex_TexelSize;
              fixed _EdgeOnly;
              fixed4 _EdgeColor;
              fixed4 _BackgroundColor;
              float _SampleDistance;
              half4 _Sensitivity;
              
              sampler2D _CameraDepthNormalsTexture;
              
              struct v2f {
                     float4 pos : SV_POSITION;
                     half2 uv[5]: TEXCOORD0;
              };
                
              v2f vert(appdata_img v) {
                     v2f o;
                     o.pos = UnityObjectToClipPos(v.vertex);
                     
                     half2 uv = v.texcoord;
                     o.uv[0] = uv;
                     
                     #if UNITY_UV_STARTS_AT_TOP
                     if (_MainTex_TexelSize.y < 0)
                           uv.y = 1 - uv.y;
                     #endif
                     
                     o.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) *  _SampleDistance;
                     o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) *  _SampleDistance;
                     o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) *  _SampleDistance;
                     o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) *  _SampleDistance;
                                   
                     return o;
              }
              
              half CheckSame(half4 center, half4 sample) {
                     half2 centerNormal = center.xy;
                     float centerDepth = DecodeFloatRG(center.zw);
                     half2 sampleNormal = sample.xy;
                     float sampleDepth = DecodeFloatRG(sample.zw);
                     
                     // difference in normals
                     // do not bother decoding normals - there's no need here
                     half2 diffNormal = abs(centerNormal - sampleNormal) *  _Sensitivity.x;      
                     int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
                     // difference in depth
                     float diffDepth = abs(centerDepth - sampleDepth) *  _Sensitivity.y;
                     // scale the required threshold by the distance
                     int isSameDepth = diffDepth < 0.1 * centerDepth;
                     
                     // return:
                     // 1 - if normals and depth are similar enough
                     // 0 - otherwise
                     return isSameNormal * isSameDepth ? 1.0 : 0.0;
              }
              
              fixed4 fragRobertsCrossDepthAndNormal(v2f i) : SV_Target {
                     half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
                     half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
                     half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
                     half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);
                     
                     half edge = 1.0;
                     
                     edge *= CheckSame(sample1, sample2);
                     edge *= CheckSame(sample3, sample4);
                     
                     fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex,  i.uv[0]), edge);
                     fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor,  edge);
                     
                     return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
              }
              
              ENDCG
              
              Pass {
                     ZTest Always Cull Off ZWrite Off
                     
                     CGPROGRAM      
                     
                     #pragma vertex vert  
                     #pragma fragment fragRobertsCrossDepthAndNormal
                     
                     ENDCG  
              }
       }
       FallBack Off
}

首先我們討論一下着色器的變量部分,
_Maintex沒什麼好說的,就是攝像機傳來的渲染紋理。
_EdgeOnly用來控制背景和非邊緣區域圖像混合的比例
_EdgeColor表示邊的顏色
_BackgroundColor表示背景顏色
_SampleDistance用來控制進行卷積的採樣距離
_Sensitivity的第一項用來控制法線紋理的敏感性,第二項用來控制深度紋理的敏感性,後面兩個項沒有實際作用

然後就是頂點着色器部分,這個部分的作用就是計算採樣座標,也就是給uv賦值,根據我們的Roberts算子,採樣座標分別爲右上左下以及左上右下,下面的代碼的作用就是賦值好採樣座標,採樣座標的具體值還會根據_SampleDistance進行偏移。

然後就是具體的判斷是不是邊緣的函數了,這個函數也是簡單粗暴,要麼是邊,要麼不是邊緣,也就是隻返回0或者1,具體的判斷方式爲把邊緣的像素點和中心的像素點進行對比,只要兩者的深度差值或者法線差值有一個不滿足條件,那麼它就是邊緣,具體的判定方法下面代碼我感覺也算是經驗所得,但是很有效。

寫完了判定函數之後,後面就是片元着色器了,往往這個着色器在shader裏面是最重要的。Roberts在x或者y方向只要有一個判定它是邊緣,那麼它就是邊緣,最後3行的差值其實有點亂,乍一看不好理解,要耐心的去分析一下。首先我們要了解到一個重點,edge的值要麼是0,要麼是1,所以withEdgeColor的值要麼是邊,要麼是原來的圖像的值,也就是說withEdgeColor是原圖像的描邊效果,onlyEdgeColor的值要麼是邊,要麼是背景,最後在對這兩個值之間進行差值,換句話說,在描好邊的情況之下,對原圖和背景之間進行差值,就像我一開始放的那兩張圖一樣,分別是原圖,描邊的背景圖,描邊圖。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章