GameTech/URP 渲染实战
源码 / shaders/Ch21/Particle.glsl
shaders/Ch21/Particle.glsl
// 案例 21.5–21.7:通用粒子 Shader——顶点色、混合模式、Custom Vertex Streams 帧间混合、软粒子、近相机淡出、渐变映射
// (GLSL 版,对应 Ch21/Particle.shader)
Shader "Cookbook/Ch21/Particle"
{
    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
            GLSLPROGRAM
            #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
            // DeclareDepthTexture.hlsl 的 SampleSceneDepth 在引擎 Core.glsl 里

            uniform vec4 _BaseMap_ST;
            uniform vec4 _BaseColor;
            uniform float _Premultiply, _Modulate;
            uniform float _SoftDistance, _CameraFadeNear, _CameraFadeFar;
            uniform float _RampScale;
            uniform sampler2D _BaseMap;
            uniform sampler2D _RampTex;

            // 与粒子系统 Renderer › Custom Vertex Streams 的顺序一一对应:
            //   Position(POSITION) · Color(COLOR) · UV(TEXCOORD0.xy) · UV2(TEXCOORD0.zw) · AnimBlend(TEXCOORD1.x)
            // GLSL 版的顶点输入名字固定:positionOS、color、texcoord0(xy = 当前帧 UV,zw = 下一帧 UV)、texcoord1(x = 两帧之间的进度 0..1)

            VARYING vec4 vColor;
            VARYING vec4 uv;           // xy 当前帧,zw 下一帧
            VARYING vec4 screenData;   // xyw = positionNDC,z = 自身观察空间深度
            VARYING vec2 extra;        // x = 帧混合系数,y = 雾

            #ifdef VERTEX
            void main()
            {
                VertexPositionInputs p = GetVertexPositionInputs(positionOS.xyz);
                gl_Position = p.positionCS;
                vColor = color;
                extra = vec2(0.0);
            #if defined(_FLIPBOOKBLENDING_ON)
                uv = vec4(TransformTex(texcoord0.xy, _BaseMap_ST), TransformTex(texcoord0.zw, _BaseMap_ST));
                extra.x = texcoord1.x;
            #else
                uv = TransformTex(texcoord0.xy, _BaseMap_ST).xyxy;
            #endif
                screenData = vec4(p.positionNDC.xy, -p.positionVS.z, p.positionNDC.w);
                extra.y = ComputeFogFactor(p.positionCS.z);
            }
            #endif

            #ifdef FRAGMENT
            vec4 frag()
            {
                vec4 tex = texture(_BaseMap, uv.xy);
            #if defined(_FLIPBOOKBLENDING_ON)
                vec4 tex2 = texture(_BaseMap, uv.zw);
                tex = mix(tex, tex2, extra.x);
            #endif

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

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

                float thisZ = screenData.z;
            #if defined(_SOFTPARTICLES_ON)
                // 软粒子:比较场景深度与粒子自身深度,越接近(快要插进物体)越透明
                vec2 suv = screenData.xy / 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 *= mix(1.0, c.a, _Premultiply);   // 预乘:Blend One OneMinusSrcAlpha 时需要
                // 正片叠底(Blend DstColor Zero)没有 alpha 参与:把 alpha 折算进颜色,透明处趋向白色(=不改变背景)
                c.rgb = mix(c.rgb, mix(vec3(1.0), c.rgb, c.a), _Modulate);
                // 雾:Alpha 混合直接 MixFog;叠加模式应往黑色雾化 MixFogColor(c.rgb, 0, fog),否则雾里越叠越亮
                c.rgb = MixFog(c.rgb, extra.y);
                return c;
            }
            void main() { fragColor = frag(); }
            #endif
            ENDGLSL
        }
    }
}