PHASE 1 · 用数学作画

1.5随机与噪声

hash、Value Noise、FBM 与 Domain Warping

到目前为止你画的一切都太「干净」了——完美的圆、精确的网格。而自然界的云、烟、山、木纹全是有规律的杂乱。制造这种杂乱的工具叫噪声(Noise),它是程序化生成的心脏,也是 Phase 2 毛玻璃、故障风的直接原料。这一章按「hash → noise → fbm → warp」四级台阶,一级级搭上去。

1GPU 没有 random():hash

Shader 里没有随机数函数——也不能有:0.1 章说过,同一像素每帧必须能算出同样的结果,否则画面会疯狂闪烁。我们要的其实是确定性伪随机:一个把坐标「搅碎」成 0~1 均匀分布的函数,输入相同输出相同,但看起来毫无规律。行业黑话叫 hash:

GLSL · 经典 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,思路只有两步:

  1. 整数格点上放 hash 随机值(格点之间先不管);
  2. 格子内部的点,把四个角的值平滑插值(mix + smoothstep,全是老朋友)。
GLSL · Value Noise(逐行都是学过的东西)
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,分形布朗运动):把噪声叠加若干层,每层频率翻倍、振幅减半:

GLSL · FBM(五行循环,半个自然界)
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 章的水珠玻璃会见到它的简化版。

Android 视角 性能

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 成本。

动手练习

  1. 做「篝火」:fbm 的灰度乘一个由下往上的渐变(底亮顶暗),用 IQ 调色板映射成黑-红-黄,再让坐标随时间向上流动。
  2. 给 1.3 章的圆点阵加噪声:每个圆的半径乘上 noise(id * 0.5),规整的阵列立刻变成有机的泡泡群。
  3. 在 Domain Warping Demo 里,把推力场 q 也随时间演化(给它的两个 fbm 各加不同速度的 uTime),观察「二阶流动」——这就是很多付费动态壁纸的全部秘密。