|
楼主 |
发表于 2022-12-19 15:35:59
|
显示全部楼层
本帖最后由 dasasdhba 于 2022-12-19 19:53 编辑
[21] 模糊(Blur)算法简介
1. 方框模糊(Box Blur)
这是非常简单的一种模糊办法,具体来说是求像素点的临近点的平均值,为此我们使用这样的卷积核:
设 E(n) 是 n×n 的元素全为 1 的矩阵,我们采用 (1/n^2)*E(n) 作为卷积核。举例(n = 3):
对 RGB 值同时作卷积即可。
2. 高斯模糊(Gaussian Blur)
事实上,方框模糊简单得有点过分了,甚至根本不需要什么“卷积”也能讲清楚。
但我使用卷积的语言并非故弄玄虚,而是因为高斯模糊从卷积的角度来看跟方框模糊有着重要的联系:
与方框模糊不同,高斯模糊使用了正态分布(也称高斯分布,故称高斯模糊)的办法。
回忆高中学习的正态分布方程:
不考虑偏移,我们将 μ 取 0,而 σ 即为“模糊半径”,下面我们将其改写为二维的情形:
但是这样不便于计算,在具体实现上,我们一般直接从一维情形出发,下面我们构造一个 5×5 的高斯模糊卷积核:
- 首先在正态分布曲线上取五个点,为此我们使用上图的 f(x),方便起见,取 μ = 0,由此得到行向量:
α = (f(-2), f(-1), f(0), f(1), f(2))
(注:因为取 μ = 0,实际上已经有 f(x) = f(-x),因此不必算两次) - 为了得到卷积核,只需要作矩阵乘法:G = α^T * α,容易验证这恰好符合二维情形的结果。
(注:直接使用二维的方法亦可,也就是:G =
G(-2,-2), G(-1,-2), G(0,-2), G(1, -2), G(2, -2)
G(-2,-1), G(-1,-1), G(0,-1), G(1, -1), G(2, -1)
...) - 最后,对 G 作归一化(否则会有亮度改变),为此只需要除以 G 的各个元素的加和。
不妨设 G = (g(i,j)),我们的卷积核就可以表示为:(1/(∑g(i,j))) * G
在实际应用中,注意到 G = α^T * α,我们完全可以分别在两个一维的方向上作卷积,这样效率更高。
具体来说,记我们需要处理的点为 P(0, 0),首先分别将 P(-2,0), P(-1, 0), P(0, 0), P(1, 0), P(2, 0) 与 α^T 作卷积,由此得到行向量:
β = (P'(-2,0), P'(-1, 0), P'(0, 0), P'(1, 0), P'(2, 0)),其中 P'(i, 0) 为 P(i, 0) 及其临近像素点与 α^T 作卷积的结果。
再将 β 与 α 作卷积即可。
(最后别忘了归一化)
因此,高斯模糊跟方框模糊的唯一不同之处,仅仅在于使用了正态分布的办法进行了“加权平均”,本质上还是取平均数。
3. 径向模糊(Radial Blur)
径向模糊非常简单,我们只需要指定一个方向向量,然后采这个方向上的样并求均值即可。
#define ITERATION 8
vec4 radial_blur(vec2 uv, vec2 dir) {
vec4 s = vec4(0.0);
for (int i=0; i<ITERATION; i++) {
s += texture(tex, uv + i * unit * dir);
}
return s / ITERATION;
}
4. 方向模糊(Direction Blur)
双向的径向模糊罢了:
#define ITERATION 8
vec4 direction_blur(vec2 uv, vec2 dir) {
vec4 s = vec4(0.0);
for (int i=-ITERATION; i<ITERATION; i++) {
s += texture(tex, uv + i * unit * dir);
}
return s / (2.0 * ITERATION);
}
5. Kawase 模糊(Kawase Blur)
受径向模糊以及方向模糊的启发,我们可以同时采用多个方向,由此又得到一种相对均衡的模糊效果。
这是 CTF 做不到的一种办法,因为需要反复迭代 Shader,我们只作简单介绍,Kawase 模糊关键是使用了:
vec4 kawase_blur(vec2 uv, vec2 offset) {
return 0.25* (texture(tex, uv + unit * vec2(offset + 0.5, offset + 0.5))
+ texture(tex, uv + unit * vec2(-offset - 0.5, offset + 0.5))
+ texture(tex, uv + unit * vec2(-offset - 0.5, -offset - 0.5))
+ texture(tex, uv + unit * vec2(offset + 0.5, -offset - 0.5))
);
}
不妨取 offset 为 (0, 0),可见这将会对像素点的临近四个角求平均值。
而 Kawase 模糊的关键在于反复迭代这种办法(一般四次以上),并以此将 offset 设为 迭代次数 * (1, 1),下面这两张图很好地展现了原理:
注 1:采用 Shader 迭代是因为这样的效果出奇地接近高斯模糊,且速度要快很多。
感兴趣可以试试在 Shader 内直接迭代的效果,这会得到类似方向模糊的效果。
注 2:在 2015 年,一个更快的 Kawase 模糊办法被提出:Dual Kawase Blur,简称 Dual Blur。
这种办法对于较大的 Texture 非常快,其实是使用了一个非常简单的思想:反正都模糊了,我为什么不缩小之后再模糊然后放大回来??
(当然,技术细节没那么简单,缩小和放大操作需要 Vertex Shader 协助,CTF 就更不用想了)
6. 散景模糊(Bokeh Blur)
这好像是摄影里面会出现的一种模糊现象,反正就是会出现一些光圈之类的东西,原理我不懂也不想懂。
为了模拟圆形光圈,可以借用黄金角 φ = 2π/((1+√5)/2)^2,大约为 137.51°,通过反复旋转进行采样。
在 Wikipedia 上有一个很直观的动图演示了这个过程:
下面是代码实现:
const mat2 rot = mat2(cos(φ), sin(φ), -sin(φ), cos(φ)) // 伪代码,黄金角 φ 的旋转矩阵
#define ITERATION 8
vec4 bokeh_blur(vec2 uv) {
float r = 1.0;
vec2 angle = vec2(1.0, 0.0);
vec4 a = vec4(0.0);
vec4 d = vec4(0.0);
for (int i=0; i<ITERATION; i++) {
r += 1.0 / r; // 半径向外沿伸
angle = rot*angle; // 旋转
vec4 bokeh = texture(tex, uv + unit * (r - 1.0) * angle);
a += bokeh * bokeh;
d += bokeh;
}
return a / d;
}
7. 粒状模糊(Grainy Blur)
这个算是我们之前学习的 Noise 的应用,也就是求特定像素点周围的若干个由二维 Noise 扰动得到的像素点的均值。
如果我们采用的是 Value Noise,就会产生颗粒感,模糊半径越大,颗粒感也会越明显。
当模糊半径足够大的时候,不难想象,这会非常接近我们之前得到的二维 Noise 对应的视觉效果。
(想想二维 Value Noise 的直观效果,也就是“雪花屏”)
如果使用 Simplex Noise 或者 Cellular Noise,效果会如何呢?感兴趣可以自己试试。
8. 镜头模糊(Lens Blur)与光圈模糊(Iris Blur)
这个也算是一个综合应用,本质上就是一部分模糊一部分不模糊的效果。
为此我们只需要利用我们之前所学的办法构造遮罩,比如使用 smoothstep 构造一个 白-黑-白 渐变(这对应镜头模糊),
或者构造一个椭圆形的径向渐变(这对应光圈模糊),然后再采用我们上面提到的各种模糊办法即可。
9. 马赛克模糊
这个太简单了,而且跟上面提到的内容也格格不入,所以单独放在这里了。
具体来说,将 Texture 用网格分割然后每个网格取一个固定的颜色就行了。 |
|