第 8 课:噪声与随机

返回教程目录 · 打开本课练习

“随机”先要稳定

程序化纹理、云、火焰、地形和木纹,都需要看起来有变化的数值。但片元 Shader 没有可持久修改的随机数状态:每个片元独立运行。同一输入应给出同一输出,才能让画面稳定。常见办法是对网格编号做确定性哈希:

float hash(vec2 p) {
  return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}

这不是密码学随机数,只是便于教学的伪随机散列。floor(vUv*12.0) 给每格一个整数编号,同格片元读同一值,刷新后分布仍相同。若把 floor(uTime*18.0) 加到编号中,每秒换 18 次随机图案,就得到电视雪花;不用 floor 时值会连续变化,视觉上更像闪动云,而不是一帧一帧的噪点。

从硬方格到连续噪声

大块马赛克只对 floor(p) 采样,格子边界会突变。Value Noise 在位置 p 所在格子的四个角各取一个 hash 值,再按格内小数坐标插值。先沿 X mix(a,b,w.x) 与 mix(c,d,w.x),再沿 Y 混合两条结果。权重 w=f*f*(3-2*f) 在格子边缘斜率趋近零,邻格连接更平滑。若直接用 f,颜色仍连续,但接缝处斜率可能可见。

噪声的输入坐标控制尺度。valueNoise(vUv*8.0) 比 *4.0 有更多细节,却不会凭空增加几何顶点。片元计算仍按画布像素数执行;把噪声用于顶点地形时,则按网格顶点数执行。根据效果选择阶段,不要把只需在少量顶点算一次的高度函数放进片元中重复几十万次。

多层噪声与 FBM

分形布朗运动(FBM)把几种尺度的噪声相加:低频负责大形状,高频负责细节,频率通常每层翻倍,权重每层减半。第 5 题先只叠加两层,便于看出各层贡献;后面云、火焰等练习使用四层。它是常见视觉合成方法,不代表真实流体或自然过程。

效果 公式中的关键变化 视觉结果
火焰 FBM 扰动左右边界,噪声坐标随时间向下移动 轮廓向上窜动
云雾 FBM 后用 smoothstep 选出较亮区域 柔和云团漂移
地形明暗 高度图相邻采样的差分构造近似法线 朝光方向的坡面更亮
大理石 FBM 扰动正弦波相位 原本笔直的条带弯曲
木纹 中心半径与扰动相位生成周期环 不规则同心年轮

第 8 题用 h(vUv+εx)-h(vUv) 与 h(vUv+εy)-h(vUv) 估算坡度,再组成法线与光方向点乘。这只改变着色,平面的轮廓仍然平;真实三维地形需像第 7 课一样移动顶点。差分步长过大使细节被平均,过小则容易受浮点精度和采样分辨率影响。示例使用 0.004 UV 单位,适合当前正方形预览。

数据流、性能与兼容性

JS 建立覆盖预览区域的平面。顶点 uv attribute 传到 vUv,经光栅化插值后,每个片元用它计算 hash、value noise、FBM 和最终颜色。动画题另由 JS 每帧以秒更新 uTime uniform。ShaderMaterial 示例使用 Three.js 的输出颜色空间片段,所有混色都在 Shader 内计算。

单次 hash 很便宜,但 value noise 每次读取四个 hash;四层 FBM 至少做四次 value noise,地形差分又会把 FBM 调用三遍。即使代码只有一行 fbm(...),也可能包含几十次数学运算。高分辨率全屏效果、移动端和多重后处理要测 GPU 时间;可降低层数、缩小渲染分辨率、缓存成纹理,或使用预制噪声贴图。sin 哈希在不同精度/驱动下不保证逐像素完全一致;需要跨设备一致的离线图案时,优先使用固定纹理或 CPU 生成的数据。

常见故障

噪声成整块纯色时,先输出 vUv 确认坐标在变,再检查是否过早 floor;格子数量不对时,区分 UV 乘数和画布像素尺寸;动画跳变时,检查 floor(uTime*rate) 是否本来就用于离散帧;火焰形状消失时,单独把遮罩输出成灰度,确认宽度与扰动项的值域。

练习

难度 题目 可见验收
⭐ 确定性的伪随机函数 刷新后相同的灰度方格
⭐ 逐帧变化的随机雪花 雪花持续闪烁
⭐⭐ Floor 分块马赛克噪声 硬边随机块
⭐⭐ Mix 双线性 Value Noise 连续灰度过渡
⭐⭐ 两层噪声叠加 FBM 大小尺度兼有
⭐⭐⭐ 噪声塑形的动态火焰 火焰向上流动
⭐⭐⭐ 多层 FBM 漂浮云雾 云团缓慢漂移
⭐⭐⭐ 噪声高度图与近似光照 明暗坡面
⭐⭐⭐⭐ 噪声扭曲的大理石纹 弯曲矿脉
⭐⭐⭐⭐ 带扰动的木纹年轮 不规则年轮