GameTech/URP 渲染实战
源码 / shaders/Ch21/HeatDistortion.glsl
shaders/Ch21/HeatDistortion.glsl
// 案例 21.8:热扭曲(Heat Distortion)——采样 _CameraOpaqueTexture,按滚动噪声偏移屏幕 UV(GLSL 版,对应 Ch21/HeatDistortion.shader)
Shader "Cookbook/Ch21/HeatDistortion"
{
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" "IgnoreProjector"="True" }

        Pass
        {
            Name "Distortion"
            Tags { "LightMode"="UniversalForward" }
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off
            GLSLPROGRAM
            #pragma multi_compile_instancing
            // DeclareOpaqueTexture.hlsl 的 SampleSceneColor 在引擎 Core.glsl 里

            uniform float _Strength;
            uniform vec4 _NoiseTiling;
            uniform float _RiseSpeed, _Phase;
            uniform float _ShowOffset;
            uniform sampler2D _NoiseTex;
            uniform sampler2D _MaskTex;

            VARYING vec2 uv;
            VARYING vec4 screenData;   // xyw = positionNDC,z = 观察空间深度
            VARYING vec4 vColor;

            #ifdef VERTEX
            void main()
            {
                VertexPositionInputs p = GetVertexPositionInputs(positionOS.xyz);
                gl_Position = p.positionCS;
                uv = texcoord0.xy;
                screenData = vec4(p.positionNDC.xy, -p.positionVS.z, p.positionNDC.w);
                vColor = color;
            }
            #endif

            #ifdef FRAGMENT
            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));
            }
            void main() { fragColor = frag(); }
            #endif
            ENDGLSL
        }
    }
}