模糊特效:高斯模糊原理、BoxBlur实现、径向模糊、缩放模糊
模糊特效,在图形学里是个老话题了。但你别小看它,很多酷炫的后期效果——辉光、景深、运动模糊——底层都离不开模糊算法。今天我们就来把这几种模糊方式彻底讲透。
高斯模糊的原理
高斯模糊,说白了就是用高斯分布对像素做加权平均。每个像素的新值,由它周围像素按权重混合得到。权重分布呈钟形曲线,离中心越近贡献越大。
数学公式长这样:
G(x, y) = (1 / (2πσ²)) * e^(-(x² + y²) / (2σ²))
σ(标准差)控制模糊程度。σ越大,曲线越平缓,周围像素权重越高,模糊越强。
我刚开始做这个的时候,直接对每个像素遍历整个卷积核,结果画面卡成PPT。后来才意识到——高斯模糊是可分离的。什么意思?你可以先对水平方向做一维模糊,再对垂直方向做一维模糊。计算量从 O(n²) 降到 O(2n),性能直接翻倍。
核心要点:高斯模糊 = 两次一维卷积。先水平,再垂直。这是所有高性能模糊实现的基础。
BoxBlur 实现
BoxBlur 是高斯模糊的「穷亲戚」。它不做加权平均,而是取周围像素的简单平均值。效果嘛,没那么平滑,但胜在速度快。
而且有个有意思的事——多次叠加 BoxBlur 可以逼近高斯模糊。我记得有篇经典论文提到,3次 BoxBlur 的效果就跟一次高斯模糊非常接近了。
来看个简单的 BoxBlur 实现:
// 一维 BoxBlur
function boxBlur1D(pixels, width, height, radius) {
const temp = new Float32Array(pixels.length);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let sum = 0;
let count = 0;
for (let dx = -radius; dx <= radius; dx++) {
const nx = x + dx;
if (nx >= 0 && nx < width) {
sum += pixels[y * width + nx];
count++;
}
}
temp[y * width + x] = sum / count;
}
}
return temp;
}
你想想看,这个实现其实有个优化空间——滑动窗口。每次移动一个像素,窗口内的数据变化其实很小。用前缀和或者双端队列,能把复杂度降到 O(n)。
我的经验:在实际项目中,如果模糊半径超过 10 像素,千万别用暴力遍历。用滑动窗口优化,性能差距能有几十倍。
径向模糊
径向模糊跟前面两种不太一样。它不是沿水平/垂直方向模糊,而是沿「从中心向外辐射」的方向模糊。效果就像相机快速旋转或缩放时拍出的照片。
实现思路很简单:
- 确定模糊中心点(通常是画面中心)
- 对每个像素,计算它到中心的方向向量
- 沿这个方向采样多个点,取平均
// 径向模糊核心代码
vec4 radialBlur(sampler2D tex, vec2 uv, vec2 center, float strength) {
vec2 dir = uv - center;
float len = length(dir);
vec2 normDir = normalize(dir);
vec4 color = vec4(0.0);
float totalWeight = 0.0;
// 沿径向采样
for (int i = -SAMPLE_COUNT; i <= SAMPLE_COUNT; i++) {
float t = float(i) / float(SAMPLE_COUNT) * strength;
vec2 sampleUV = uv + normDir * t;
float weight = 1.0 - abs(t) / strength;
color += texture2D(tex, sampleUV) * weight;
totalWeight += weight;
}
return color / totalWeight;
}
我曾经在做一个赛车游戏的时候用过径向模糊。当时想模拟高速行驶时的速度感,结果发现采样点太少会出现条纹,采样点太多又跑不动。最后折中了一下,用了 16 个采样点,配合抖动采样,效果和性能都满意了。
注意:径向模糊的采样方向是发散的,边缘像素的采样点可能会跑到画面外。记得做边界截断或镜像处理,不然会出现奇怪的黑色条纹。
缩放模糊
缩放模糊(Zoom Blur)跟径向模糊很像,但方向是「沿视线方向」——从中心向外或从外向中心。效果就像镜头突然变焦。
实现上,它其实就是径向模糊的一个特例:采样方向全部指向中心或背离中心。代码几乎一样,只是采样方向的计算方式不同。
我个人习惯把缩放模糊和径向模糊合并成一个函数,通过参数控制方向模式。这样代码复用率高,维护也方便。
| 模糊类型 | 采样方向 | 典型应用 | 性能开销 |
|---|---|---|---|
| 高斯模糊 | 水平+垂直 | 通用模糊、辉光预处理 | 中等 |
| BoxBlur | 水平+垂直 | 快速模糊、阴影软化 | 低 |
| 径向模糊 | 从中心向外辐射 | 速度感、旋转特效 | 较高 |
| 缩放模糊 | 沿视线方向 | 变焦效果、冲击感 | 较高 |
实际项目中的选择建议
嗯,这里要注意一点——别一上来就上高斯模糊。很多时候 BoxBlur 就够了。我在一个移动端项目里,所有模糊效果都用 BoxBlur 叠加 3 次实现,用户根本看不出区别,但帧率从 30 提到了 60。
径向模糊和缩放模糊属于「特效型」模糊,用的时候要克制。用得太多,画面会显得很「脏」。我建议只在关键帧或过渡动画中使用,持续时间控制在 0.3 秒以内。
最后说一句:模糊效果的参数(半径、采样数、强度)最好暴露给美术或策划调。你永远不知道他们想要多糊。留个参数面板,能省你不少改代码的时间。