GameTech/URP 渲染实战
源码 / Ch21/Particle.shader
Ch21/Particle.shader
// 案例 21.5–21.7:通用粒子 Shader——顶点色、混合模式、Custom Vertex Streams 帧间混合、软粒子、近相机淡出、渐变映射
Shader "Cookbook/Ch21/Particle"
{
    Properties
    {
        [MainTexture] _BaseMap ("Texture", 2D) = "white" {}
        [HDR] _BaseColor ("Tint", Color) = (1,1,1,1)
        [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 5     // SrcAlpha
        [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 10    // OneMinusSrcAlpha
        [Toggle] _Premultiply ("Premultiply RGB by Alpha", Float) = 0
        [Toggle] _Modulate ("Modulate (for Multiply blend)", Float) = 0
        [Toggle(_FLIPBOOKBLENDING_ON)] _FlipbookBlending ("Flipbook Blending (needs UV2 + AnimBlend streams)", Float) = 0
        [Toggle(_SOFTPARTICLES_ON)] _SoftParticles ("Soft Particles", Float) = 0
        _SoftDistance ("Soft Distance (m)", Float) = 0.5
        [Toggle(_CAMERAFADE_ON)] _CameraFade ("Camera Near Fade", Float) = 0
        _CameraFadeNear ("Fade Near (m)", Float) = 0.3
        _CameraFadeFar ("Fade Far (m)", Float) = 1.5
        [Toggle(_RAMP_ON)] _UseRamp ("Color Ramp (fire)", Float) = 0
        [NoScaleOffset] _RampTex ("Ramp", 2D) = "white" {}
        _RampScale ("Ramp Input Scale", Float) = 1.5
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "RenderPipeline"="UniversalPipeline" "Queue"="Transparent" "IgnoreProjector"="True" "PreviewType"="Plane" }

        Pass
        {
            Name "ForwardUnlit"
            Tags { "LightMode"="UniversalForward" }
            Blend [_SrcBlend] [_DstBlend]
            ZWrite Off
            Cull Off
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature_local _FLIPBOOKBLENDING_ON
            #pragma shader_feature_local _SOFTPARTICLES_ON
            #pragma shader_feature_local _CAMERAFADE_ON
            #pragma shader_feature_local_fragment _RAMP_ON
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
                half _Premultiply, _Modulate;
                float _SoftDistance, _CameraFadeNear, _CameraFadeFar;
                half _RampScale;
            CBUFFER_END
            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
            TEXTURE2D(_RampTex); SAMPLER(sampler_RampTex);

            // 与粒子系统 Renderer › Custom Vertex Streams 的顺序一一对应:
            //   Position(POSITION) · Color(COLOR) · UV(TEXCOORD0.xy) · UV2(TEXCOORD0.zw) · AnimBlend(TEXCOORD1.x)
            struct Attributes
            {
                float4 positionOS : POSITION;
                half4  color      : COLOR;
            #if defined(_FLIPBOOKBLENDING_ON)
                float4 texcoords  : TEXCOORD0;   // xy = 当前帧 UV,zw = 下一帧 UV
                float  animBlend  : TEXCOORD1;   // 两帧之间的进度 0..1
            #else
                float2 texcoords  : TEXCOORD0;
            #endif
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                half4  color      : COLOR;
                float4 uv         : TEXCOORD0;   // xy 当前帧,zw 下一帧
                float4 screenData : TEXCOORD1;   // xyw = positionNDC,z = 自身观察空间深度
                float2 extra      : TEXCOORD2;   // x = 帧混合系数,y = 雾
            };

            Varyings vert(Attributes v)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(v);
                VertexPositionInputs p = GetVertexPositionInputs(v.positionOS.xyz);
                o.positionCS = p.positionCS;
                o.color = v.color;
            #if defined(_FLIPBOOKBLENDING_ON)
                o.uv = float4(TRANSFORM_TEX(v.texcoords.xy, _BaseMap), TRANSFORM_TEX(v.texcoords.zw, _BaseMap));
                o.extra.x = v.animBlend;
            #else
                o.uv = TRANSFORM_TEX(v.texcoords.xy, _BaseMap).xyxy;
            #endif
                o.screenData = float4(p.positionNDC.xy, -p.positionVS.z, p.positionNDC.w);
                o.extra.y = ComputeFogFactor(p.positionCS.z);
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                half4 tex = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv.xy);
            #if defined(_FLIPBOOKBLENDING_ON)
                half4 tex2 = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv.zw);
                tex = lerp(tex, tex2, i.extra.x);
            #endif

                // 顶点色 = 粒子系统的 Start Color × Color over Lifetime:整个粒子系统的颜色变化都从这里进来
                half4 c = tex * i.color * _BaseColor;

            #if defined(_RAMP_ON)
                // 渐变映射:用“贴图浓度 × 粒子剩余寿命(顶点色 alpha)”去查火焰渐变,越老越暗红
                half k = saturate(tex.a * i.color.a * _RampScale);
                c.rgb = SAMPLE_TEXTURE2D(_RampTex, sampler_RampTex, float2(k, 0.5)).rgb * _BaseColor.rgb;
                c.a = saturate(k);
            #endif

                float thisZ = i.screenData.z;
            #if defined(_SOFTPARTICLES_ON)
                // 软粒子:比较场景深度与粒子自身深度,越接近(快要插进物体)越透明
                float2 suv = i.screenData.xy / i.screenData.w;
                float sceneZ = LinearEyeDepth(SampleSceneDepth(suv), _ZBufferParams);
                c.a *= saturate((sceneZ - thisZ) / _SoftDistance);
            #endif
            #if defined(_CAMERAFADE_ON)
                // 近相机淡出:粒子贴脸时会糊满屏幕、产生巨大 Overdraw,提前淡掉
                c.a *= saturate((thisZ - _CameraFadeNear) / max(_CameraFadeFar - _CameraFadeNear, 1e-3));
            #endif

                c.rgb *= lerp(1.0, c.a, _Premultiply);   // 预乘:Blend One OneMinusSrcAlpha 时需要
                // 正片叠底(Blend DstColor Zero)没有 alpha 参与:把 alpha 折算进颜色,透明处趋向白色(=不改变背景)
                c.rgb = lerp(c.rgb, lerp(1.0, c.rgb, c.a), _Modulate);
                // 雾:Alpha 混合直接 MixFog;叠加模式应往黑色雾化 MixFogColor(c.rgb, 0, fog),否则雾里越叠越亮
                c.rgb = MixFog(c.rgb, i.extra.y);
                return c;
            }
            ENDHLSL
        }
    }
}