1.原理
假设要模糊一个像素,模糊半径为10,如下图所示
模糊一个像素需要合并400个像素的颜色,如果要模糊整张图像那么计算量是非常大的。
为了简化计算量,我们将这个模糊分成两个阶段 。首先,水平模糊图像,然后使用这个图像垂直模糊图像,这个方法可以极大减少计算量
原始 模糊的计算量:
假设图像的尺寸为 width x height
,我们对每个像素都要应用这个 21x21 的卷积核。因此:
-
对于每个像素,都要处理它周围的 21x21 个像素,总共需要处理 441 个像素的加权平均。
-
由于图像有
width * height
个像素,因此整个图像的计算量就是:
优化后的计算量:
1. 水平模糊:
对于图像的每一行(height
行,每行 width
个像素),使用高斯权重对当前像素及其左右 10
个邻居像素进行加权平均。这一操作只沿着 x
方向进行。
- 对于每一个像素,处理
2 * 10 + 1 = 21
个像素。 - 总的计算量为:
width * height * 21
。
2. 垂直模糊:
水平模糊结束后,得到一个经过水平方向模糊的图像。然后我们在垂直方向上对每一列进行模糊(width
列,每列 height
个像素),这次的模糊只沿着 y
方向进行。
- 对于每一个像素,处理
2 * 10 + 1 = 21
个像素。 - 总的计算量为:
width * height * 21
。
总计算量 = width * height * 21 + width * height * 21 = width * height * 42
可以看到计算量减少了百分之90,而且优化后的模糊效果和原来的效果几乎没有什么差别。
2.实战
这里主要学习核函数的编写思路
首先得到左边第一个开始的像素颜色,为了避免是负数加上了限制,然后计算一共要模糊的像素的数量,这里的代码可以根据下面这张图来理解。然后从最左边的像素的颜色开始,计算所有要计算的颜色之和,最后除以计算的像素的数量。
[numthreads(8, 8, 1)]
void HorzPass(uint3 id : SV_DispatchThreadID)
{
int left = max(0, (int)(id.x - blurRadius));
int count = min(blurRadius, (int)id.x) + min(blurRadius, source.Length.x - (int)id.x);
float4 color = 0;
uint2 index = uint2((uint)left, id.y);
[unroll(100)]//限制最大循环次数
for(int x = 0; x < count; x++) {
color += source[index];
index.x++;
}
color /= (float)count;
horzOutput[id.xy] = color;
}
一样的思路,在另一个核函数中编写垂直方向的模糊。
从下往上,计算像素的颜色并求平均,最后输出模糊后的颜色
[numthreads(8, 8, 1)]
void Highlight(uint3 id : SV_DispatchThreadID)
{
int bottom = max(0, (int)(id.y - blurRadius));
int count = min(blurRadius, (int)id.y) + min(blurRadius, source.Length.y - (int)id.y);
float4 blurColor = 0;
uint2 index = uint2(id.x, (uint)bottom);
[unroll(100)]
for(int y = 0; y < count; y++) {
blurColor += horzOutput[index];
index.y++;
}
blurColor /= (float)count;
output[id.xy] = blurColor;
}
模糊效果:
但是我们想要人物周围是清晰的,其它地方是模糊且变暗的。先将模糊后的颜色乘上shade进行变暗,然后根据当前像素是否在人物周围来对颜色进行插值
[numthreads(8, 8, 1)]
void Highlight(uint3 id : SV_DispatchThreadID)
{
int bottom = max(0, (int)(id.y - blurRadius));
int count = min(blurRadius, (int)id.y) + min(blurRadius, source.Length.y - (int)id.y);
float4 blurColor = 0;
uint2 index = uint2(id.x, (uint)bottom);
[unroll(100)]
for(int y = 0; y < count; y++) {
blurColor += horzOutput[index];
index.y++;
}
blurColor /= (float)count;
float4 srcColor = source[id.xy];
float4 shadedBlurColor = blurColor * shade;
float highlight = inCircle((float2)id.xy, center.xy, radius, edgeWidth);
float4 color = lerp(shadedBlurColor, srcColor, highlight);
output[id.xy] = color;
}
效果:
总结:这种模糊是最简单的模糊。还有一种高斯模糊效果更好,它的做法是根据与模糊像素的距离来赋予每个像素一个权重,而不是像这次这样平均模糊。