3. 噪波与分形:理解不同噪波类型、分形布朗运动、噪波叠加与混合
噪波,说白了就是程序化纹理的「地基」。我做了这么多年Houdini,发现很多新手一上来就想着调各种炫酷节点,结果做出来的东西要么太规则像塑料,要么乱成一团。其实问题往往出在——你没搞懂噪波是怎么工作的。
这一章,咱们就把它彻底讲透。
3.1 三种核心噪波:Perlin、Worley、Cellular
Houdini里内置了十几种噪波,但真正常用的,其实就三种。我个人习惯把它们分成「平滑派」、「细胞派」和「混合派」。
3.1.1 Perlin噪波——平滑自然的「万金油」
Perlin噪波是Ken Perlin在1983年发明的,用来生成自然纹理。它的特点是:连续、平滑、各向同性。说白了,就是看起来像云、像大理石、像地形起伏的那种感觉。
在VEX里调用很简单:
// 基本Perlin噪波
float n = noise(@P);
// 带频率和振幅控制
float n = noise(@P * 2.0) * 0.5;
我在项目中遇到过一个问题:用Perlin噪波做地形时,如果频率设得太高,会出现「锯齿感」。后来发现是因为采样点不够密。嗯,这里要注意——Perlin噪波在低频时表现最好,高频时建议用其他噪波替代。
3.1.2 Worley噪波——细胞纹理的「硬核担当」
Worley噪波也叫Cellular噪波,它模拟的是细胞、岩石、马赛克那种「块状」结构。它的核心思想是:计算空间点到最近特征点的距离。
你想想看,为什么叫「细胞」?因为每个点都离自己的「细胞核」最近,自然就形成了多边形边界。
// Worley噪波
float n = noise(@P, "worley");
// 获取距离值(F1表示最近距离,F2表示次近距离)
float f1, f2;
f1 = xyzdist(1, @P, f2);
我曾经用Worley噪波做岩石裂缝纹理,效果出奇的好。但有个坑——Worley噪波默认输出的是距离值,范围是0到1,但分布不均匀。如果你直接用它做高度图,边缘会特别锐利。我的建议是:先做一次smoothstep再使用。
3.1.3 Cellular噪波——Perlin和Worley的「混血儿」
Cellular噪波其实不是独立算法,它是Perlin和Worley的混合体。Houdini里有个专门的cellnoise函数,它生成的是「每个晶胞内恒定值」的效果。
// Cellular噪波(每个格子内恒定值)
float n = cellnoise(@P);
// 配合随机旋转可以做出六边形蜂窝效果
vector pos = @P;
float n = cellnoise(pos * 2.0);
为什么需要它?因为Perlin太平滑,Worley太锐利,Cellular正好在中间——它保留了块状结构,但每个块内部是平滑过渡的。做瓷砖、地板、鳞片纹理时特别好用。
3.2 分形布朗运动(fBm)——从单层到多层
单层噪波有个致命问题:细节不够。你想想看,自然界里的石头、木头、云朵,哪个是单一频率的?都是大结构套小结构,层层嵌套。
fBm(分形布朗运动)就是解决这个问题的。它的原理很简单:把不同频率、不同振幅的噪波叠加起来。
// 手动实现fBm
float fbm(vector 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;
}
这里有个关键参数叫「持久度」(Persistence),也就是每次迭代振幅衰减的速度。持久度越高,高频细节越明显;持久度越低,整体越平滑。
核心公式:
fBm(P) = Σ (amplitude_i × noise(frequency_i × P))
其中 amplitude_i = persistence^i,frequency_i = lacunarity^i
我记得有一次做沙漠地形,用了6层fBm,结果渲染出来全是高频噪点。后来发现是持久度设成了0.7,太高了。对于地形,我建议持久度在0.4-0.5之间,否则会失去宏观结构。
3.3 噪波叠加与混合——创造无限可能
单种噪波就像单一乐器,听着单调。叠加混合就像交响乐,能做出千变万化的效果。
3.3.1 加法混合
最简单的方式:把不同噪波的值加起来。
// Perlin + Worley 混合
float perlin = noise(@P);
float worley = noise(@P, "worley");
float mixed = perlin * 0.6 + worley * 0.4;
这种混合适合做「既有平滑起伏又有细节纹理」的效果,比如风化岩石。
3.3.2 乘法混合
乘法混合会产生「遮罩」效果——一个噪波控制另一个噪波的可见区域。
// 用Perlin遮罩Worley
float mask = noise(@P * 0.5); // 低频遮罩
float detail = noise(@P * 4.0, "worley");
float result = mask * detail;
我在做苔藓纹理时就用过这招:用Perlin噪波控制苔藓的分布区域,用Worley噪波控制苔藓内部的细节。效果非常自然。
3.3.3 域扭曲(Domain Warping)
这是我最喜欢的技术。原理很简单:用第一个噪波的结果去偏移第二个噪波的采样位置。
// 域扭曲
vector offset = vector(noise(@P), noise(@P + 100), noise(@P + 200));
vector warpedPos = @P + offset * 0.3;
float n = noise(warpedPos);
为什么这么做?因为直接叠加只是「值」的混合,而域扭曲是「结构」的混合。做出来的纹理会有流动感、扭曲感,像水波纹、烟雾、大理石纹路。
我的经验:
域扭曲的偏移量不要超过0.5,否则结构会完全破碎。我一般控制在0.1-0.3之间,既能产生扭曲效果,又保留原始结构。
3.4 知识体系结构图
下面这张图总结了本章的核心逻辑,我建议你保存下来,做纹理时对照着看:
3.5 避坑指南与实用技巧
最后,分享几个我踩过的坑,希望能帮你少走弯路:
我曾经犯过的错误:
- 噪波范围没归一化:不同噪波的值域不同,Perlin在[-1,1],Worley在[0,1]。混合前一定要先归一化,否则结果会偏暗或偏亮。
- fBm层数太多:超过6层后,肉眼基本看不出区别,但计算量翻倍。我一般控制在3-5层。
- 域扭曲过度:偏移量太大时,结构会完全破碎。记住:域扭曲是「调味料」,不是「主菜」。
我的调试技巧:
在Houdini里,我习惯先用visualize节点看噪波的灰度分布。如果大部分像素集中在0.3-0.7之间,说明对比度不够,需要做contrast调整。如果出现大量纯黑或纯白,说明范围溢出,需要fit到0-1。
好了,这一章的内容就到这里。噪波这东西,说难不难,说简单也不简单。关键是多动手试,多观察自然界里的纹理结构。你做得多了,自然就能「看山不是山,看水不是水」——看到任何纹理,脑子里都能拆解出它用了哪几种噪波、怎么混合的。