Shader3_UI滤波扩展以及铅笔画效果

Pencil 铅笔画效果

最终效果就是这个样子啦,也是使用了上节课提到滤波,因为使用滤波可以让边界变得突出或者模糊,上节课是使用滤波让边界变得模糊,这节课就是使用滤波让边界突出,然后再进行一些颜色处理就可以了。

首先,明确一下铅笔画的数学含义,就是把将颜色连续的地方填充为白色,颜色不连续的地方即边界改为黑色,所以我们首先需要使用滤波让边界突出,就是让边界的颜色值变得更大,其余部分更小或者反过来,使用的滤波矩阵是这样的:

1
2
3
4
5
6
float3x3 pencilFilter = 
{
-0.5, -1.0, 0.0,
-1.0, 0.0, 1.0,
0.0, 1.0, 0.5
};

让我们来分析一下这个矩阵,通过上节课我们知道对于坐标(x,y)来说,分别取自己和其余八个方位的颜色值与矩阵对应值的乘积做加法,那么就相当于是把左上角三个方位的颜色值和右下角的颜色值做了减法,可以想象,如果是连续的颜色的话做了减法基本上就为0了,因为左上颜色和右下颜色基本是一致的,而边界的话是不同的,得到值就不会是0,数值就分别开了。由于颜色值不能为0,这里我们对滤波方法进行扩充,最后的返回值加个 abs() 取绝对值方法再返回。

让我们看一下只做了滤波后的效果:

可以发现,颜色的确突出了,而且好像这个效果还挺不错。只是与模糊效果不同,我们希望他不要扩散到本来图片透明的地方,模糊效果倒是应该扩散到原本透明地方,毕竟重影嘛。为了实现不扩散到透明地方的效果,让我们再回顾一下之前的滤波方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 3x3 滤波
float4 filterWithA(float3x3 filter,float _BlurOffset, sampler2D tex, float2 coord)
{
float4 outCol = float4(0,0,0,0);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//计算采样点,得到当前像素附近的像素的坐标
float2 newUV= float2(coord.x + (i-1)*_BlurOffset, coord.y + (j-1)*_BlurOffset);
//采样并乘以滤波器权重,然后累加
outCol += tex2D(tex, newUV) * filter[i][j];
}
}
//这里改了下位置。。之前不太对,虽然之前也是注释掉了
//是否处理透明度
//outCol.a = tex2D(tex, coord).a;
return abs(outCol);
}

可以发现,之前这个滤波方法在进行颜色混合师把透明度也按照比例进行了混合,我把方法重命名了,加了个 WithA 的后缀,即对透明度也进行滤波,我们把下面注释掉的那句代码改回来,意思是透明度维持原来图片的值不变。outCol.a = tex2D(tex, coord).a;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
float4 filterWithoutA(float3x3 filter, float _BlurOffset, sampler2D tex, float2 coord)
{
float4 outCol = float4(0, 0, 0, 0);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
//计算采样点,得到当前像素附近的像素的坐标
float2 newUV = float2(coord.x + (i - 1)*_BlurOffset, coord.y + (j - 1)*_BlurOffset);
//采样并乘以滤波器权重,然后累加
outCol += tex2D(tex, newUV) * filter[i][j];
}
}
//是否处理透明度
outCol.a = tex2D(tex, coord).a;
return abs(outCol);
}

让我们使用这个方法来看下效果

达到效果啦!

然后我们需要做的是什么呢,把图片中黑色的部分(原先图片颜色连续的部分)改为白色,把有颜色的部分改为铅笔的颜色,这里黑色的部分改为白色很简单,黑色的RGB值是(0,0,0),而白色是(1,1,1),在 Shader 中都进行了归一化,只要用1-去颜色值就好了,1-0=1。

而把有颜色的部分改为铅笔的感觉就用到了灰度图的原理,使用公式将颜色转为灰色

具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fixed4 ProcessColor(v2f IN)
{
float3x3 pencilFilter =
{
-0.5, -1.0, 0.0,
-1.0, 0.0, 1.0,
0.0, 1.0, 0.5
};

float4 filterCol = filterWithoutA(pencilFilter,_BlurOffset, _MainTex, IN.texcoord);
float gray = 0.3 * filterCol.x + 0.59 * filterCol.y + 0.11 * filterCol.z;
gray = 1.0 - gray;
return float4(gray, gray, gray, filterCol.a);
}

依次进行了滤波、灰度图处理、1-操作

  • 滤波的效果是突出边界,把连续颜色值归为0。
  • 灰度图处理的效果是把边界的颜色改为铅笔的效果,而黑色的地方是(0,0,0),即使进行了灰度计算也还是(0,0,0)。
  • 1- 操作就是将黑色部分变为了白色,铅笔画效果基本不变,或者说变成了1-,翻转的效果23333。

另外最后返回的值中透明度使用的是 filterCol.a,即原图的透明度分布。对于导入的图片没有透明度通道的话,这个算法就会出现问题了,但是游戏开发中的图片一般都是具有透明度通道的,所以这个基本上够用了,如果没有透明度通道的话,所有的透明度都应该是1,在最后返回的值把 filter.a 改为 1 即可。

滤波的其他效果

通过学习了这两个例子我们可以发现滤波和颜色边界值是息息相关的,使用这个方法我们还可以实现很多有关边界的效果,这里提一个锐化,另外这个效果只需要改动滤波矩阵就可以实现了,大家可以尝试一下,锐化的效果是 边界两侧的颜色值相差越大边界的颜色就越突出,其他效果大家就多多探索吧!

计算机图形学第一定律:如果它看起来是对的,那么它就是对的。

0%