GameTech/URP 渲染实战
源码 / Ch29/BatcherFriendly.shader
Ch29/BatcherFriendly.shader
// 案例 29.1:一个“对合批友好”的最小 Shader:
//  · 所有材质属性都在 UnityPerMaterial CBUFFER 里 → SRP Batcher 兼容
//  · 支持 GPU Instancing(multi_compile_instancing),并把颜色做成可逐实例覆盖的属性
//  · 支持 LOD Cross-Fade(与 URP Lit 相同的关键字与函数)
Shader "Cookbook/Ch29/BatcherFriendly"
{
    Properties
    {
        [MainTexture] _BaseMap ("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor ("Color", Color) = (1, 1, 1, 1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }
        Pass
        {
            Name "Forward"
            Tags { "LightMode"="UniversalForward" }
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma multi_compile_fragment _ LOD_FADE_CROSSFADE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #if defined(LOD_FADE_CROSSFADE)
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/LODCrossFade.hlsl"
            #endif

            TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap);
            // SRP Batcher:每个材质一份常量缓冲,布局在所有变体里必须一致
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                half4 _BaseColor;
            CBUFFER_END

            // GPU Instancing:允许用 MaterialPropertyBlock 逐实例覆盖颜色
            UNITY_INSTANCING_BUFFER_START(PerInstance)
                UNITY_DEFINE_INSTANCED_PROP(half4, _InstanceColor)
            UNITY_INSTANCING_BUFFER_END(PerInstance)

            struct Attributes
            {
                float4 positionOS : POSITION;
                float3 normalOS : NORMAL;
                float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct Varyings
            {
                float4 positionCS : SV_POSITION;
                float2 uv : TEXCOORD0;
                half3 normalWS : TEXCOORD1;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            Varyings vert(Attributes input)
            {
                Varyings o;
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, o);
                o.positionCS = TransformObjectToHClip(input.positionOS.xyz);
                o.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                o.normalWS = TransformObjectToWorldNormal(input.normalOS);
                return o;
            }

            half4 frag(Varyings i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
            #if defined(LOD_FADE_CROSSFADE)
                LODFadeCrossFade(i.positionCS);   // 抖动裁剪,两级 LOD 交叉淡入淡出
            #endif
                half4 tint = UNITY_ACCESS_INSTANCED_PROP(PerInstance, _InstanceColor);
                half3 albedo = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, i.uv).rgb * _BaseColor.rgb;
                albedo *= any(tint.rgb) ? tint.rgb : 1;     // 没设置逐实例颜色时为 0,退回材质颜色
                Light light = GetMainLight();
                half ndl = saturate(dot(normalize(i.normalWS), light.direction));
                half3 color = albedo * (light.color * ndl + SampleSH(normalize(i.normalWS)));
                return half4(color, 1);
            }
            ENDHLSL
        }
    }
}