到目前为止你画的一切都太「干净」了——完美的圆、精确的网格。而自然界的云、烟、山、木纹全是有规律的杂乱。制造这种杂乱的工具叫噪声(Noise),它是程序化生成的心脏,也是 Phase 2 毛玻璃、故障风的直接原料。这一章按「hash → noise → fbm → warp」四级台阶,一级级搭上去。
1GPU 没有 random():hash
Shader 里没有随机数函数——也不能有:0.1 章说过,同一像素每帧必须能算出同样的结果,否则画面会疯狂闪烁。我们要的其实是确定性伪随机:一个把坐标「搅碎」成 0~1 均匀分布的函数,输入相同输出相同,但看起来毫无规律。行业黑话叫 hash:
// 输入 2D 坐标,输出 0~1 的"随机"数
float hash21(vec2 p) {
p = fract(p * vec2(234.34, 435.345));
p += dot(p, p + 34.23);
return fract(p.x * p.y);
}
// 输入 2D 输出 2D(每个格子一个随机偏移向量时用)
vec2 hash22(vec2 p) {
float n = hash21(p);
return vec2(n, hash21(p + n));
}
原理不必背:大数相乘 + fract 折叠,把邻近输入放大到完全不同的输出。用它配合 1.3 章的格子编号,立刻能做「每个格子随机」:
2Value Noise:柔化的随机
hash 是「白噪声」——相邻两点毫无关系,看起来是砂纸。自然界的随机是连绵的:山这里高,旁边也不会突然矮成海沟。制造连绵随机的最简单办法叫 Value Noise,思路只有两步:
- 在整数格点上放 hash 随机值(格点之间先不管);
- 格子内部的点,把四个角的值平滑插值(mix + smoothstep,全是老朋友)。
float noise(vec2 p) {
vec2 id = floor(p); // 格子编号(1.3 章)
vec2 f = fract(p); // 格子内坐标 0~1
// 四个角的随机值
float a = hash21(id);
float b = hash21(id + vec2(1.0, 0.0));
float c = hash21(id + vec2(0.0, 1.0));
float d = hash21(id + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f); // smoothstep 曲线(1.1 章)
return mix(mix(a, b, u.x), // 底边两角插值
mix(c, d, u.x), // 顶边两角插值
u.y); // 上下再插值
}
就这么多——噪声不是新知识,是 fract/floor/hash/mix/smoothstep 的组合拳。这也是本书把造型函数放在最前面的原因。
3FBM:分形叠加出自然
单层 value noise 还是太「圆润」,像一锅慢速沸腾的粥。自然界的地形是大起伏上叠着小起伏,小起伏上还有更小的。模拟它的手法叫 FBM(Fractal Brownian Motion,分形布朗运动):把噪声叠加若干层,每层频率翻倍、振幅减半:
float fbm(vec2 p) {
float v = 0.0, amp = 0.5;
for (int i = 0; i < 5; i++) { // 5 层八度(octave)
v += amp * noise(p);
p *= 2.0; // 频率翻倍:细节更密
amp *= 0.5; // 振幅减半:影响更小
}
return v;
}
4Domain Warping:空间扭曲
压轴戏。前面都是「用噪声当颜色」,而高手的用法是用噪声扭曲坐标本身——查噪声之前,先让噪声把你的坐标推一把:fbm(p + fbm(p))。这叫 Domain Warping(定义域扭曲),大理石纹、烟雾、极光、油画质感,全是它:
5选型速查:什么时候用什么
| 工具 | 特征 | 典型用途 |
|---|---|---|
| hash(逐像素) | 砂纸,高频 | 胶片颗粒、抖动(dither)、毛玻璃采样偏移(2.3) |
| hash(逐格子) | 随机马赛克 | 格子花色、glitch 块位移(2.4)、星星位置 |
| value noise | 连绵起伏,较圆润 | 柔和扰动、水面微波、飘动 |
| fbm | 多尺度细节 | 云、烟、山、火焰、木纹 |
| domain warp | 流动的有机感 | 大理石、极光、油墨、抽象背景 |
Perlin/Simplex Noise:比 value noise 质量更高的梯度噪声(方向性更少、更均匀),原理类似但在格点放的是随机梯度而非随机值,需要更高质量时替换即可。Voronoi/Cellular Noise:基于「离哪个随机点最近」的细胞状图案,做水面焦散、鳞片、碎裂效果——2.3 章的水珠玻璃会见到它的简化版。
FBM 一次调用 = 5 层 noise = 20 次 hash,全屏跑在低端机上可能吃紧。省钱三招:① 降 octave 数,3 层往往够;② 把静态噪声烘焙成纹理提前生成,运行时只采样(2.1 章);③ AGSL 里能用 half 精度就用。手机 GPU 不是 Shadertoy 的 RTX,写之前心里要有这根弦。
本章小结
- Shader 的随机是确定性 hash:同输入同输出,画面才稳定;「随机感」来自输入被搅碎。
- Value Noise = 格点放 hash + 格内平滑插值——全部由 Phase 1 已学原语组成。
- FBM = 噪声按「频率×2、振幅÷2」叠 3~6 层,多尺度细节瞬间自然。
- Domain Warping = 用噪声扭坐标再查噪声,
fbm(p + fbm(p)),有机流动感之王。 - 选型:颗粒用 hash、起伏用 noise、云山用 fbm、流动用 warp;移动端注意 octave 成本。
动手练习
- 做「篝火」:fbm 的灰度乘一个由下往上的渐变(底亮顶暗),用 IQ 调色板映射成黑-红-黄,再让坐标随时间向上流动。
- 给 1.3 章的圆点阵加噪声:每个圆的半径乘上
noise(id * 0.5),规整的阵列立刻变成有机的泡泡群。 - 在 Domain Warping Demo 里,把推力场 q 也随时间演化(给它的两个 fbm 各加不同速度的 uTime),观察「二阶流动」——这就是很多付费动态壁纸的全部秘密。