引言

“随机”是游戏重玩价值的源泉。但计算机无法产生真正的随机,只能产生伪随机 (Pseudo-Random)。 在程序化生成 (Procedural Generation) 中,我们不仅需要随机,还需要平滑的随机,这就是噪声 (Noise) 的舞台。

本篇将依次介绍白噪声、三种主流噪声算法(Perlin / Simplex / Worley)、分形叠加原理,以及实际工程中的常见技巧。

Noise Types

1. 伪随机数 (White Noise)

最常见的 Random.Range(0, 1) 产生的是白噪声。 它的特点是:当前值与上一个值完全无关——频谱上各频率能量均匀分布,因此得名”白”噪声。 如果你用它来生成地形,你会得到一堆尖锐的锯齿,像地震仪的记录,而不是连绵的山脉。

// 白噪声:相邻采样点之间没有相关性
for (int x = 0; x < width; x++)
    heightmap[x] = Random.Range(0f, 1f); // 每个点独立掷骰子

白噪声适合离散事件——掉落率、暴击判定、洗牌,但不适合任何需要空间连续性的场景。

2. 柏林噪声 (Perlin Noise)

Ken Perlin 在 1983 年为电影《电子世界争霸战》(TRON) 发明了这种算法,并因此获得奥斯卡技术成就奖。 它的核心性质是:相近的输入产生相近的输出,即空间上连续可导。 这被称为梯度噪声 (Gradient Noise)——它在整数网格点上放置随机梯度向量,再通过插值让结果平滑过渡。

2.1 算法骨架(以 2D 为例)

Perlin 噪声的计算分四步:

第一步:确定网格单元。 对输入坐标 (x, y) 取整,找到它所在的单位正方形的四个顶点。

第二步:生成梯度向量。 每个整数网格点通过哈希函数映射到一个伪随机的单位梯度向量 (Gradient Vector)。经典实现使用一张长度 256 的排列表 (Permutation Table) 来完成这一映射。

第三步:计算距离向量并求点积。 从四个顶点分别到 (x, y) 做距离向量,再与对应顶点的梯度向量做点积 (Dot Product),得到四个标量影响值。

第四步:插值混合。 用缓动函数 (Ease Curve) 对四个点积值进行双线性插值。Ken Perlin 最初使用 \(3t^2 - 2t^3\),后改进为更平滑的 \(6t^5 - 15t^4 + 10t^3\)(消除二阶导数不连续的问题)。

// Perlin 噪声核心伪代码 (2D)
float PerlinNoise2D(float x, float y) {
    // 1) 网格坐标
    int x0 = FloorToInt(x), y0 = FloorToInt(y);
    int x1 = x0 + 1,        y1 = y0 + 1;

    // 小数部分(单元内的相对位置)
    float dx = x - x0, dy = y - y0;

    // 2) 四个顶点的梯度向量(由排列表 + 哈希决定)
    Vector2 g00 = Gradient(Hash(x0, y0));
    Vector2 g10 = Gradient(Hash(x1, y0));
    Vector2 g01 = Gradient(Hash(x0, y1));
    Vector2 g11 = Gradient(Hash(x1, y1));

    // 3) 距离向量 · 梯度向量
    float d00 = Dot(g00, new Vector2(dx,     dy));
    float d10 = Dot(g10, new Vector2(dx - 1, dy));
    float d01 = Dot(g01, new Vector2(dx,     dy - 1));
    float d11 = Dot(g11, new Vector2(dx - 1, dy - 1));

    // 4) 缓动插值  fade(t) = 6t^5 - 15t^4 + 10t^3
    float u = Fade(dx), v = Fade(dy);
    float lerp0 = Lerp(d00, d10, u);
    float lerp1 = Lerp(d01, d11, u);
    return Lerp(lerp0, lerp1, v);   // 结果范围约 [-1, 1]
}

为什么是梯度而不是直接存值? 如果直接在网格点存随机高度再插值(即 Value Noise),结果会有明显的”方块感”。梯度噪声让值在网格点上恰好为 0,变化由梯度方向决定,产生更自然的纹理。

2.2 维度

  • 1D 噪声: 摇晃的摄像机、起伏的沙丘轮廓线。
  • 2D 噪声: 地形高度图、云层纹理。
  • 3D 噪声: 洞穴生成、体积云、可动画化的 2D 纹理(第三维用作时间)。
  • 4D 噪声: 可无缝循环的 3D 动画纹理。

3. 单纯形噪声 (Simplex Noise)

2001 年,Ken Perlin 自己提出了对经典 Perlin 噪声的改进——Simplex Noise

3.1 为什么需要改进?

经典 Perlin 噪声存在两个问题: 1. 方向性伪影 (Directional Artifacts): 正方形网格导致沿 45° 方向出现可感知的条纹。 2. 高维性能差: 在 N 维空间中,正方形网格的一个单元有 \(2^N\) 个顶点,3D 时需要对 8 个顶点插值,4D 时 16 个。

3.2 核心改进

Simplex Noise 将正方形/立方体网格替换为单纯形网格 (Simplex Grid): - 2D 中单纯形是等边三角形,而非正方形。 - 3D 中是四面体,而非立方体。 - N 维中的单纯形只有 \(N + 1\) 个顶点(而非 \(2^N\)),因此高维时计算量大幅减少。

插值方式也不同:Simplex Noise 不使用缓动曲线 + 线性插值,而是让每个顶点的贡献通过径向衰减核 \(\max(0,\; r^2 - d^2)^4\) 独立累加,其中 \(d\) 是到顶点的距离。这种方式天然连续可导,且没有方向性偏好。

3.3 Perlin vs Simplex 小结

Perlin (经典) Simplex
网格形状 正方形 / 立方体 三角形 / 四面体
N 维顶点数 \(2^N\) \(N+1\)
伪影 有方向性条纹 极少
计算复杂度 \(O(2^N)\) \(O(N^2)\)

注意: Simplex Noise 的原始论文实现曾受专利保护(美国专利 6867776,2022 年已过期)。实践中可使用 OpenSimplex 或 OpenSimplex2 等无专利替代实现。

4. Worley 噪声 (Cellular Noise)

1996 年 Steven Worley 提出的细胞噪声 (Cellular Noise) 走了一条完全不同的路。

4.1 算法思路

  1. 在空间中随机撒特征点 (Feature Points)。
  2. 对任意采样点 \(P\),计算它到最近的第 \(n\) 个特征点的距离 \(F_n\)
  3. \(F_1\)(到最近点的距离)就是最基本的 Worley 噪声值。

结果看起来像 Voronoi 图:每个特征点周围形成一个”细胞”,细胞边界处值最大,中心处值为 0。

4.2 视觉变化

  • \(F_1\): 鹅卵石、鳞片、龟壳。
  • \(F_2 - F_1\): 更明显的细胞壁,适合石墙纹理。
  • \(F_1\) 取反或乘以颜色: 水面焦散 (Caustics)、岩浆裂纹。

4.3 性能优化

朴素实现需要遍历所有特征点 (\(O(N)\))。实际中将空间划分为等大网格,每个格子只放一个特征点,采样时只需检查周围 \(3^D\) 个格子(2D 检查 9 个,3D 检查 27 个),变成常数时间。

5. 噪声对比

特性 Perlin Simplex Worley
视觉风格 平滑连绵 平滑连绵(更均匀) 细胞状 / Voronoi
高维性能 较差 (\(2^N\)) 优秀 (\(N+1\)) 中等(邻域搜索)
方向伪影 极少
典型用途 地形、云、雾 地形、云(替代 Perlin) 石纹、鳞片、焦散
实现难度 中等 较高(坐标变换) 简单

在现代项目中,Simplex 通常是 Perlin 的直接上位替代;而 Worley 噪声提供了完全不同的美学,常与前两者组合使用。

6. 分形噪声 (Fractal Noise / fBm)

单一的噪声层看起来太”圆润”了。真实的山脉既有公里级别的大轮廓,也有米级别的小石块。 为了模拟这种多尺度细节,我们将多层不同频率的噪声叠加——这称为分形布朗运动 (Fractional Brownian Motion, fBm),也叫倍频程叠加 (Octave Stacking)。

\[ \text{fBm}(x) = \sum_{i=0}^{N-1} A_i \cdot \text{Noise}(x \cdot F_i) \]

三个关键参数: - Frequency (频率): 采样点的密度。每层频率通常是上一层的 2 倍。 - Amplitude (振幅): 该层的高度范围。每层振幅是上一层的 persistence 倍。 - Lacunarity (间隙度): 频率的层间倍率。默认为 2.0,增大它会让细节更密集。

/// <summary>
/// 分形噪声:多倍频程叠加
/// </summary>
float FractalNoise(float x, float y, int octaves, float persistence, float lacunarity) {
    float total     = 0f;
    float frequency = 1f;
    float amplitude = 1f;
    float maxValue  = 0f;   // 用于归一化

    for (int i = 0; i < octaves; i++) {
        total += PerlinNoise2D(x * frequency, y * frequency) * amplitude;

        maxValue  += amplitude;
        amplitude *= persistence;   // 典型值 0.5:每层振幅减半
        frequency *= lacunarity;    // 典型值 2.0:每层频率翻倍
    }

    return total / maxValue;  // 归一化到 [-1, 1]
}

参数调节直觉: - octaves = 1: 只有大山轮廓,没有细节。 - octaves = 6~8: 大轮廓 + 丰富的岩石细节,适合地形。 - persistence = 0.3: 高频细节很弱,地形偏圆润。 - persistence = 0.7: 高频细节强烈,地形粗粝嶙峋。 - lacunarity > 2.0: 细节频率跳得更快,纹理更”碎”。

7. 工程实践

7.1 种子管理 (Seed Management)

程序化生成的一大优势是确定性 (Determinism)——同一个种子 (Seed) 永远产生同一个世界。

实现要点: - 用种子初始化排列表 (Permutation Table),而不是使用固定的默认表。 - 同一帧内不要使用全局 Random,否则调用顺序变化会导致结果不可复现。推荐为每个系统创建独立的随机实例。 - 存档只需保存种子值 + 玩家修改部分,而非整张地图。

// 为地形和植被分别创建独立随机源,互不干扰
System.Random terrainRng    = new System.Random(worldSeed);
System.Random vegetationRng = new System.Random(worldSeed ^ 0x5DEECE66D);

7.2 无缝平铺 (Tileable Noise)

普通噪声在边界处不连续。要制作可重复平铺的纹理,有两种常用方法:

  1. 高维投影法: 将 2D 平面上的 (u, v) 映射到 4D 环面上的圆环坐标,再采样 4D 噪声。相当于在高维空间里走了一个”甜甜圈”。
  2. 边界混合法: 在接近边界时,将当前值与对边的值线性混合。简单但会在混合区产生模糊带。

7.3 域扭曲 (Domain Warping)

将噪声的输出反馈为另一次噪声采样的输入偏移,可以产生极其有机的扭曲效果——类似大理石纹、烟雾或奇幻地图。

// 域扭曲:用噪声扭曲采样坐标
float DomainWarp(float x, float y, float warpStrength) {
    float offsetX = FractalNoise(x,        y,        4, 0.5f, 2f);
    float offsetY = FractalNoise(x + 5.2f, y + 1.3f, 4, 0.5f, 2f);

    return FractalNoise(x + offsetX * warpStrength,
                        y + offsetY * warpStrength,
                        6, 0.5f, 2f);
}

多次迭代域扭曲(即用扭曲结果再扭曲)可以产生更加复杂的图案,Inigo Quilez 的文章中有许多精彩的示例。

7.4 多倍频程调参技巧

  • 先确定 scale(采样坐标的缩放因子),它决定了噪声的”基础波长”。地形通常 scale = 0.005 ~ 0.02
  • 再调 octaves: 从 1 开始逐层增加,直到细节足够。注意每增加一层都有性能开销。
  • 最后微调 persistence: 控制地形的粗粝程度。
  • 如果 GPU 生成,考虑在 Compute Shader 中并行计算——噪声采样天然适合并行,每个像素独立。

总结

  • 白噪声 用于掉落率、洗牌等离散事件。
  • 梯度噪声 (Perlin / Simplex) 用于地形、云层、雾气等连续变化,Simplex 是 Perlin 的现代替代。
  • 细胞噪声 (Worley) 用于石纹、鳞片、水面焦散等有机纹理。
  • 通过分形叠加(多倍频程),可以在单一噪声基础上创造出多尺度的自然细节。
  • 工程上,种子管理保证确定性,域扭曲创造复杂图案,无缝平铺解决纹理拼接。

< 上一篇: 骨骼动画 | 回到目录 | 下一篇: 导航与行为 >

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。