URP 渲染实战 / 第 21 章 UV 动画与粒子 / 案例 21.8
案例 21.8热扭曲:采样 Opaque Texture进阶
目标:火焰上方的空气抖动,透过它看到的背景在晃动。
热扭曲(Heat Distortion)不是画出一种颜色,而是把“背后的画面”挪一挪再画回来。URP 在不透明物体画完后可以把颜色缓冲拷贝一份,作为 _CameraOpaqueTexture 供透明物体采样(URP Asset 勾选 )。扭曲 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)); }
几个细节:
- 偏移来自单通道噪声的两次错位采样。公共贴图
noise_fbm只有一个通道,在两个不同位置采样得到 x、y 两个不相关的分量。用法线贴图的 xy 作为偏移也很常见,效果相同。 - 除以深度。同样强度的扰动,离相机远时在屏幕上应该更小,否则远处的火堆会把整片背景扭得乱七八糟。
- 遮罩。偏移量乘以
particle_soft的径向遮罩和顶点色 alpha,扭曲在面片边缘归零,不会露出矩形边框;输出 alpha 也用遮罩,边缘处仍然显示原本的画面。

Opaque Texture 的三个限制① 它只包含不透明物体和天空盒:拷贝发生在不透明 Pass 之后,透明物体(包括其他粒子)不在里面。扭曲面片如果挡在另一团透明烟前面,那团烟会在扭曲区域里“消失”。② URP Asset 的 默认是 2× 双线性降采样,透过扭曲区域看到的画面会略模糊——对热浪反而是好事,对需要清晰折射的玻璃就要改成 None。③ 偏移采样会把位于扭曲面片前方的物体也“吸”进来,形成鬼影,修正方法是比较偏移位置的场景深度与面片深度,详见第 19 章水的折射。