GameTech/URP 渲染实战

案例 21.1UV 滚动:流动的熔岩入门

目标:只用一张灰度噪声图,做出缓慢流动、亮暗翻滚的熔岩河。

单层滚动就是 uv + speed × t:整张图匀速平移,像传送带。它的问题是“太刚体”——所有细节以同样的速度、同样的方向移动,眼睛一下就能看出是一张图在平移。改进分三步:

  1. 双层叠加:同一张噪声以不同的平铺、不同的速度和方向再滚一层,两层相乘。两层的相对运动让图案不断变化,再也找不到“那一张图”。相乘会让平均亮度下降(0.5 × 0.5 = 0.25),所以乘以 2 补回来。
  2. UV 扰动:用第二层的值去偏移第一层的 UV。原本直线运动的图案开始扭曲、汇聚、分叉,像有了流体的速度场。
  3. 渐变映射(Gradient Mapping):把灰度当作“温度”,查一张一维的颜色渐变(公共贴图 ramp_fire):暗处是冷却凝固的黑色岩壳,亮处是黄白色的高温熔岩,并乘上 k² 让高温处成为 HDR 发光。
Assets/Cookbook/Ch21/UVScroll.shader(节选:frag)
half4 frag(Varyings i) : SV_Target
{
    // 时间:运行时用 _Time.y,配图时 _Time 不可控,用 _Phase 指定“第几秒”
    // frac 把偏移量限制在 0..1:贴图是 Repeat 寻址,结果不变,但避免长时间运行后浮点精度下降
    float t = _Time.y + _Phase;
    float2 off1 = frac(_Layer1.zw * t);
    float2 off2 = frac(_Layer2.zw * t);

    // 第二层:既参与叠加,也用来扰动第一层的 UV
    half n2 = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, i.uv * _Layer2.xy + off2).r;
    float2 distort = (n2 - 0.5) * _Distort * _SecondLayer;
    half n1 = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, i.uv * _Layer1.xy + off1 + distort).r;

    half v = lerp(n1, n1 * n2 * 2.0, _SecondLayer);     // 两层相乘再 ×2,保持平均亮度
    v = saturate(v);

    half3 col = v.xxx;
    if (_UseRamp > 0.5)
    {
        // 渐变映射:灰度 → 颜色。暗处是冷却的黑色岩壳,亮处是高温的黄白色
        half k = smoothstep(0.3, 1.0, v);
        col = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, float2(k, 0.5)).rgb * (k * k * _Intensity);
    }
    col = MixFog(col, i.fog);
    return half4(col, 1);
}
vec4 frag()
{
    // 时间:运行时用 _Time.y,配图时 _Time 不可控,用 _Phase 指定“第几秒”
    // fract 把偏移量限制在 0..1:贴图是 Repeat 寻址,结果不变,但避免长时间运行后浮点精度下降
    float t = _Time.y + _Phase;
    vec2 off1 = fract(_Layer1.zw * t);
    vec2 off2 = fract(_Layer2.zw * t);

    // 第二层:既参与叠加,也用来扰动第一层的 UV
    float n2 = texture(_NoiseTex, uv * _Layer2.xy + off2).r;
    vec2 distort = vec2((n2 - 0.5) * _Distort * _SecondLayer);
    float n1 = texture(_NoiseTex, uv * _Layer1.xy + off1 + distort).r;

    float v = mix(n1, n1 * n2 * 2.0, _SecondLayer);     // 两层相乘再 ×2,保持平均亮度
    v = saturate(v);

    vec3 col = vec3(v);
    if (_UseRamp > 0.5)
    {
        // 渐变映射:灰度 → 颜色。暗处是冷却的黑色岩壳,亮处是高温的黄白色
        float k = smoothstep(0.3, 1.0, v);
        col = texture(_RampTex, vec2(k, 0.5)).rgb * (k * k * _Intensity);
    }
    col = MixFog(col, fog);
    return vec4(col, 1.0);
}
图 21-1左:单层噪声滚动;中:两层不同速度的噪声相乘,并用第二层扰动第一层的 UV;右:灰度经过火焰渐变映射,成为熔岩
调参建议两层的速度方向不要相同也不要正好相反,夹角 30°–90° 最自然;平铺比例避免整数倍(例如 1 与 1.7),否则两层图案会周期性地重合,出现“节拍”。渐变图的采样坐标先过一次 smoothstep 拉开对比,熔岩才会有清晰的“岩壳/裂缝”分界。
坑:贴图的 Wrap ModeUV 滚动依赖 Repeat 寻址。如果噪声图被设成 Clamp,滚出 0..1 之后就是一片拉长的边缘像素。渐变图则相反,必须是 Clamp——否则采样坐标为 1 时会因为双线性过滤混进另一端的颜色,最亮处突然出现一圈暗色。本书公共贴图里 ramp_* 已设为 Clamp。