GameTech/URP 渲染实战

案例 21.8热扭曲:采样 Opaque Texture进阶

目标:火焰上方的空气抖动,透过它看到的背景在晃动。

热扭曲(Heat Distortion)不是画出一种颜色,而是把“背后的画面”挪一挪再画回来。URP 在不透明物体画完后可以把颜色缓冲拷贝一份,作为 _CameraOpaqueTexture 供透明物体采样(URP Asset 勾选 Opaque Texture)。扭曲 Shader 用滚动的噪声生成一个二维偏移,加到屏幕 UV 上去采样这张图:

Assets/Cookbook/Ch21/HeatDistortion.shader(节选:frag)
half4 frag(Varyings i) : SV_Target
{
    float t = _Time.y + _Phase;
    // 两次错位采样单通道噪声,拼成一个二维偏移向量;整体向上滚动,像热空气上升
    float2 nuv = i.uv * _NoiseTiling.xy + float2(0, -frac(t * _RiseSpeed));
    half nx = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, nuv).r - 0.5;
    half ny = SAMPLE_TEXTURE2D(_NoiseTex, sampler_NoiseTex, nuv * 1.31 + 0.37).r - 0.5;
    half mask = SAMPLE_TEXTURE2D(_MaskTex, sampler_MaskTex, i.uv).a * i.color.a;

    // 屏幕上的偏移量除以深度:同样的“空气扰动”,离得越远在屏幕上越小
    float2 offset = float2(nx, ny) * (_Strength * mask) / max(i.screenData.z * 0.2, 1.0);

    // 调试:把偏移量放大后显示,R/G = 水平/竖直偏移,0.5 灰为不偏移
    if (_ShowOffset > 0.5) return half4(offset * (1.5 / max(_Strength, 1e-4)) + 0.5, 0.5, 1);

    float2 suv = i.screenData.xy / i.screenData.w;
    half3 scene = SampleSceneColor(suv + offset);
    return half4(scene, saturate(mask * 3.0));
}
vec4 frag()
{
    float t = _Time.y + _Phase;
    // 两次错位采样单通道噪声,拼成一个二维偏移向量;整体向上滚动,像热空气上升
    vec2 nuv = uv * _NoiseTiling.xy + vec2(0.0, -fract(t * _RiseSpeed));
    float nx = texture(_NoiseTex, nuv).r - 0.5;
    float ny = texture(_NoiseTex, nuv * 1.31 + 0.37).r - 0.5;
    float mask = texture(_MaskTex, uv).a * vColor.a;

    // 屏幕上的偏移量除以深度:同样的“空气扰动”,离得越远在屏幕上越小
    vec2 offset = vec2(nx, ny) * (_Strength * mask) / max(screenData.z * 0.2, 1.0);

    // 调试:把偏移量放大后显示,R/G = 水平/竖直偏移,0.5 灰为不偏移
    if (_ShowOffset > 0.5) return vec4(offset * (1.5 / max(_Strength, 1e-4)) + 0.5, 0.5, 1.0);

    vec2 suv = screenData.xy / screenData.w;
    vec3 scene = SampleSceneColor(suv + offset);
    return vec4(scene, saturate(mask * 3.0));
}

几个细节:

图 21-13左:原始场景;中:调试视图,显示放大后的屏幕 UV 偏移(R/G = 水平/竖直偏移,灰色为零);右:最终的热扭曲,背景网格线在面片范围内弯曲晃动,边缘平滑过渡
Opaque Texture 的三个限制① 它只包含不透明物体和天空盒:拷贝发生在不透明 Pass 之后,透明物体(包括其他粒子)不在里面。扭曲面片如果挡在另一团透明烟前面,那团烟会在扭曲区域里“消失”。② URP Asset 的 Opaque Downsampling 默认是 2× 双线性降采样,透过扭曲区域看到的画面会略模糊——对热浪反而是好事,对需要清晰折射的玻璃就要改成 None。③ 偏移采样会把位于扭曲面片前方的物体也“吸”进来,形成鬼影,修正方法是比较偏移位置的场景深度与面片深度,详见第 19 章水的折射。