unity屏幕特效综述4 边缘检测

unity屏幕特效综述4 边缘检测unity屏幕特效综述4 边缘检测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的值要么是边,要么是背景,最后在对这两个值之间进行差值,换句话说,在描好边的情况之下,对原图和背景之间进行差值,就像我一开始放的那两张图一样,分别是原图,描边的背景图,描边图。

上一篇:TIA博途中无法添加HSP硬件支持包的解决办法


下一篇:点击滚动到任意位置