Glitch(故障艺术)效果在网页动效、MV视觉、游戏后处理中非常流行,但很多实现做出来总是同一种味道:撕裂位移永远是固定像素数,RGB分离偏移量一成不变,闪烁节奏像钟表一样规律。这种效果看两遍就腻了,因为真实世界的信号故障本身就是不规则的、突发的、不可预测的。要让Glitch效果“活”起来,核心手段有两个:一是参数随机化,让每次触发的数值都不同;二是噪声注入,用连续噪声函数驱动效果,使其在时间轴上呈现自然的随机波动。本文围绕这两个方向详细展开。

为什么固定参数的Glitch看起来很假
先看一段典型的固定参数实现。这种写法在各种教程里非常常见:
uniform sampler2D uTexture;
varying vec2 vUv;
void main() {
vec2 uv = vUv;
// 固定位移:永远向右偏移0.02
uv.x += 0.02;
// 固定RGB分离:红蓝通道各偏移固定值
float r = texture2D(uTexture, uv + vec2(0.005, 0.0)).r;
float g = texture2D(uTexture, uv).g;
float b = texture2D(uTexture, uv - vec2(0.005, 0.0)).b;
gl_FragColor = vec4(r, g, b, 1.0);
}这段代码的问题非常明显:撕裂量、色差量都是常量,画面每一帧都完全一样,连“动”都算不上,更谈不上“故障感”。真实的信号损坏是由电磁干扰、数据传输错误引起的,发生时机随机、强度随机、位置随机。如果效果永远均匀分布在整张画面上,观感就会像贴了一层滤镜,而不是偶发的故障。
另一个常见误区是只用一个随机数控制全局。比如每帧生成一个0到1的随机值,大于0.8才触发Glitch,触发时所有效果共用这个随机强度。这样虽然有了节奏变化,但画面各区域的故障完全同步,缺乏空间上的差异性,看起来依然机械。理想的Glitch应该是:不同区域、不同通道、不同效果维度各自拥有独立的随机源,才能产生丰富的层次。
参数随机化:让每次故障都不一样
参数随机化的思路是把效果中的所有硬编码常量替换为受随机源驱动的变量。具体做法是维护一个或多个种子值,每隔随机时间更新种子,再由种子派生出各维度的参数。下面是一个基于JavaScript的调度逻辑示例:
class GlitchScheduler {
constructor() {
this.seed = 0;
this.nextTrigger = 0;
}
update(now) {
if (now >= this.nextTrigger) {
// 重新生成种子,派生各维度参数
this.seed = Math.random();
// 故障持续时间:40ms 到 200ms 之间随机
this.duration = 40 + Math.random() * 160;
// 触发下一次故障的间隔:200ms 到 1500ms 之间随机
this.nextTrigger = now + this.duration + 200 + Math.random() * 1300;
}
}
}这种“随机触发 + 随机时长 + 随机间隔”的结构是节奏层面的随机化,能让Glitch呈现突发感。注意持续时间不要太长,真实故障往往是瞬时的,超过300ms的持续撕裂会显得刻意。间隔也不要太规律,可以再叠加一个概率判断,让某些时刻出现“故障连发”,模拟信号持续恶化的感觉。
第二层是空间随机化:把画面水平切成若干条带,每条带独立决定是否发生位移以及位移量。着色器实现如下:
uniform sampler2D uTexture;
uniform float uTime;
uniform float uIntensity; // 由调度器传入的当前强度
float random(float x) {
return fract(sin(x * 127.1) * 43758.5453);
}
void main() {
vec2 uv = vUv;
// 以条带索引作为随机种子,条带索引随时间离散跳变
float band = floor(uv.y * 20.0);
float jump = floor(uTime * 15.0);
float bandRand = random(band + jump * 7.3);
// 只有约30%的条带发生撕裂,位移量随机
if (bandRand > 0.7) {
float offset = (random(band + jump * 3.1) - 0.5) * 0.15 * uIntensity;
uv.x += offset;
}
// RGB分离量也由条带随机值派生
float split = bandRand * 0.01 * uIntensity;
float r = texture2D(uTexture, uv + vec2(split, 0.0)).r;
float g = texture2D(uTexture, uv).g;
float b = texture2D(uTexture, uv - vec2(split, 0.0)).b;
gl_FragColor = vec4(r, g, b, 1.0);
}这里的关键点是离散跳变:用floor(uTime * 15.0)让随机种子每秒变化15次,而不是每帧连续变化。连续变化的随机数会让画面像静电噪点,而离散跳变则模拟出数字信号“这一帧坏了、下一帧好了”的突变特征。条带数量、跳变频率、触发阈值都可以作为暴露给设计师的调节参数,不同组合能产生从轻微毛刺到重度损坏的各种风格。
噪声注入:让故障具备连续的有机质感
纯随机数(白噪声)的问题是各帧之间毫无关联,适合模拟突变,但无法表现“渐强渐弱”的波动过程。如果想要Glitch有呼吸感——比如干扰逐渐加重再消退——就需要连续噪声函数。常用的有值噪声和Simplex噪声,后者在GLSL中有成熟的实现,可以直接内嵌到着色器中。
噪声注入的典型用法是用低频噪声作为强度包络,再叠加高频噪声制造细节抖动:
// 假设已定义 simplexNoise(vec2) 函数
void main() {
vec2 uv = vUv;
float t = uTime;
// 低频噪声:控制整体故障强度,变化缓慢
float envelope = simplexNoise(vec2(t * 0.3, 0.0));
// 高频噪声:制造瞬时的尖锐抖动
float jitter = simplexNoise(vec2(t * 12.0, uv.y * 3.0));
// 只有包络超过阈值时才进入故障状态
float glitchAmount = smoothstep(0.2, 0.6, envelope);
// 位移量 = 整体强度 × 局部抖动
uv.x += jitter * 0.06 * glitchAmount;
vec4 color = texture2D(uTexture, uv);
// 色差也乘上同一个包络
float split = 0.008 * glitchAmount * (0.5 + 0.5 * jitter);
color.r = texture2D(uTexture, uv + vec2(split, 0.0)).r;
color.b = texture2D(uTexture, uv - vec2(split, 0.0)).b;
gl_FragColor = color;
}这段代码中,envelope像一条缓慢起伏的波浪,决定“什么时候有故障、有多严重”,而jitter则在故障期间提供高频细节。两者相乘,效果就有了主次结构:大节奏由低频噪声掌控,小抖动由高频噪声补充。这种分层思路是程序化视觉效果的核心技巧,同样适用于屏幕抖动、色彩闪烁、扫描线扭曲等衍生效果。
还可以引入噪声域扰动进一步提升丰富度:用一个噪声的输出作为另一个噪声的输入坐标。例如simplexNoise(vec2(t + simplexNoise(vec2(t * 0.2, 5.0)) * 2.0, uv.y)),这样故障出现的时间点本身也不再规律,会呈现出类似信号受外界干扰的不可预测模式。代价是多一层噪声计算的GPU开销,在移动端需要权衡采样次数。
随机与噪声的组合策略与性能建议
实战中最好把两种手段组合使用:白噪声负责离散事件(条带撕裂、块损坏、通道交换),连续噪声负责强度调制和缓动过渡。一个推荐的参数结构如下:
| 参数 | 驱动源 | 建议范围 |
|---|---|---|
| 故障触发时机 | 随机间隔调度 | 200ms - 1500ms |
| 单次故障时长 | 随机时长 | 40ms - 200ms |
| 条带撕裂量 | 白噪声(离散跳变) | 0 - 0.15 |
| RGB分离量 | 条带随机值派生 | 0 - 0.01 |
| 整体强度包络 | 低频Simplex噪声 | smoothstep(0.2, 0.6) |
性能方面有三点建议。第一,噪声函数尽量复用,同一次片元着色中调用Simplex噪声的次数控制在3次以内,避免在低端设备上掉帧。第二,条带随机计算很便宜,一个fract(sin(...))哈希就够,可以放心提高条带密度,但条带超过50条后视觉差异就不明显了。第三,如果使用WebGL,可以把强度包络的计算放在CPU端每帧算一次再传入uniform,比在每个像素里计算噪声省得多。
最后提醒一点:随机不等于混乱。参数的取值范围依然需要人为约束,比如撕裂量不要超过画布宽度的15%,否则会破坏画面内容的可读性。随机化解决的是“每次不一样”,而好的取值范围才决定“不一样得好不好看”。先固定一套审美的基准参数,再在其上叠加随机扰动,是比全范围随机更可控、更容易出效果的做法。