Unity Shader入门教程(十八):卷积与边缘检测
发表于2018-06-07
一、什么是卷积



在图像处理中,卷积操作指的就是使用一个卷积核(kernel)对一张图像中的每个像素进行一系列的操作。
卷积核通常是一个四方形的网格结构(例如2x2、3x3的方形区域),该区域内每个方格都有一个权重值。
党对图像中的某个像素进行卷积是,我们会把卷积核的中心放置于该像素上,如下图所示,翻转核之后再依次计算核中的每个元素和其覆盖的图像像素值的乘积并求和,得到的结果就是改为的新像素值。

使用一个3x3大小的卷积核对一张5x5大小的图像进行卷积操作,当计算图中红色方块对应的像素的卷积结果是,我们首先把卷积的中心防止在该像素的位置,翻转核之后再依次计算核中的每个元素和其覆盖的图像像素值的乘积并求和,得到新的像素值。
这样的简单的计算过程可以实现很多常见的图像处理效果,如图像模糊、边缘检测等。
如果我们想要对图像进行均值模糊,可以使用一个3x3的卷积核,核内每个元素的值均为1/9。
二、常见的边缘检测算子
常见的边缘检测算子有三种,它们用来计算相邻像素之间的差值——梯度,边缘处的梯度绝对值会比较大。

上面的算子,它们都包含了两个方向的卷积核,分别用于检测水平方向和数值方向上的边缘信息。在进行边缘检测时,我们需要对每个像素分别进行一次卷积计算,得到两个方向上的梯度值Gx、Gx,而整体的梯度值可按下面的公式计算而得:
G = {G2x +G2y}
出于性能考虑,往往使用绝对值操作来代替开根号操作:
G =| Gx | + | Gy |
得到了梯度值G后,我们就可以据此来判断那些像素对应了边缘(梯度越大,越有可能是边缘点)
三、Sobel算子实现描边效果
1、代码实践
shader 代码
Shader "Custom/Edu/EdgeCheck" {
Properties {
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_EdgeOnly("Edge Only",Range(0,1)) = 0.5
_EdgeColor("Edge Color",Color) = (1,1,1,1)
_BackgroundColor("BackGround Color",Color) =(1,1,1,1)
}
CGINCLUDE
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_TexelSize;
fixed _EdgeOnly;
fixed3 _EdgeColor;
fixed3 _BackgroundColor;
struct a2v
{
float4 vertex:POSITION;
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
half2 uv[9]:TEXCOORD0;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
half2 uv = v.texcoord;
o.uv[0] = uv + half2(-1,-1) * _MainTex_TexelSize;
o.uv[1] = uv + half2(0,-1) * _MainTex_TexelSize;
o.uv[2] = uv + half2(1,-1) * _MainTex_TexelSize;
o.uv[3] = uv + half2(-1,0) * _MainTex_TexelSize;
o.uv[4] = uv + half2(0,0) * _MainTex_TexelSize;
o.uv[5] = uv + half2(1,0) * _MainTex_TexelSize;
o.uv[6] = uv + half2(-1,1) * _MainTex_TexelSize;
o.uv[7] = uv + half2(0,1) * _MainTex_TexelSize;
o.uv[8] = uv + half2(1,1) * _MainTex_TexelSize;
return o;
}
//计算各个颜色通道分量对亮度贡献
fixed luminance(fixed3 color)
{
return color.r * 0.212 + color.g * 0.715 + color.b * 0.072;
}
half sobel(v2f i)
{
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 edgeX;
half edgeY;
for(int it=0;it<9;it++)
{
half lum = luminance(tex2D(_MainTex,i.uv[it]).rgb);
edgeX += lum * Gx[it];
edgeY += lum * Gy[it];
}
return 1 - abs(edgeX) - abs(edgeY);
}
fixed4 frag(v2f i):SV_Target
{
//G值越大表示梯度越小
half G = sobel(i);
fixed3 withEdgeColor = lerp(_EdgeColor,tex2D(_MainTex,i.uv[4]).rgb,G);
fixed3 edgeOnlyColor = lerp(_EdgeColor,_BackgroundColor,G);
return fixed4(lerp(withEdgeColor,edgeOnlyColor,_EdgeOnly),1.0);
}
ENDCG
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
ENDCG
}
}
FallBack "Diffuse"
}
cs 代码
using UnityEngine;
using System.Collections;
using UnityEngine.SocialPlatforms;
using System.Security.Cryptography;
public class EdgeCheck : PostEffectsBase {
public Shader shader;
private Material edgeCheckMat;
public Material mat
{
get
{
if(edgeCheckMat==null)
{
edgeCheckMat = CheckShaderAndCreateMaterial (shader, edgeCheckMat);
}
return edgeCheckMat;
}
}
[Range(0f,1f)]
public float edgeOnly = 0.1f;
public Color edgeColor = Color.black;
public Color backgroundColor = Color.white;
void OnRenderImage(RenderTexture src,RenderTexture dest)
{
if (mat != null)
{
mat.SetFloat ("_EdgeOnly", edgeOnly);
mat.SetColor ("_EdgeColor", edgeColor);
mat.SetColor ("_BackgroundColor", backgroundColor);
Graphics.Blit (src, dest, mat);
}
else
{
Graphics.Blit (src, dest);
}
}
}
2、效果图

