Learn ComputeShader 08 Blurring an image
1.原理
假设要模糊一个像素,模糊半径为10,如下图所示

要实现整幅图像的模糊效果,则计算量将显著增加。

为了减少计算负担,在本研究中我们将其划分为两个阶段。首先采用水平方向的模糊处理,并在此基础上进行垂直方向的模糊处理。这种方法能够显著降低整体计算复杂度。
原始 模糊的计算量:
给定一个图像大小为 width x height ,我们通过使用一个 21x21 的卷积核对图像中的每一个像素进行处理以确保每个像素都被卷积操作所作用
每一个像素都需要计算其周围区域的所有21×21个像素,并最终确定它们各自的加权平均值。
由于图像有 width * height 个像素,因此整个图像的计算量就是:

优化后的计算量:
1. 水平模糊 :
在图像中每一行(具有高度 height 的每一行包含宽度 width 的像素),通过应用高斯权重计算当前像素及其左右邻域中的十个相邻像素的加权平均值。该操作仅在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.实战
这里主要学习核函数的编写思路
首先获取左侧第一个像素的颜色值,在运算过程中为了避免出现负数结果,在计算前进行了必要的约束;接着计算需要进行模糊处理的所有 pixels 数量;这里的代码可以根据下图来进行理解;随后从左到右依次处理每个 pixel 的颜色值;将所有需要参与运算的颜色值累加起来;最后将上述总和除以所处理 pixels 的数量

[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;
}
模糊效果:

但是我们希望人物周围的图像较为锐利而清晰,并非整体都保持明亮状态。首先将图像经过模糊处理后调低亮度以达到变暗的效果;随后依据像素点是否位于人物边缘区域对颜色进行插值处理。
[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;
}
效果:

概述:最基础的 fuzzy 现象即为这种类型;另外一种高斯型 fuzzy 效果更为出色,在其机制上主要通过距离计算赋予每个像素相应的权重系数,并非采用简单地将所有相邻像素取平均的方法
