GameTech/URP 渲染实战

案例 4.8域扭曲:大理石与流体纹理高阶

目标:用噪声扭曲噪声的输入坐标,从普通的 fBm 得到大理石、玛瑙、烟雾般的流动纹理。

域扭曲(Domain Warping)的思想很简单:与其对 f(p) 采样,不如对 f(p + h(p)) 采样,其中 h 是另一个噪声生成的二维位移场。原本规整的噪声被“揉皮”一样地拉扯,就出现了流体般的卷曲。这个技巧可以嵌套:

q=(F(p),F(p+o1)),r=(F(p+kq+o2),F(p+kq+o3)),f=F(p+kr)

F 是 fBm,o₁、o₂、o₃ 是任意的固定偏移(让两个分量互不相关),k 是扭曲强度。这一系列做法因 Inigo Quilez 的介绍而广为人知。

Assets/Cookbook/Ch04/C04_08_DomainWarp.shader(节选:warp)
// 第 1 层:q 是一个二维“位移场”,两个分量用两个不同偏移的 fBm
float2 q = 0;
if (_Levels >= 1)
    q = float2(F(p + float2(0.0, 0.0) + t), F(p + float2(5.2, 1.3) - t));
// 第 2 层:拿被 q 扭过的坐标再求一次位移场 r
float2 r = 0;
if (_Levels >= 2)
    r = float2(F(p + _Strength * q + float2(1.7, 9.2)), F(p + _Strength * q + float2(8.3, 2.8)));
// 最终:在被扭曲的坐标上采样
float2 warped = p + _Strength * ((_Levels >= 2) ? r : q);
float f = F(warped) * 0.5 + 0.5;
// 第 1 层:q 是一个二维“位移场”,两个分量用两个不同偏移的 fBm
vec2 q = vec2(0.0);
if (_Levels >= 1.0)
    q = vec2(F(p + vec2(0.0, 0.0) + t), F(p + vec2(5.2, 1.3) - t));
// 第 2 层:拿被 q 扭过的坐标再求一次位移场 r
vec2 r = vec2(0.0);
if (_Levels >= 2.0)
    r = vec2(F(p + _Strength * q + vec2(1.7, 9.2)), F(p + _Strength * q + vec2(8.3, 2.8)));
// 最终:在被扭曲的坐标上采样
vec2 warped = p + _Strength * ((_Levels >= 2.0) ? r : q);
float f = F(warped) * 0.5 + 0.5;
Assets/Cookbook/Ch04/C04_08_DomainWarp.shader(节选:color)
// 用 f、|q|、r.x 三个量混色:位移场本身也是很好的“着色依据”
half3 col = lerp(_Color1.rgb, _Color2.rgb, saturate(f * f * 2.2));
col = lerp(col, _Color4.rgb, saturate(length(q) * 0.9));
col = lerp(col, _Color3.rgb, saturate(abs(r.x) * 1.2) * step(1.5, _Levels));
col *= 0.6 + 0.6 * f;
// 用 f、|q|、r.x 三个量混色:位移场本身也是很好的“着色依据”
vec3 col = mix(_Color1.rgb, _Color2.rgb, saturate(f * f * 2.2));
col = mix(col, _Color4.rgb, saturate(length(q) * 0.9));
col = mix(col, _Color3.rgb, saturate(abs(r.x) * 1.2) * step(1.5, _Levels));
col *= 0.6 + 0.6 * f;

着色部分体现了域扭曲的另一个妙处:中间产物 q、r 本身就是很好的“着色依据”。用最终值 f 决定基础色,用 |q| 混入第二种颜色,用 r.x 混入高光色,同一个位置的几个量彼此相关又不完全相同,颜色层次就自然地“缠绕”在一起,比只用一个噪声值映射颜色丰富得多。

图 4-14扭曲层数 0、1、2(强度 4,5 个八度)。0 层就是普通的 fBm;1 层开始出现流动的卷曲;2 层像抛光的玛瑙或大理石切面
贵两层扭曲要调用 5 次 fBm(q 两次、r 两次、最终一次),每次 5 个八度,一共 25 次 Gradient Noise、100 次哈希,每个像素都要算。这样的 Shader 适合离线生成贴图、做加载界面背景或低分辨率的特效层;用作大面积的实时材质前,请先在目标机型上测试。