3、噪声算法基础(下):Simplex噪声、分形布朗运动(FBM)、域扭曲
好,我们接着聊噪声。上一节我们把Perlin噪声的底子打好了,这一节要聊的几个概念,才是真正让程序化地形“活”起来的关键。说白了,Perlin噪声只是原材料,而Simplex噪声、FBM和域扭曲,才是我们这些“厨师”手里的刀工和火候。
一、Simplex噪声:为什么我们需要它?
Perlin噪声有个老毛病——在高维度下计算量会爆炸。你想想看,在2D里我们要算4个顶点的插值,到了3D就是8个,4D就是16个。这谁顶得住?
Simplex噪声就是来解决这个问题的。它的核心思路很简单:用最少的顶点来构建网格。在2D里,Perlin用正方形网格(4个顶点),Simplex用三角形网格(3个顶点)。别小看这一个顶点的差距,到了3D,Perlin需要8个顶点,Simplex只需要4个。
我个人习惯在项目里,只要维度超过2D,就无脑上Simplex。尤其是做3D地形体素的时候,性能差距肉眼可见。
核心区别速览:
- 网格形状:Perlin用正方形/立方体,Simplex用三角形/四面体
- 计算复杂度:Perlin是O(2^n),Simplex是O(n²)
- 视觉效果:Simplex的高频细节更自然,没有明显的“网格感”
不过Simplex也有个坑——它的实现比Perlin复杂不少。主要是那个“从超立方体到单纯形的映射”有点绕。我建议你直接用现成的库,或者抄一份经过验证的实现。我自己当年手写过一次,debug到怀疑人生。
二、分形布朗运动(FBM):让地形有层次
单层噪声生成的地形,说白了就是“一马平川的小土坡”。你放大看,细节不够;缩小看,起伏不够。这就像画画只用了一支笔,没有层次感。
FBM的思路特别朴素:把不同频率、不同振幅的噪声叠在一起。
公式长这样:
float FBM(float2 pos, int octaves) {
float value = 0.0;
float amplitude = 0.5;
float frequency = 1.0;
for (int i = 0; i < octaves; i++) {
value += amplitude * noise(pos * frequency);
amplitude *= 0.5; // 振幅衰减
frequency *= 2.0; // 频率翻倍
}
return value;
}
你看,每次循环,频率翻倍(细节更密),振幅减半(影响变小)。这样叠加出来的结果,既有大尺度的山脉轮廓,又有小尺度的岩石纹理。
我在项目中遇到过一个问题:octaves设太高,性能扛不住。后来我学乖了,octaves一般设4-6层就够了,再多的话人眼也分辨不出来,反而徒增计算量。
我的调参经验:
- Lacunarity(频率增量):默认2.0,想要更平滑的地形就降到1.5
- Gain(振幅衰减):默认0.5,想要更崎岖的地形就提到0.6
- Octaves(层数):4-6层足够,再多就是浪费
三、域扭曲:让地形“不像是程序生成的”
这是我最喜欢的一个技巧。FBM虽然好,但看多了你会发现——它太“规整”了。山脉的走向、纹理的分布,总有一种数学上的均匀感。
域扭曲的思路是:不要直接采样噪声,而是先用另一个噪声去“扭曲”采样坐标。
代码实现很简单:
float DomainWarp(float2 pos) {
// 先用一个低频噪声扭曲坐标
float2 warp = noise(pos) * 0.5;
pos += warp;
// 再用扭曲后的坐标去采样FBM
return FBM(pos, 4);
}
效果呢?原本笔直的山脊会变得蜿蜒曲折,纹理的分布会呈现出一种“有机感”。说白了,就是让程序生成的地形看起来像是自然形成的,而不是数学公式算出来的。
我曾经用这个技巧做了一个峡谷地形,效果惊艳到我自己都不敢信。同事跑过来问:“你这是手捏的还是程序生成的?”
注意:域扭曲的强度不要太大。我见过有人把warp强度设到1.0以上,结果地形完全扭曲变形,根本没法看。建议从0.2开始调,慢慢往上加。
四、三者结合:实战中的黄金组合
在实际项目中,我很少单独用其中某一个。通常是:
- 底层地形:用Simplex噪声做FBM,生成大尺度山脉
- 细节纹理:用域扭曲后的FBM,生成岩石纹理和植被分布
- 混合使用:不同层级的噪声叠加,控制地形的高度、坡度、朝向
举个例子,我在UE5里做地形材质时,会这样写:
// 材质函数伪代码
float Height = FBM(WorldPosition.xz, 6);
float Detail = DomainWarp(WorldPosition.xz * 0.1);
float Slope = CalculateSlope(Height);
// 根据坡度混合不同的纹理
float GrassMask = smoothstep(0.0, 0.3, Slope);
float RockMask = 1.0 - GrassMask;
// 最终颜色
float3 FinalColor = lerp(GrassColor, RockColor, RockMask);
这样出来的地形,既有宏观的山脉起伏,又有微观的纹理细节,而且过渡自然,不会出现“一刀切”的生硬感。
五、一张图总结本章核心
下面这张图,是我自己梳理的知识体系。你看一眼,应该就能明白这三个概念之间的关系:
嗯,这一节的内容就到这里。Simplex噪声解决了性能问题,FBM解决了层次问题,域扭曲解决了真实感问题。三者配合,你就能做出让人“哇”出来的地形了。
公众号:蓝海资料掘金营,微信deep3321