GameTech/URP 渲染实战
源码 / Ch21/HeatDistortion.shader
Ch21/HeatDistortion.shader
// 案例 21.8:热扭曲(Heat Distortion)——采样 _CameraOpaqueTexture,按滚动噪声偏移屏幕 UV
Shader "Cookbook/Ch21/HeatDistortion"
{
    Properties
    {
        [NoScaleOffset] _NoiseTex ("Noise", 2D) = "gray" {}
        [NoScaleOffset] _MaskTex ("Mask (A)", 2D) = "white" {}
        _Strength ("Strength", Range(0, 0.2)) = 0.04
        _NoiseTiling ("Noise Tiling", Vector) = (1.5, 1, 0, 0)
        _RiseSpeed ("Rise Speed", Float) = 0.6
        _Phase ("Phase (for stills)", Float) = 0
        [Toggle] _ShowOffset ("Debug: Show Offset", Float) = 0
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" "IgnoreProjector"="True" }

        Pass
        {
            Name "Distortion"
            Tags { "LightMode"="UniversalForward" }
            Blend SrcAlpha OneMinusSrcAlpha
            ZWrite Off
            Cull Off
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"

            CBUFFER_START(UnityPerMaterial)
                half _Strength;
                float4 _NoiseTiling;
                float _RiseSpeed, _Phase;
                half _ShowOffset;
            CBUFFER_END
            TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex);
            TEXTURE2D(_MaskTex);  SAMPLER(sampler_MaskTex);

            struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; UNITY_VERTEX_INPUT_INSTANCE_ID };
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv         : TEXCOORD0;
                float4 screenData : TEXCOORD1;   // xyw = positionNDC,z = 观察空间深度
                half4  color      : COLOR;
            };

            Varyings vert(Attributes v)
            {
                Varyings o;
                UNITY_SETUP_INSTANCE_ID(v);
                VertexPositionInputs p = GetVertexPositionInputs(v.positionOS.xyz);
                o.positionCS = p.positionCS;
                o.uv = v.uv;
                o.screenData = float4(p.positionNDC.xy, -p.positionVS.z, p.positionNDC.w);
                o.color = v.color;
                return o;
            }

            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));
            }
            ENDHLSL
        }
    }
}