Unity Shader 屏幕後效果——邊緣檢測

關於屏幕後效果的控制類詳細見以前寫的另外一篇博客:html

http://www.javashuo.com/article/p-zhugcbxg-dw.html算法

這篇主要是基於以前的控制類,實現另外一種常見的屏幕後效果——邊緣檢測app

 

概念和原理部分:spa

 

首先,咱們須要知道在圖形學中常常處理像素的一種操做——卷積code

卷積操做的實質在於,對於圖像中的每一個像素與其周圍的像素進行的從新融合計算行爲,以獲得不一樣的像素處理效果,例如銳化圖像模糊圖像檢測邊緣等。htm

 

卷積操做經過不一樣的像素融合算法能獲得各不相同的效果,這主要依賴於卷積核blog

能夠把卷積核看做是一個n行n列方陣,原始像素則位於方陣的中心。繼承

 

邊緣檢測的卷積核也叫邊緣檢測算子,以Sobel算子爲例,形如:ip


須要特別注意的是,這裏的Sobel算子是基於座標軸以屏幕左上爲原點,右下分別爲+x,+y方向的,而不是相似於uv座標軸的以屏幕左下爲原點,右上分別爲+x,+y方向的。這一點須要特別注意,否則後面的程序很容易寫錯。get

其中GxGy分別是縱向和橫向兩個方向的邊緣線檢測,你能夠經過去掉矩陣中的零元素來想象,由於零元素不會對像素產生任何影響。也就是說,Gx是爲了計算橫向的梯度值Gy爲了計算縱向的梯度值。

橫向的梯度值檢測出來的是縱向的邊緣線,縱向的梯度值檢測出來的是橫向的邊緣線。這一點很是容易混淆,須要特別注意。

 

利用邊緣檢測算子除了融合像素外,主要是爲了計算出像素的梯度值

一個像素和周圍的像素以前梯度值很高,意味着它與周圍的像素差別很大,咱們能夠想象這個像素和周圍的像素格格不入,存在一個沒法逾越的階梯;那麼就能夠這麼認爲,這個像素能夠做爲一條邊界中的值

對圖像中的每一個像素都如此處理,最終就能獲得圖像的邊緣。這也就是邊緣檢測的實質內容。

 

計算方法:

 

1.獲得每一個像素周圍的8個像素的座標位置以便與Sobel算子進行計算,相似於:(排列方式應該與Sobel算子的座標軸保持一致)

uv[0] uv[1] uv[2]
uv[3] uv[4](原始像素點) uv[5]
uv[6] uv[7] uv[8]

 

 

 

 

但由於uv座標的原點在左下角,所以在計算uv[0]-uv[8]時,若依據uv[4]爲原始像素點,則它們的偏移能夠表示爲以下狀況:

(-1,1)uv[0] (0,1)uv[1] (1,1)uv[2]
(-1,0)uv[3] (0,0)uv[4] (1,0)uv[5]
(-1,-1)uv[6] (0,-1)uv[7] (1,-1)uv[8]

 

 

 

 

 

2.經過偏移值能夠很快計算出目標像素的周圍像素位置座標信息,隨後與GxGy對應元素分別進行橫向和縱向的梯度值計算,也就是分別進行縱向和橫向的邊緣檢測:

具體計算方法爲:先對卷積核進行180度翻轉,獲得新的矩陣,隨後各項對應元素相乘並相加,注意,不要與矩陣的乘法計算混淆。

但由於Sobel算子是否執行飯莊操做對計算結果沒有任何影響,故對於Sobel算子來講,翻轉操做能夠省略。

GxGy計算結束後再將它們開平方和;但每每爲了簡化GPU的計算量,能夠直接取各自的絕對值再相加,獲得最終的梯度值G

 

3.計算出梯度值後對原始的採樣結果進行關於G的插值操做以獲得最終的圖像。

 

程序實現:

 

首先是參數調控的腳本:

 
 
 1 using UnityEngine;
 2 
 3 public class EdgeDetectionCtrl : ScreenEffectBase
 4 {
 5     private const string _EdgeOnly = "_EdgeOnly";
 6     private const string _EdgeColor = "_EdgeColor";
 7     private const string _BackgroundColor = "_BackgroundColor";
 8 
 9     [Range(0,1)]
10     public float edgeOnly = 0.0f;
11 
12     public Color edgeColor = Color.black;
13 
14     public Color backgroundColor = Color.white;
15 
16     private void OnRenderImage(RenderTexture source, RenderTexture destination)
17     {
18         if (Material!=null)
19         {
20             Material.SetFloat(_EdgeOnly, edgeOnly);
21             Material.SetColor(_EdgeColor, edgeColor);
22             Material.SetColor(_BackgroundColor, backgroundColor);
23             Graphics.Blit(source, destination, Material);
24         }
25         else
26             Graphics.Blit(source, destination);
27     }
28 }
 

一樣是繼承自ScreenEffectBase基類,三個參數的意義分別以下:

edgeOnly(shader中:_EdgeOnly):邊緣線的疊加程度,0表示徹底疊加,1表示只顯示邊緣線,不顯示原圖
edgeColor(shader中:_EdgeColor):邊緣線的顏色
backgroundColor(shader中:_BackgroundColor):背景顏色,當只顯示邊緣線時,能夠很清晰看出

下面是Shader腳本:

  1 Shader "MyUnlit/EdgeDetection"
  2 {
  3     Properties
  4     {
  5         _MainTex ("Texture", 2D) = "white" {}
  6     }
  7     SubShader
  8     {
  9         Tags { "RenderType"="Opaque" }
 10 
 11         Pass
 12         {
 13             ZTest always
 14             Cull off
 15             ZWrite off
 16 
 17             CGPROGRAM
 18             #pragma vertex vert
 19             #pragma fragment frag
 20             // make fog work
 21             #pragma multi_compile_fog
 22 
 23             #include "UnityCG.cginc"
 24 
 25             struct appdata
 26             {
 27                 float4 vertex : POSITION;
 28                 float2 uv : TEXCOORD0;
 29             };
 30 
 31             struct v2f
 32             {
 33                 half2 uv[9] : TEXCOORD0;
 34                 UNITY_FOG_COORDS(1)
 35                 float4 pos : SV_POSITION;
 36             };
 37 
 38             sampler2D _MainTex;
 39             //紋理映射到[0,1]以後的大小,用於計算相鄰區域的紋理座標
 40             half4 _MainTex_TexelSize;
 41             //定義控制腳本中對應的參數
 42             fixed _EdgeOnly;
 43             fixed4 _EdgeColor;
 44             fixed4 _BackgroundColor;
 45 
 46             v2f vert (appdata v)
 47             {
 48                 v2f o;
 49                 o.pos = UnityObjectToClipPos(v.vertex);
 50 
 51                 half2 uv = v.uv;
 52                 half2 size = _MainTex_TexelSize;
 53                 //計算周圍像素的紋理座標位置,其中4爲原始點,右側乘積因子爲偏移的像素單位,座標軸爲左下角原點,右上爲+x,+y方向,與uv的座標軸匹配
 54                 o.uv[0] = uv + size * half2(-1, 1);
 55                 o.uv[1] = uv + size * half2(0, 1);
 56                 o.uv[2] = uv + size * half2(1, 1);
 57                 o.uv[3] = uv + size * half2(-1, 0);
 58                 o.uv[4] = uv + size * half2(0, 0);
 59                 o.uv[5] = uv + size * half2(1, 0);
 60                 o.uv[6] = uv + size * half2(-1, -1);
 61                 o.uv[7] = uv + size * half2(0, -1);
 62                 o.uv[8] = uv + size * half2(1, -1);
 63 
 64                 UNITY_TRANSFER_FOG(o,o.pos);
 65                 return o;
 66             }
 67             //計算對應像素的最低灰度值並返回
 68             fixed minGrayCompute(v2f i,int idx) 
 69             {
 70                 return Luminance(tex2D(_MainTex, i.uv[idx]));
 71             }
 72             //利用Sobel算子計算最終梯度值
 73             half sobel(v2f i) 
 74             {
 75                 const half Gx[9] = {
 76                     - 1,0,1,
 77                     - 2,0,2,
 78                     - 1,0,1
 79                 };
 80                 const half Gy[9] = {
 81                     -1,-2,-1,
 82                      0, 0, 0,
 83                      1, 2, 1
 84                 };
 85                 //分別計算橫向和縱向的梯度值,方法爲各項對應元素相乘並相加
 86                 half graX = 0;
 87                 half graY = 0;
 88 
 89                 for (int it = 0; it < 9; it++) 
 90                 {
 91                     graX += Gx[it] * minGrayCompute(i, it);
 92                     graY += Gy[it] * minGrayCompute(i, it);
 93                 }
 94                 //絕對值相加近似模擬最終梯度值
 95                 return abs(graX) + abs(graY);
 96              }
 97 
 98             fixed4 frag (v2f i) : SV_Target
 99             {
100                 half gra = sobel(i);
101                 fixed4 col = tex2D(_MainTex, i.uv[4]);
102                 //利用獲得的梯度值進行插值操做,其中梯度值越大,越接近邊緣的顏色
103                 fixed4 withEdgeColor = lerp( col, _EdgeColor, gra);
104                 fixed4 onlyEdgeColor = lerp( _BackgroundColor, _EdgeColor, gra);
105                 fixed4 color = lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
106 
107                 UNITY_APPLY_FOG(i.fogCoord, color);
108                 return color;
109             }
110             ENDCG
111         }
112     }
113 }

效果以下:

相關文章
相關標籤/搜索