噪声:有记忆的随机
Noise Functions
从雪花到「有记忆」的混乱
上一课的随机数彼此毫无关联,画出来就是一屏电视雪花。可真实世界里的「随机」不是这样的:路面的起伏、岩石的纹理、天上的云,都难以预测,却又连贯、平滑,相邻的地方总是彼此相似。
1980 年代初,Ken Perlin 为电影《电子世界争霸战》(Tron) 设计了一种算法,专门生成这种「看起来随机、却不像雪花」的纹理。这个算法后来让他获得了奥斯卡科学工程奖。这就是噪声 (noise)。
一维:只在格点上掷骰子
思路很简单:别给每个 x 都掷一次骰子,只在整数格点上掷。把 x 拆成整数部分和小数部分:
i = floor(x):左边的格点;f = fract(x):在两个格点之间走了多远。
然后在 rand(i) 和 rand(i + 1.0) 两个随机高度之间插值。先用直线连,再把直线换成上一课就认识的 S 形曲线 f * f * (3.0 - 2.0 * f):
在下面的绘图器里依次点几个预设,看看一条随机折线是怎么变成平滑起伏的:
直接改上面的表达式就能重画:x 是 0 到 1 的横坐标,可以用 sin、pow、smoothstep 等函数。
峰和谷之间不再生硬地拐弯,这就是图形里「有机感」的来源。
二维:插值正方形的四个角
一维插的是两个端点,二维就插正方形的四个角。三维是立方体的八个角,一般地说,N 维要插 个角。
也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。
值噪声和梯度噪声
仔细看值噪声,会发现它有一种方块感:亮斑和暗斑总是排在格子上。1985 年,Perlin 提出了梯度噪声 (gradient noise):格点上放的不再是随机的高度,而是随机的方向。
每个角的贡献是 dot(随机方向, f - 角的位置):像素顺着这个方向越远,值越大。所有格点处的值都正好是 0,起伏全在格点之间,所以亮暗不再贴着格子排列。
也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。
Íñigo Quilez 在 Shadertoy 上做过值噪声和梯度噪声的并排对比,值得去看看。
更平的插值曲线
Perlin 后来还改进了经典噪声的插值:把三次曲线 (也就是 smoothstep)换成五次曲线
两条曲线在 0 和 1 处的斜率都是 0,但五次曲线在两端更平,格子之间的接缝更顺。上面梯度噪声的代码用的就是它。在绘图器里比一比:
直接改上面的表达式就能重画:x 是 0 到 1 的横坐标,可以用 sin、pow、smoothstep 等函数。
Simplex:三个角就够了
2001 年的 Siggraph 上,Perlin 又提出了 Simplex Noise。按他的说法,它比前代有这些改进:
- 乘法更少,计算复杂度更低;
- 扩展到更高维度时代价小得多;
- 方向性的瑕疵更少;
- 梯度定义良好,容易计算;
- 更适合用硬件实现。
关键在一个几何上的发现:要填满二维平面,不一定非得用正方形,最简单的形状是等边三角形。N 维的单纯形 (simplex) 只有 N + 1 个角。
| 维度 | 正方形 / 立方体的角 | 单纯形的角 |
|---|---|---|
| 2 | 4 | 3 |
| 3 | 8 | 4 |
| N |
具体做法是:先把方格切成两个等腰直角三角形,再把整个网格斜着「推」一下,让三角形变成等边的;然后比较变换后的 x 和 y 谁大,就知道像素落在上面那个三角形还是下面那个,只插三个角。Ian McEwan 和 Stefan Gustavson 写过面向教学的 GLSL 实现和论文。
拿噪声来做点什么
噪声本身只是一片起伏,真正好玩的是把它接到前几课学过的工具上。
用噪声扭转空间:木纹
把噪声的值当成旋转角度,先把坐标扭一扭,再画一排平行的条纹。条纹被局部地扭向不同方向,看上去就像木头的年轮和纹理:
也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。
用噪声当距离场,调制轮廓
噪声的值也可以当成距离场:在某个阈值附近用 smoothstep 切一刀,就得到一块块墨点和孔洞。还可以用噪声去推一个圆的半径,让轮廓活起来:
也可以直接改右边的代码:停手约 0.3 秒会自动重新运行;改坏了点「还原代码」。
在游戏里怎么用
取舍与常见的坑
小结与练习
- 噪声只在格点上取随机值,格点之间平滑插值,所以它「有记忆」:相邻的地方彼此相似。
- 一维插两个端点,二维插四个角,N 维插 个角;Simplex 用单纯形把角数降到 N + 1。
- 梯度噪声在格点上放随机方向,比值噪声少了方块感;五次插值曲线让接缝更顺。
- 噪声可以当角度、当距离场、当轮廓的半径,接上前面学过的一切。
练习:用噪声做一块大理石:先用噪声扭转空间,画出弯曲的条纹,再把条纹的颜色限制在白色和浅灰之间,最后用另一层更细的噪声加一点纹理。
Ken Perlin 为电影《电子世界争霸战》(Tron) 设计的噪声,解决「看起来随机却不像电视雪花」的程序纹理问题。入门路径是值噪声 (value noise):格子角点取伪随机,格内用平滑曲线插值;进阶有梯度噪声 (gradient noise)、五次插值改进版,以及用单纯形网格降复杂度的 Simplex Noise。
从雪花到「有记忆」的混沌
纯 着色器伪随机 样本互不相关。真实路面、岩石、云朵的不可预测性被称为「随机」,却带着连贯起伏。Perlin 在 1980 年代初给出优雅算法(并因此获奥斯卡科学工程奖);BoS 先用教学版讲清思路,再对照经典实现。
一维值噪声
把连续 拆成整数 i = floor(x) 与小数 f = fract(x):
- 对
i与i+1各算一次rand(格点随机高度); - 用
f在两点间插值:先试线性mix(rand(i), rand(i+1), f); - 再换成
smoothstep(0.,1.,f)或等价三次曲线u = f*f*(3.0-2.0*f)(与smoothstep同族 Hermite)。
平滑后峰谷之间不再折线硬拐——这就是图形里「有机感」的入口。练习方向:封装 noise(x);用噪声驱动平移/旋转/缩放;多形状「共舞」;用噪声调制外形做成「生物」再赋运动性格。
二维值噪声
一维插两个端点;二维则插正方形四角:rand(st)、rand(st+(1,0))、rand(st+(0,1))、rand(st+(1,1)),再用格内小数坐标做双线性(实际为立方平滑)混合。三维对应立方体八角( 个角)。书中示例先把空间 ×5 以看清格子,再在 noise 内 floor/fract、取四角、插值。
缩放练习:放大到再像随机、缩小到几乎平坦;接鼠标;把噪声梯度当距离场;用矩形色块 + 噪声模仿 Rothko 式色域。
值噪声 vs 梯度噪声
值噪声易显「方块感」。1985 年 Perlin 提出梯度噪声:角点存的是随机方向(vec2),在点处用梯度贡献再插值。Íñigo Quilez 的 Shadertoy 值噪声 / 梯度噪声对照能直观看出差异。
生成用法示例(书中):
- 用噪声角旋转空间再画线:
pos = rotate2d(noise(pos))*pos→ 木纹涡旋感(接 着色器二维矩阵变换); - 把噪声当距离场,叠
smoothstep做墨点/孔洞(接 二维 SDF 形状); - 用噪声调制圆等外形轮廓。
改进插值与 Simplex
原版非单纯形噪声里,三次 Hermite(,即 smoothstep)可换成五次曲线 ,两端更「平」,格子缝更顺(Perlin 论文说明)。
Siggraph 2001 的 Simplex Noise 相对前代的宣称改进包括:更少乘法与更低复杂度、高维扩展更便宜、减弱方向伪影、梯度定义良好且易算、利于硬件实现。关键几何洞察:二维不必填正方形——最简形是等边三角形;N 维单纯形有 N+1 个角(二维 3 而非 4,三维 4 而非 8……)。做法是把方格剖成等腰直角三角形再斜切成等边单纯形网格;比较变换后 、 大小判断落在上/下三角,只插三个角。Ian McEwan & Stefan Gustavson 等有面向教学的 GLSL 实现与论文。
在游戏开发中的意义
- 地形高度、风场、破坏掩码、云与雾的相位场;
- 材质:木纹、大理石、熔岩流的 UV 扰动;
- 取舍:值噪声实现短但块状;Simplex/梯度观感更好,代码与指令成本更高;引擎里常直接用内置噪声或烘焙噪声贴图(见 着色器纹理采样)。
工具箱:LYGIA generative;观察自然纹理与「噪声手感」同等重要。
原书问题
- ch.11「Improved Noise」开篇句「An improvement by Perlin to his original non-simplex noise Simplex Noise」语义粘连(把非单纯形改进与 Simplex 名称叠在一起)。按后文公式与 Perlin 论文链接,五次曲线 是对经典(非单纯形)噪声插值的改进;Simplex Noise 在下一小节才单独介绍。本页按此理解书写。