Zoom Blur Shader 深度解析:打造电影级转场特效
📝 前言
在图形渲染和视频特效领域,Zoom Blur(缩放模糊)是一种极具视觉冲击力的效果,常用于场景转场、时间减速等场景。本文将深入解析一个基于 GLSL 的 Zoom Blur Shader 实现,该 Shader 改编自 GLSL Transitions,通过巧妙的数学插值和采样技术,实现了平滑自然的缩放模糊转场效果。
🎯 效果展示

该 Shader 实现的核心视觉效果包括:
- 动态缩放模糊:从图像中心向外辐射的模糊效果
- 双图像转场:在两张图像之间平滑过渡
- 自适应强度:模糊强度随时间动态变化
- 随机化采样:消除固定采样带来的带状伪影
🔧 核心原理
1. 缓动函数(Easing Functions)
Shader 中使用了三种缓动函数来控制动画的时间曲线,这是实现平滑动画的关键:
1.1 线性缓动(Linear Ease)
float Linear_ease(in float begin, in float change, in float duration, in float time) {
return change * time / duration + begin;
}
原理:最简单的插值方式,值随时间线性变化。
数学表达式:
f ( t ) = b e g i n + c h a n g e × t d u r a t i o n f(t) = begin + change \times \frac{t}{duration} f(t)=begin+change×durationt
应用场景:用于控制缩放中心点的水平移动,实现匀速平移效果。
1.2 指数缓动(Exponential Ease In-Out)
float Exponential_easeInOut(in float begin, in float change, in float duration, in float time) {
if (time == 0.0)
return begin;
else if (time == duration)
return begin + change;
time = time / (duration / 2.0);
if (time < 1.0)
return change / 2.0 * pow(2.0, 10.0 * (time - 1.0)) + begin;
return change / 2.0 * (-pow(2.0, -10.0 * (time - 1.0)) + 2.0) + begin;
}
原理:前半段加速(ease in),后半段减速(ease out),形成"慢-快-慢"的节奏。
曲线特点:
t < 0.5:指数增长 2 10 ( 2 t − 1 ) 2^{10(2t-1)} 210(2t−1)t ≥ 0.5:指数衰减 2 − 2 − 10 ( 2 t − 1 ) 2 - 2^{-10(2t-1)} 2−2−10(2t−1)
应用场景:控制两张图像之间的溶解(dissolve)过渡,让转场更加自然。
1.3 正弦缓动(Sinusoidal Ease In-Out)
float Sinusoidal_easeInOut(in float begin, in float change, in float duration, in float time) {
return -change / 2.0 * (cos(PI * time / duration) - 1.0) + begin;
}
原理:使用余弦函数的半周期实现平滑的加速和减速。
数学表达式:
f ( t ) = b e g i n + c h a n g e 2 × ( 1 − cos ( π × t d u r a t i o n ) ) f(t) = begin + \frac{change}{2} \times (1 - \cos(\pi \times \frac{t}{duration})) f(t)=begin+2change×(1−cos(π×durationt))
曲线特点:
- 在
t=0和t=duration处导数为 0,实现完美的平滑起止 - 创建对称的"镜像循环":0 → strength → 0
应用场景:控制模糊强度,实现"淡入-增强-淡出"的效果。
2. 随机数生成
float random(in vec3 scale, in float seed) {
return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
}
原理:基于片段坐标的伪随机数生成器。
实现细节:
- 使用点积
dot(gl_FragCoord.xyz + seed, scale)将 3D 坐标映射到标量 - 通过
sin()函数将值映射到 [-1, 1] - 乘以大质数
43758.5453放大并打散分布 - 使用
fract()提取小数部分,得到 [0, 1] 范围的随机值
作用:为每个像素生成独特的采样偏移,消除固定采样模式带来的带状伪影(banding artifacts)。
3. 交叉淡化(Cross Fade)
vec3 crossFade(in vec2 uv, in float dissolve) {
return mix(texture(iChannel0, uv).rgb, texture(iChannel1, uv).rgb, dissolve);
}
原理:在两张纹理之间进行线性插值混合。
数学表达式:
r e s u l t = ( 1 − d i s s o l v e ) × t e x t u r e 0 + d i s s o l v e × t e x t u r e 1 result = (1 - dissolve) \times texture_0 + dissolve \times texture_1 result=(1−dissolve)×texture0+dissolve×texture1
参数说明:
dissolve = 0.0:完全显示第一张图像dissolve = 0.5:两张图像各占 50%dissolve = 1.0:完全显示第二张图像
🎬 主函数流程详解
第一步:初始化参数
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 texCoord = fragCoord.xy / iResolution.xy;
float progress = sin(iTime*0.5) * 0.5 + 0.5;
关键变量:
texCoord:归一化的纹理坐标 [0, 1]progress:动画进度,通过正弦函数实现循环动画sin(iTime*0.5)产生 [-1, 1] 的振荡- 映射到 [0, 1] 范围,实现 0→1→0 的循环
第二步:计算动态参数
vec2 center = vec2(Linear_ease(0.5, 0.0, 1.0, progress), 0.5);
float dissolve = Exponential_easeInOut(0.0, 1.0, 1.0, progress);
float strength = Sinusoidal_easeInOut(0.0, strength, 0.5, progress);
参数解析:
| 参数 | 缓动函数 | 起始值 | 变化量 | 作用 |
|---|---|---|---|---|
center.x |
Linear | 0.5 | 0.0 | 缩放中心(本例中保持在中央) |
center.y |
- | 0.5 | - | 垂直居中 |
dissolve |
Exponential | 0.0 | 1.0 | 图像溶解度(0→1) |
strength |
Sinusoidal | 0.0 | 0.3 | 模糊强度(0→0.3→0) |
设计巧思:
center保持在屏幕中央(0.5, 0.5),可修改为动态移动dissolve使用指数缓动,让图像切换更有"冲击感"strength使用正弦缓动,在动画中段达到最大模糊,首尾清晰
第三步:计算方向向量
vec3 color = vec3(0.0);
float total = 0.0;
vec2 toCenter = center - texCoord;
几何意义:
toCenter:从当前像素指向缩放中心的向量- 向量长度代表像素到中心的距离
- 向量方向决定模糊的辐射方向
第四步:随机化采样偏移
float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0) * 0.5;
作用:
- 为每个像素生成 [0, 0.5] 范围的随机偏移
- 打破固定采样模式,避免出现明显的同心圆带状伪影
- 增加视觉上的"噪点感",反而让模糊更自然
第五步:多重采样与加权累积
for (float t = 0.0; t <= 20.0; t++) {
float percent = (t + offset) / 20.0;
float weight = 1.0 * (percent - percent * percent);
color += crossFade(texCoord + toCenter * percent * strength, dissolve) * weight;
total += weight;
}
核心算法:这是整个 Shader 的精髓所在!
采样策略
-
采样次数:21 次(t = 0 到 20)
-
采样位置:
采样点 = 当前像素 + 方向向量 × 百分比 × 强度percent:采样进度 [0, 1],加入随机偏移toCenter * percent * strength:沿着指向中心的方向采样- 当
percent = 0时,采样当前像素 - 当
percent = 1时,采样最接近中心的点
-
权重函数:
weight = percent - percent²数学分析:
- 这是一个抛物线函数: f ( x ) = x − x 2 f(x) = x - x^2 f(x)=x−x2
- 在 x = 0.5 x = 0.5 x=0.5 时达到最大值 0.25 0.25 0.25
- 在 x = 0 x = 0 x=0 和 x = 1 x = 1 x=1 时值为 0 0 0
视觉效果:
- 中间采样点权重更高,边缘采样点权重较低
- 形成高斯模糊般的平滑过渡
- 避免了简单平均带来的"硬边缘"
可视化权重分布
权重分布图(21 个采样点):
0.25 | ╱‾‾‾╲
| ╱ ╲
0.15 | ╱ ╲
| ╱ ╲
0.05 | ╱ ╲
|╱ ╲
0 +---------------+
0 0.5 1
percent
第六步:归一化输出
fragColor = vec4(color / total, 1.0);
}
归一化原因:
- 由于使用了加权采样,总权重
total不等于 1 - 除以
total确保亮度守恒,避免过曝或过暗 - Alpha 通道固定为 1.0(完全不透明)
📊 参数调优指南
全局参数
| 参数 | 默认值 | 推荐范围 | 效果 |
|---|---|---|---|
strength |
0.3 | 0.1 - 0.8 | 模糊强度,值越大模糊越明显 |
| 采样次数 | 21 | 10 - 40 | 质量与性能的平衡 |
offset 系数 |
0.5 | 0.3 - 1.0 | 随机化程度 |
动画速度
float progress = sin(iTime * 0.5) * 0.5 + 0.5;
修改 0.5 可调整动画速度:
0.25:慢速循环(4秒周期)0.5:默认速度(2秒周期)1.0:快速循环(1秒周期)
缩放中心
vec2 center = vec2(0.5, 0.5); // 固定中心
// 或者
vec2 center = vec2(Linear_ease(0.25, 0.5, 1.0, progress), 0.5); // 动态移动
可实现:
- 固定中心缩放
- 从左到右移动
- 圆周运动(使用 sin/cos)
🎨 视觉效果分析
1. 径向模糊(Radial Blur)
由于采样沿 toCenter 方向进行,形成了从中心向外辐射的模糊轨迹:
↖ ↑ ↗
╲ │ ╱
←───●───→ (中心点)
╱ │ ╲
↙ ↓ ↘
每个像素的模糊方向都指向中心,创造出"缩放感"。
2. 景深效果(Depth of Field)
通过 strength 的动态变化,模拟了相机焦距变化:
strength = 0:完全清晰(焦点准确)strength = 0.3:最大模糊(焦点移动中)strength = 0:再次清晰(焦点重新对准)
3. 时间扭曲感
配合图像转场,创造出"时空穿梭"的视觉效果:
- 前半段:第一张图像模糊并缩放
- 中段:两张图像混合,模糊最强
- 后半段:第二张图像逐渐清晰
🚀 性能优化建议
1. 采样次数优化
// 低端设备
for (float t = 0.0; t <= 10.0; t++) // 11 次采样
// 中端设备
for (float t = 0.0; t <= 20.0; t++) // 21 次采样(默认)
// 高端设备
for (float t = 0.0; t <= 40.0; t++) // 41 次采样
2. 动态采样
根据模糊强度调整采样次数:
float maxSamples = mix(5.0, 20.0, strength / 0.3);
for (float t = 0.0; t <= maxSamples; t++) {
// ...
}
3. 纹理采样优化
使用 textureLod 指定 mipmap 级别,减少带宽:
vec3 crossFade(in vec2 uv, in float dissolve) {
float lod = strength * 3.0; // 根据模糊强度选择 mipmap
return mix(
textureLod(iChannel0, uv, lod).rgb,
textureLod(iChannel1, uv, lod).rgb,
dissolve
);
}
🔬 扩展应用
1. 单图模糊
去掉转场逻辑,只保留模糊效果:
vec3 color = vec3(0.0);
float total = 0.0;
for (float t = 0.0; t <= 20.0; t++) {
float percent = (t + offset) / 20.0;
float weight = percent - percent * percent;
color += texture(iChannel0, texCoord + toCenter * percent * strength).rgb * weight;
total += weight;
}
fragColor = vec4(color / total, 1.0);
2. 多中心模糊
创建多个缩放中心:
vec2 centers[3] = vec2[](
vec2(0.25, 0.5),
vec2(0.5, 0.5),
vec2(0.75, 0.5)
);
for (int i = 0; i < 3; i++) {
vec2 toCenter = centers[i] - texCoord;
// ... 采样累积
}
3. 色散效果(Chromatic Aberration)
对 RGB 通道使用不同的模糊强度:
float strengthR = strength * 1.0;
float strengthG = strength * 0.95;
float strengthB = strength * 0.9;
// 分别采样 R、G、B 通道
📚 数学原理深入
加权平均的数学本质
该 Shader 实际上在计算加权积分的离散近似:
C o l o r ( p ) = ∫ 0 1 w ( t ) ⋅ S a m p l e ( p + t ⋅ d ⃗ ) d t ∫ 0 1 w ( t ) d t Color(p) = \frac{\int_0^1 w(t) \cdot Sample(p + t \cdot \vec{d}) \, dt}{\int_0^1 w(t) \, dt} Color(p)=∫01w(t)dt∫01w(t)⋅Sample(p+t⋅d)dt
其中:
- p p p:当前像素位置
- d ⃗ \vec{d} d:方向向量(toCenter × strength)
- w ( t ) = t − t 2 w(t) = t - t^2 w(t)=t−t2:权重函数
- S a m p l e Sample Sample:纹理采样函数
权重函数的选择
不同的权重函数会产生不同的模糊效果:
| 权重函数 | 公式 | 效果 |
|---|---|---|
| 均匀 | w ( t ) = 1 w(t) = 1 w(t)=1 | 硬边缘,有带状伪影 |
| 线性 | w ( t ) = t w(t) = t w(t)=t | 偏向远端采样 |
| 抛物线 | w ( t ) = t − t 2 w(t) = t - t^2 w(t)=t−t2 | 平滑过渡(本 Shader) |
| 高斯 | w ( t ) = e − k ( t − 0.5 ) 2 w(t) = e^{-k(t-0.5)^2} w(t)=e−k(t−0.5)2 | 最平滑,计算量大 |
🎓 总结
这个 Zoom Blur Shader 通过以下技术要点实现了高质量的视觉效果:
- 多种缓动函数:精确控制动画节奏
- 径向采样:创造缩放感和方向性模糊
- 加权累积:实现平滑的模糊过渡
- 随机化采样:消除带状伪影
- 双图转场:无缝衔接两个场景
该技术广泛应用于:
- 🎬 视频转场特效
- 🎮 游戏场景切换
- 📱 UI 动画效果
- 🎨 艺术滤镜
💡 实践建议
-
调试技巧:
- 先关闭随机化(
offset = 0),观察采样模式 - 减少采样次数到 5 次,快速迭代参数
- 单独测试每个缓动函数的效果
- 先关闭随机化(
-
创意方向:
- 结合粒子系统,增强动感
- 添加色彩分离,模拟镜头畸变
- 使用噪声纹理调制强度,创造不规则效果
-
性能监控:
- 使用 GPU 分析工具(如 RenderDoc)
- 监控纹理采样次数
- 在移动设备上测试帧率
📖 参考资源
💬 如果本文对您有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区讨论~
更多推荐


所有评论(0)