URP 渲染实战 / 第 4 章 Shader 里的数学工具箱与程序化图案 / 案例 4.7
案例 4.7fBm 云纹进阶
目标:用分形布朗运动(fractional Brownian motion,fBm)叠加多层噪声,做出一片有大轮廓也有小细节的云。
单层噪声只有一种“尺度”的起伏:要么是大团块,要么是小碎点。自然界的形状在各个尺度上都有细节——远看是山的轮廓,近看是岩石的起伏,再近看是砂砾。fBm 就是把同一种噪声以不同频率叠加:
每一层叫一个八度(octave)。lacunarity(间隙度)是相邻两层的频率比,通常取 2,即每层细节缩小一半;gain(增益,也叫 persistence)是振幅比,通常取 0.5,即每层强度减半。gain 越大,小尺度细节越突出,画面越“粗糙”;gain 越小越平滑。
Assets/Cookbook/Ch04/ProceduralLib.hlsl(节选:fbm)
// fBm(分形布朗运动):把同一种噪声以“频率 ×lacunarity、振幅 ×gain”叠加 octaves 层。结果约在 [-1,1] float Fbm(float2 p, int octaves, float lacunarity, float gain) { float sum = 0.0, amp = 0.5, norm = 0.0; [loop] for (int k = 0; k < 10; k++) { if (k >= octaves) break; sum += amp * GradientNoise(p); norm += amp; // 每层旋转一点再平移,避免各层格子方向对齐出现“十字纹” p = Rotate2D(p, 0.6) * lacunarity + float2(17.3, -9.1); amp *= gain; } return sum / norm; }
// fBm(分形布朗运动):把同一种噪声以“频率 ×lacunarity、振幅 ×gain”叠加 octaves 层。结果约在 [-1,1] float Fbm(vec2 p, int octaves, float lacunarity, float gain) { float sum = 0.0, amp = 0.5, norm = 0.0; for (int k = 0; k < 10; k++) { if (k >= octaves) break; sum += amp * GradientNoise(p); norm += amp; // 每层旋转一点再平移,避免各层格子方向对齐出现“十字纹” p = Rotate2D(p, 0.6) * lacunarity + vec2(17.3, -9.1); amp *= gain; } return sum / norm; }
实现上有两个细节。一是除以振幅总和 norm,让不同层数的结果都保持在噪声原本的值域内,调层数时整体亮度不会跳变。二是每层先旋转再平移:如果各层都对齐在同一套方格上,它们的格线会在同样的位置叠加,出现隐约的横竖纹理;旋转一个不规则的角度(这里 0.6 弧度)可以打散这种对齐。
Assets/Cookbook/Ch04/C04_07_FbmClouds.shader(节选:clouds)
float n = Fbm(p, oct, _Lacunarity, _Gain) * 0.5 + 0.5; // → [0,1] // 覆盖率:把阈值往下推,云就变多;smoothstep 的宽度决定边缘软硬 float density = smoothstep(1.0 - _Cover, 1.0 - _Cover + _Softness, n); // 简单的“自阴影”:朝光源方向(左上)偏移一点再采一次,差值当作明暗 float n2 = Fbm(p + float2(-0.06, 0.06), oct, _Lacunarity, _Gain) * 0.5 + 0.5; float lit = saturate(0.5 + (n - n2) * 6.0);
float n = Fbm(p, oct, _Lacunarity, _Gain) * 0.5 + 0.5; // → [0,1] // 覆盖率:把阈值往下推,云就变多;smoothstep 的宽度决定边缘软硬 float density = smoothstep(1.0 - _Cover, 1.0 - _Cover + _Softness, n); // 简单的“自阴影”:朝光源方向(左上)偏移一点再采一次,差值当作明暗 float n2 = Fbm(p + vec2(-0.06, 0.06), oct, _Lacunarity, _Gain) * 0.5 + 0.5; float lit = saturate(0.5 + (n - n2) * 6.0);
把 fBm 变成云还需要两步“塑形”。覆盖率:用 smoothstep(1 − cover, 1 − cover + softness, n) 把噪声值截出一部分作为云,cover 越大阈值越低、云越多,softness 控制云边的软硬。体积感:朝光源方向偏移一点再采样一次,两次的差近似表示“噪声沿光方向的变化率”,正值说明朝光的一面,提亮;负值是背光面,压暗。这是一种很便宜的伪光照,第 28 章的体积光会看到更正式的做法。

动起来
_Wind 让采样坐标随时间平移,云就飘起来了。只平移整体会显得像一张贴图在滑动;让不同八度以不同速度移动(例如在 Fbm 的循环里给每层加上 _Time.y * 速度 * 层号),云会有“翻滚”的感觉。