中EN
精修课概念页

细胞噪声:离我最近的那个点

Cellular Noise

更新于 2026-10-15 约 18 分钟WorleyVoronoi距离场GLSL程序化

另一种噪声

上一课的噪声是在格点上放随机值,再平滑地插值。1996 年,Steven Worley 发表了《A Cellular Texture Basis Function》,换了一个完全不同的出发点:在空间里撒一把点,每个像素只关心离自己最近的那一个点有多远。

书里特意标了时间:这比 Perlin 最初的噪声晚了大约十六年,又比他的 Simplex Noise(2001)早五年。这种噪声叫细胞噪声 (cellular noise),也叫 Worley 噪声。

先用笨办法

最直接的写法:对每个像素,和每一个点都算一次距离,留下最小的那个。

float minDist = 1.0;
for (int i = 0; i < 5; i++) {
  minDist = min(minDist, distance(st, pts[i]));
}
五个点的距离场0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

越黑离最近的点越近。四个点在慢慢漂,第五个点跟着鼠标。注意等距线在两个点的「势力范围」交界处折出了一道棱。

这个办法有两个硬伤:

  • GLSL 的循环上界必须是编译期常量,没法在运行时增减点的数量;
  • 每个像素都要和所有点比一遍,点一多,开销就跟着点数线性增长。

所以不能把「所有的点」都塞给每一个像素。

格子:每格一个点,只看邻居

Worley 的关键想法用的还是前几课那一套 floor 和 fract:

  1. 把空间放大并切成格子:i_st = floor(st),f_st = fract(st);
  2. 用格子编号生成随机数,在每一格里放一个随机的特征点:random2(i_st);
  3. 离当前像素最近的点,一定在自己这一格或者周围 8 格里。所以只需要比 9 个点。
和所有点都比一次每格一个点,只看 3 × 3
左边:每个像素都要和所有点比一次,点越多越慢。右边:每格只放一个点,像素只和自己所在格子及周围 8 格里的点比,不管画布上一共有多少点,都只比 9 次。

核心是一个双重循环,x 和 y 都从 −1 走到 1:

for (int y = -1; y <= 1; y++) {
  for (int x = -1; x <= 1; x++) {
    vec2 neighbor = vec2(float(x), float(y));   // 邻居格子的偏移
    vec2 point = random2(i_st + neighbor);      // 那一格里的点(格内坐标)
    vec2 diff = neighbor + point - f_st;        // 从像素指向那个点
    m_dist = min(m_dist, length(diff));
  }
}
Worley 噪声:格子加邻域搜索0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

橙线是格子,每格里有一个黄色的特征点在转圈。打开格子线能看到:每个点都待在自己的格子里,但它的「势力范围」可以伸进邻居家。

换个角度看:每个点都在向外「生长」,直到撞上邻居的生长前沿。这很像自然界里的生长:向外扩张,又被外力限制住。

Voronoi:不只要多远,还要「是谁」

细胞噪声输出的是一个距离。如果再记住最近的那个点是谁,就能把平面分成一块块区域,每块归离它最近的那个点管。这就是 Georgy Voronoi 提出的划分,也叫 Voronoi 图。

代码只要多存一样东西。这时不能再只写 min:找到更近的点时,除了更新距离,还要顺手记下它的身份:

if (dist < m_dist) {
  m_dist = dist;
  m_cell = cell;   // 记住最近的点属于哪一格
}
按最近的点上色:Voronoi 图0.0s
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

每块颜色归一个点管。鼠标所在那一格的点会直接放到鼠标上,移动鼠标,看看周围的区域怎么跟着变形。

拿它来做点什么

细胞噪声的距离本身就很好用。把每一块的颜色交给格子编号,再让离点越远越暗,一块块中间鼓、边缘陷下去的鹅卵石就出来了:

一地鹅卵石
改一改

也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。

错乱为 0 时,所有点都在格子正中,石块排成整齐的方格;往右拖,石块的大小和形状开始变得参差。

后来的改进

  • Gustavson(2011):把邻域从 3 × 3 缩到 2 × 2,工作量大减,代价是格子边界上可能出现不连续。
  • Quilez(2012):讲了怎么画出精确的 Voronoi 边界。
  • voro-noise(2014):在「插值随机值或梯度」(噪声)和「到最近点的距离」(Voronoi)之间找到一种更一般的度量,两者都成了同一个格子生成器的特例,可以连续地从一个过渡到另一个。

在游戏里怎么用

取舍与常见的坑

小结与练习

  • 细胞噪声 = 每个像素到最近特征点的距离。
  • 把空间切成格子、每格放一个点,最近的点一定在 3 × 3 的邻域里,所以每个像素只比 9 次。
  • 记住最近的点是谁,就得到 Voronoi 图;距离和身份一起用,能做出石块、细胞、裂纹。

练习:做一块龟裂的干泥地:用 Voronoi 的身份给每块泥土一个略有不同的颜色,再用距离把每块的边缘压暗,最后用上一课的噪声给整块地面加一点起伏。

  1. The Book of Shaders: More noise
  2. thebookofshaders.com/12

相关页面

图形学噪声函数Noise FunctionsKen Perlin 为电影《电子世界争霸战》(Tron) 设计的噪声,解决「看起来随机却不像电视雪花」的程序纹理问题。入门路径是值噪声 (value noise):格子角点取伪随机,格内用平滑曲线插值;进阶有梯度噪声 (gradient noise)、五次插值改进版,以及用单纯形网格降复杂度的 Simplex Noise。图形学二维 SDF 形状2D SDF Shapes在片元着色器里画形,不是发三角形图元,而是对每个像素问「我在形的里面还是外面」。矩形用 step/smoothstep 做轴对齐边界;圆与更复杂形用距离场 (distance field)——到特征点的距离再阈值。这是 2D SDF 程序化造型的入门路径。图形学隐式曲面与有符号距离函数Implicit Surfaces & Signed Distance Functions隐式曲面用 f(\mathbf{p})=0 定义:代入点结果为零即在曲面上。返回到最近表面有符号距离的 f 称为有符号距离函数 (SDF)。它们便于做光线求交、构造实体几何 (CSG)、混合与变形,通常用光线步进可视化,也可用移动立方体等方法转为三角形。图形学程序化图案Procedural Patterns片元着色器按像素执行:形状重复多少次,计算量几乎不变,因此特别适合平铺图案。核心手法是放大 st 再 fract 得到重复的 [0,1] 子空间,再在格子内画形、做矩阵变换,或按奇偶行错位、按格子旋转拼 Truchet。图形学着色器伪随机Shader Pseudorandom片元着色器里没有真正的「每次调用都不同」的随机:rand(x) 必须是确定性伪随机——同一输入永远同一输出。常用手法是 fract(sin(x)大数);二维则用 dot 把 vec2 压成标量再喂给同一套路。把空间 floor 成格子后,格子共享一个随机值,就能在秩序与混沌之间做生成设计。图形学分形布朗运动Fractional Brownian Motion分形布朗运动 (fBm) / 分形噪声:把多「八度 (octave)」噪声叠加——频率按空隙度 (lacunarity) 步进升高,振幅按增益 (gain) 衰减——得到跨尺度自相似细节。变体包括湍流(绝对值尖谷)、脊状噪声、乘性/多重分形,以及用噪声扭曲采样坐标的域扭曲 (domain warping)。

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原