扭曲效果 (Distortion)

屏幕空间的折射和扭曲

📚 原理

扭曲效果通过偏移屏幕 UV 来模拟折射、热浪、水下等视觉效果。核心是采样屏幕颜色纹理(_CameraOpaqueTexture)时使用偏移后的 UV。

原始像素 UV 偏移 采样位置

💻 URP 实现

基础扭曲 Shader

Shader "Custom/Distortion"
{
    Properties
    {
        _DistortionMap("Distortion Map", 2D) = "bump" {}
        _DistortionStrength("Strength", Range(0, 0.1)) = 0.02
    }
    
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" "RenderPipeline"="UniversalPipeline" }
        
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareOpaqueTexture.hlsl"
            
            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 screenPos : TEXCOORD1;
            };
            
            TEXTURE2D(_DistortionMap);
            SAMPLER(sampler_DistortionMap);
            
            CBUFFER_START(UnityPerMaterial)
                float4 _DistortionMap_ST;
                float _DistortionStrength;
            CBUFFER_END
            
            Varyings vert(Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                output.uv = TRANSFORM_TEX(input.uv, _DistortionMap);
                output.screenPos = ComputeScreenPos(output.positionCS);
                return output;
            }
            
            half4 frag(Varyings input) : SV_Target
            {
                // 采样扭曲贴图(法线贴图格式)
                half4 distortTex = SAMPLE_TEXTURE2D(_DistortionMap, sampler_DistortionMap, input.uv);
                half2 distortion = (distortTex.xy * 2.0 - 1.0) * _DistortionStrength;
                
                // 计算屏幕 UV
                float2 screenUV = input.screenPos.xy / input.screenPos.w;
                
                // 应用扭曲
                screenUV += distortion;
                
                // 采样屏幕颜色
                half4 sceneColor = SAMPLE_TEXTURE2D(_CameraOpaqueTexture, sampler_CameraOpaqueTexture, screenUV);
                
                return sceneColor;
            }
            ENDHLSL
        }
    }
}

动态热浪效果

// 使用噪声和时间产生动态扭曲
float2 AnimatedDistortion(float2 uv, float time)
{
    // 多层噪声叠加
    float2 offset1 = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, uv + time * 0.1).xy;
    float2 offset2 = SAMPLE_TEXTURE2D(_NoiseMap, sampler_NoiseMap, uv * 2 - time * 0.15).xy;
    
    float2 distortion = (offset1 + offset2 - 1.0) * _Strength;
    return distortion;
}
⚙️ URP 设置

启用 Opaque Texture

在 URP Asset 中启用 "Opaque Texture" 选项,否则 _CameraOpaqueTexture 不可用。

路径:URP Asset > Quality > Opaque Texture = On

🎮 应用场景

🔮 玻璃/水晶

透明物体的折射效果

🔥 热浪

火焰、沙漠的空气扭曲

🌊 水下

水面折射、水下视角

🛡️ 隐身效果

隐形斗篷的光学迷彩

💡 技巧

边缘处理

扭曲后的 UV 可能超出 [0,1] 范围,建议 clamp 或使用边缘渐变:

// 边缘衰减
float edge = smoothstep(0, 0.1, screenUV.x) * smoothstep(1, 0.9, screenUV.x);
edge *= smoothstep(0, 0.1, screenUV.y) * smoothstep(1, 0.9, screenUV.y);
distortion *= edge;