• Unity Shader 屏幕后效果——边缘检测


    关于屏幕后效果的控制类详细见之前写的另一篇博客:

    https://www.cnblogs.com/koshio0219/p/11131619.html

    这篇主要是基于之前的控制类,实现另一种常见的屏幕后效果——边缘检测

    概念和原理部分:

    首先,我们需要知道在图形学中经常处理像素的一种操作——卷积

    卷积操作的实质在于,对于图像中的每个像素与其周围的像素进行的重新融合计算行为,以得到不同的像素处理效果,例如锐化图像模糊图像检测边缘等。

    卷积操作通过不同的像素融合算法能得到各不相同的效果,这主要依赖于卷积核

    可以把卷积核看作是一个n行n列方阵,原始像素则位于方阵的中心。

    边缘检测的卷积核也叫边缘检测算子,以Sobel算子为例,形如:


    需要特别注意的是,这里的Sobel算子是基于坐标轴以屏幕左上为原点,右下分别为+x,+y方向的,而不是类似于uv坐标轴的以屏幕左下为原点,右上分别为+x,+y方向的。这一点需要特别注意,不然后面的程序很容易写错。

    其中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):背景颜色,当只显示边缘线时,可以很清晰看出

    基类脚本见:

    https://www.cnblogs.com/koshio0219/p/11131619.html


    下面是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             
     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 }

    效果如下:

     
  • 相关阅读:
    python mock知识02
    python+selenium实现web自动化
    接口幂等性
    Nacicat for Oracle 绿色版 亲测可用
    isinstance、流程控制(单项、双项、多项、巢状)
    算数运算符、比较运算符、赋值运算符、成员运算符、身份运算符、逻辑运算符、位运算符
    集合、字典、哈希算法、变量的缓存机制、小数据池、强制类型转换
    变量、编码知识、数据类型、字符串格式化
    Python基础(电脑基础知识、原码补码及反码)
    函数操作
  • 原文地址:https://www.cnblogs.com/koshio0219/p/11137155.html
Copyright © 2020-2023  润新知