这里讲边缘检测这一节中的笔记内容。边缘检测这里更多的用到了图像学的一些基本内容,包括卷积核,索贝尔算子等等一些内容。整个流程还是比较清晰的。
获取到屏幕渲染的颜色缓冲之后,将屏幕中的像素用索贝尔算子进行卷积操作,卷积的结果就是当前像素的一个导数。这里说导数其实可能比较难懂,说变化程度应该会好理解一些。如果这里颜色突变的厉害那么他的变化程度就会大一些,如果颜色变化比较平坦,那么变换程度就会小一些,计算的结果也会小一些。
这里需要将一些索贝尔算子和图像卷积操作。
⎡⎣⎢−101−202−101⎤⎦⎥=Gx [ − 1 − 2 − 1 0 0 0 1 2 1 ] = G x
⎡⎣⎢−1−2−1000121⎤⎦⎥=Gy [ − 1 0 1 − 2 0 2 − 1 0 1 ] = G y
大家在看书写代码的时候可能会有疑问,正文中写的Gx有值的部分明明是在列上面,第二列为0,其他列有值,Gy为第二行为0.但是在代码中Gx与Gy似乎反了。
在图像处理(冈萨雷斯)这本书里是这样描述Gx与Gy的:
算子与像素相乘,第三行与第一行间的差距接近与x方向上的微分,第三列与第一列间的差接近y方向的微分。(P107,Edition 2);
同时在编码中还有一个问题,不是说卷积操作的时候需要先将算子转180度,然后再对于相乘相加吗?怎么编码的时候又直接对应相乘相机没有旋转了?
答:(图像处理(冈萨雷斯)P92,Edi 2)中提到,线性空间滤波与频率域中的卷积概念类似,因此线性空间滤波也称为“掩膜与图像的卷积”。“滤波掩膜”也被称为“卷积模板”,“卷积核”。
也就是说图像与算子的卷积实际上是一种线性空间滤波,而这种滤波的表达形式就是对应相乘相加。
这里给出mxn大小的线性滤波掩膜的表达式:
g(x,y)=∑as=−a∑bt=−bw(s,t)f(x+s,y+t) g ( x , y ) = ∑ s = − a a ∑ t = − b b w ( s , t ) f ( x + s , y + t )
看到这里应该知道为什么要这样来计算了吧。
边缘检测代码上一波:
Shader "Custom/8/8.1.2" {//边缘检测
Properties{
_MainTex("Main Texture",2D) = "white"{}
_EdgeColor("Edge Color",color) = (1,1,1,1)
_BackgroundColor("Background",Color) = (1,1,1,1)
_EdgeOnly("edgeOnly",float) = 1
}
SubShader{
Pass{
ZTest Always
ZWrite Off
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler _MainTex;
float4 _MainTex_TexelSize;//纹素
float4 _EdgeColor;
float4 _BackgroundColor;
float _EdgeOnly;
struct v2f {
float4 pos : SV_POSITION;
float2 uv[9] : TEXCOORD0;
};
fixed luminance(fixed3 color)
{
return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
}
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 };
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 };
float4 texColor;
float edgex = 0;
float edgey = 0;
for (int it = 0; it < 9; it++)
{
texColor = luminance(tex2D(_MainTex, i.uv[it]));
edgex += texColor * Gx[it];
edgey += texColor * Gy[it];
}
return 1 - abs(edgex) - abs(edgey);
}
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//求纹素
float2 uv = v.texcoord;
o.uv[0] = uv + _MainTex_TexelSize.xy * half2(-1, -1); //左上
o.uv[1] = uv + _MainTex_TexelSize.xy * half2(0, -1); //左上
o.uv[2] = uv + _MainTex_TexelSize.xy * half2(1, -1); //左上
o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1, 0); //左上
o.uv[4] = uv + _MainTex_TexelSize.xy * half2(0, 0);
o.uv[5] = uv + _MainTex_TexelSize.xy * half2(1, 0); //左上
o.uv[6] = uv + _MainTex_TexelSize.xy * half2(-1, 1); //左上
o.uv[7] = uv + _MainTex_TexelSize.xy * half2(0, 1); //左上
o.uv[8] = uv + _MainTex_TexelSize.xy * half2(1, 1); //左上
return o;
}
fixed4 frag(v2f i) :SV_Target{
half edge = sobel(i);
fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[4]), edge);
fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);
return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
ENDCG
}
}
FallBack Off
}
shader代码没有难的地方,唯一需要说的是shader中要避免if和循环语句。