1. 程式人生 > 實用技巧 >UnityShader——邊緣檢測

UnityShader——邊緣檢測

目錄

邊緣檢測

邊緣檢測有兩種方式:
色差檢測:以畫素中心周圍的畫素顏色為根據判斷中心畫素點是不是在邊緣線上。
深度檢測:檢測畫素點所對應的視角空間中的法線(and/or)深度,以此做判斷當前點是否在邊緣上。

色差檢測

我們可以首先回想一下邊到底是如何形成的。如果相鄰畫素之間存在差別明顯的顏色、亮度、紋理等屬性,我們就會認為它們之間應該有一條邊界。這種相鄰畫素之間的差值可以用梯度(gradient)來表示,可以想象得到,邊緣處的梯度絕對值會比較大。基於這樣的理解,有幾種不同的邊緣檢測運算元被先後提出來,分別是Robert運算元、Prewitt運算元、Sobel運算元。
Robert運算元:
\(\begin{bmatrix} -1 & 0 \\ 0 & -1 \end{bmatrix}\)

Prewitt運算元:
\(\begin{bmatrix} -1 & -1 & -1 \\ 0 & 0 & 0 \\ 1 & 1 & 1 \end{bmatrix}\)

Sobel運算元:
\(\begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}\)

色差檢測邊緣的原理:用一個卷積核做卷積運算,這個卷積核一般採用Sobel運算元,先把卷積核置於畫素中心做卷積運算,再翻轉卷積核做一次卷積運算(其實就相當於先求出水平X方向梯度差Gx,再求出垂直y方向上梯度差Gy),再根據這兩個梯度值得到整體梯度值G。
\(G = \sqrt{Gx^2 + Gy^2}\)


但是在Shader中,因為開根號的開銷比較大所以,可以用加法運算代替。
\(G = \|Gx\| + \|Gy\|\)
根據這個梯度值G可以檢測哪些畫素點是邊緣。
下面以Sobel運算元為例寫一個Shader:

//C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class WSPostEffect : MonoBehaviour
{
    public Material material = null;
    public bool IsEdgeOnly;
    public Color edgeColor;
    public Color backgroundColor;
    public float edgeFactor;
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material == null)
            Graphics.Blit(source, destination);
        else
        {
            material.SetFloat("_EdgeOnly", IsEdgeOnly ? 1.0f : 0.0f);
            material.SetColor("_EdgeColor", edgeColor);
            material.SetColor("_BackgroundColor", backgroundColor);
            material.SetFloat("_EdgeFactor", edgeFactor);
            Graphics.Blit(source, destination, material, -1);
        }
    }
}

//Shader
Shader "WS/EdgeDetect"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		Tags { "RenderType"="Transparent" "Queue"="Transparent" }
		LOD 100

		Pass
		{
			ZTest Always
			ZWrite Off
			Cull Off
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
			};

			struct v2f
			{
                                //畫素點及其周圍的9個uv座標
				float2 uv[9] : TEXCOORD0;
				float4 vertex : SV_POSITION;
			};

			sampler2D _MainTex;
			float4 _MainTex_ST;
			half4 _MainTex_TexelSize;
                        //是否只顯示邊緣線條
			fixed _EdgeOnly;
                        //邊緣線顏色
			fixed4 _EdgeColor;
                        //背景色
			fixed4 _BackgroundColor;
			

			
			v2f vert (appdata v)
			{
				v2f o;
				o.vertex = UnityObjectToClipPos(v.vertex);
				half2 uv = v.uv;
                                //計算限速點及其周圍的9個uv座標,放在頂點著色器計算能減少
				o.uv[0] = uv + _MainTex_TexelSize.xy * (-1, -1);
				o.uv[1] = uv + _MainTex_TexelSize.xy * (0, -1);
				o.uv[2] = uv + _MainTex_TexelSize.xy * (1, -1);
				o.uv[3] = uv + _MainTex_TexelSize.xy * (-1, 0);
				o.uv[4] = uv + _MainTex_TexelSize.xy * (0, 0);
				o.uv[5] = uv + _MainTex_TexelSize.xy * (1, 0);
				o.uv[6] = uv + _MainTex_TexelSize.xy * (-1, 1);
				o.uv[7] = uv + _MainTex_TexelSize.xy * (0, 1);
				o.uv[8] = uv + _MainTex_TexelSize.xy * (1, 1);
				return o;
			}

                        //計算畫素的明度值
			fixed luminance(fixed4 color)
			{
				return 0.299 * color.r + 0.587 * color.g + 0.114 * color.b;
			}

			half Sobel(v2f v)
			{
                                //Sobel運算元及其翻轉後的運算元
				const half Gx[9] = {-1, -2, -1,
						    0, 0, 0,
			    			    1, 2, 1};
				const half Gy[9] = {-1, 0, 1,
						   -2, 0, 2,
						   -1, 0, 1};
				half texColor;
                                //水平梯度值
				half edgeX = 0;
                                //垂直梯度值
				half edgeY = 0;
				for (int i = 0; i < 9; ++i)
				{
					texColor = luminance(tex2D(_MainTex, v.uv[i]));
					edgeX += texColor * Gx[i];
					edgeY += texColor * Gy[i];
				}
                                //越小越是邊緣
				half edge = 1 - sqrt(edgeX * edgeX + edgeY * edgeY);
				//關注效能時可改為
				//half edge = 1 - abs(edgeX) - abs(edgeY);
				return edge;
			}
			
			fixed4 frag (v2f i) : SV_Target
			{
				half edge = Sobel(i);
				fixed4 edgeOnlyColor = lerp(_EdgeColor, _BackgroundColor, edge);
				fixed4 edgeWithColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), step(0.3, edge));
				return lerp(edgeWithColor, edgeOnlyColor, _EdgeOnly);
			}
			ENDCG
		}
	}
}

效果:
使用前:

使用後: