接下来几篇主要围绕卷积具体能在图形渲染中实现什么效果,比如常用的边缘检测滤波器(卷积滤波器),这个滤波器不仅在学习卷积的时候基本都会讲到,而且我看过的国内外shader开发书籍中也都会提到,滤波器构成如下:
三维图形学中边缘的意义我们之前讲过,不明白的可以回过去看下,这里我们在来看下二维图形中边缘的意义。二维图形中,因为图形中像素值的不同,会在连续像素差异较大的地方让我们肉眼形成一种边界的视觉,同时边界分为xy两轴组成,所以我们需要xy两个方向的滤波器进行卷积运算。
贴下xy两个方向的滤波器(算子)的数值矩阵,如下:
-1 0 1 -1 -2 -1
x = -2 0 2 y = 0 0 0
-1 0 1 1 2 1
首先我说下sobel卷积核心公式:这两个算子需要和当前像素矩阵的亮度值做点乘,得到的xy轴两方向的值(也称梯度),计算其结合梯度值(G = sqrt(Gx^2 + Gy ^2);),然后将这个梯度值G作为主纹理颜色与边缘颜色混合的权重值即可。
这里需要将RGB转YUV中取Y也就是明亮度(如果做视频直播的同学可能容易理解YUV编码方式的意义和应用,比如占用储存空间更小等)
一般的亮度计算公式: Y(亮度)=(0.299*R)+(0.587*G)+(0.114*B)
当然了还有如下:
这里我们不用算子去点乘RGB是因为Y明亮度作为参数取梯度值更能准确表明连续像素之间的差异。那么对于xy轴两个算子,图形数学敏感一些的话,看得出来x轴算子处理的像素矩阵,如果x正负方向上明亮度差异很大,就能产生一个绝对值较大的梯度值,那么我们就认为这就是“边界”,y轴算子同理。
接着就来shader实现,如下:
Shader "Unlit/EdgeSobelShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_EdgeColor("Edge Color",color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv[9] : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _MainTex_TexelSize;
float4 _EdgeColor; /*边缘颜色值*/
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
float2 uv = TRANSFORM_TEX(v.uv, _MainTex);
//从上到下从左到右依次计算卷积核对应的9个像素值
o.uv[0] = uv + _MainTex_TexelSize.xy*float2(-1, 1);
o.uv[1] = uv + _MainTex_TexelSize.xy*float2(0, 1);
o.uv[2] = uv + _MainTex_TexelSize.xy*float2(1, 1);
o.uv[3] = uv + _MainTex_TexelSize.xy*float2(-1, 0);
o.uv[4] = uv;
o.uv[5] = uv + _MainTex_TexelSize.xy*float2(1, 0);
o.uv[6] = uv + _MainTex_TexelSize.xy*float2(-1, -1);
o.uv[7] = uv + _MainTex_TexelSize.xy*float2(0, -1);
o.uv[8] = uv + _MainTex_TexelSize.xy*float2(1, -1);
return o;
}
//rgb提取明亮度
//rgb转yuv公式
float RgbToY(fixed4 col)
{
return 0.299 * col.r + 0.587 * col.g + 0.114 * col.b;
}
fixed4 frag (v2f i) : SV_Target
{
float xSobel[9] = { -1,0,1,-2,0,2,-1,0,1 }; /*x轴sobel算子*/
float ySobel[9] = { -1,-2,-1,0,0,0,1,2,1 }; /*y轴sobel算子*/
//xy轴梯度和结合梯度
float G = 0, Gx = 0, Gy = 0;
for (int k = 0; k < 9; k++)
{
float y = RgbToY(tex2D(_MainTex, i.uv[k]));
Gx += y * xSobel[k];
Gy += y * ySobel[k];
}
//计算结合梯度
G = sqrt(Gx*Gx + Gy*Gy);
//主纹理采样
fixed4 col = tex2D(_MainTex, i.uv[4]);
//梯度混合
col = lerp(col, _EdgeColor, G);
return col;
}
ENDCG
}
}
}
顺便看下效果,如下:
好了,之后会找一些其他的卷积滤波器来实现一些效果。