📝 前言

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

🎯 效果展示

在这里插入图片描述

该 Shader 实现的核心视觉效果包括:

  1. 动态缩放模糊:从图像中心向外辐射的模糊效果
  2. 双图像转场:在两张图像之间平滑过渡
  3. 自适应强度:模糊强度随时间动态变化
  4. 随机化采样:消除固定采样带来的带状伪影

🔧 核心原理

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(2t1)
  • t ≥ 0.5:指数衰减 2 − 2 − 10 ( 2 t − 1 ) 2 - 2^{-10(2t-1)} 2210(2t1)

应用场景:控制两张图像之间的溶解(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×(1cos(π×durationt))

曲线特点

  • t=0t=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);
}

原理:基于片段坐标的伪随机数生成器。

实现细节

  1. 使用点积 dot(gl_FragCoord.xyz + seed, scale) 将 3D 坐标映射到标量
  2. 通过 sin() 函数将值映射到 [-1, 1]
  3. 乘以大质数 43758.5453 放大并打散分布
  4. 使用 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=(1dissolve)×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 的精髓所在!

采样策略
  1. 采样次数:21 次(t = 0 到 20)

  2. 采样位置

    采样点 = 当前像素 + 方向向量 × 百分比 × 强度
    
    • percent:采样进度 [0, 1],加入随机偏移
    • toCenter * percent * strength:沿着指向中心的方向采样
    • percent = 0 时,采样当前像素
    • percent = 1 时,采样最接近中心的点
  3. 权重函数

    weight = percent - percent²
    

    数学分析

    • 这是一个抛物线函数: f ( x ) = x − x 2 f(x) = x - x^2 f(x)=xx2
    • 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)dt01w(t)Sample(p+td )dt

其中:

  • p p p:当前像素位置
  • d ⃗ \vec{d} d :方向向量(toCenter × strength)
  • w ( t ) = t − t 2 w(t) = t - t^2 w(t)=tt2:权重函数
  • 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)=tt2 平滑过渡(本 Shader)
高斯 w ( t ) = e − k ( t − 0.5 ) 2 w(t) = e^{-k(t-0.5)^2} w(t)=ek(t0.5)2 最平滑,计算量大

🎓 总结

这个 Zoom Blur Shader 通过以下技术要点实现了高质量的视觉效果:

  1. 多种缓动函数:精确控制动画节奏
  2. 径向采样:创造缩放感和方向性模糊
  3. 加权累积:实现平滑的模糊过渡
  4. 随机化采样:消除带状伪影
  5. 双图转场:无缝衔接两个场景

该技术广泛应用于:

  • 🎬 视频转场特效
  • 🎮 游戏场景切换
  • 📱 UI 动画效果
  • 🎨 艺术滤镜

💡 实践建议

  1. 调试技巧

    • 先关闭随机化(offset = 0),观察采样模式
    • 减少采样次数到 5 次,快速迭代参数
    • 单独测试每个缓动函数的效果
  2. 创意方向

    • 结合粒子系统,增强动感
    • 添加色彩分离,模拟镜头畸变
    • 使用噪声纹理调制强度,创造不规则效果
  3. 性能监控

    • 使用 GPU 分析工具(如 RenderDoc)
    • 监控纹理采样次数
    • 在移动设备上测试帧率

📖 参考资源


💬 如果本文对您有帮助,欢迎点赞、收藏、关注!有任何问题欢迎在评论区讨论~

Logo

网易智企-云信开发者社区是面向全网开发者的技术交流与服务平台,依托近 29 年 IM、音视频技术积累,提供 IM、RTC、实时对话智能体、云原生、短信等全场景开发资源。

更多推荐