GameTech/URP 渲染实战
源码 / Ch10/LitBreakdown.shader
Ch10/LitBreakdown.shader
// 第 10 章:把 URP Lit 的光照拆开看。
// 按 UniversalFragmentPBR 的流程逐项重算,但把四个分量分开累加:
//   直接漫反射 / 直接高光 / 间接漫反射(SH)/ 间接高光(反射探针),
// 用 _Component 选择输出哪一项。Full 模式直接调用 URP 原函数,Sum 模式输出四项之和,两者应当一致。
Shader "Cookbook/Ch10/LitBreakdown"
{
    Properties
    {
        [Enum(Full_URP,0,DirectDiffuse,1,DirectSpecular,2,IndirectDiffuse,3,IndirectSpecular,4,Sum,5,MainShadow,6)]
        _Component("Component", Float) = 0
        [MainTexture] _BaseMap("Base Map", 2D) = "white" {}
        [MainColor] _BaseColor("Base Color", Color) = (1, 1, 1, 1)
        _Metallic("Metallic", Range(0, 1)) = 0
        _Smoothness("Smoothness", Range(0, 1)) = 0.5
        [Normal][NoScaleOffset] _BumpMap("Normal Map", 2D) = "bump" {}
        _BumpScale("Normal Scale", Float) = 1
        [HDR] _EmissionColor("Emission", Color) = (0, 0, 0, 1)
        [HideInInspector] _Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5
    }

    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" "Queue"="Geometry" }

        HLSLINCLUDE
        // 写在 HLSLINCLUDE 里的内容会拼到本 SubShader 每个 Pass 的最前面,三个 Pass 的 CBUFFER 自然一致
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
        CBUFFER_START(UnityPerMaterial)
            float4 _BaseMap_ST;
            half4  _BaseColor;
            half4  _EmissionColor;
            half   _Cutoff;
            half   _Metallic;
            half   _Smoothness;
            half   _BumpScale;
            half   _Component;
        CBUFFER_END
        ENDHLSL

        Pass
        {
            Name "ForwardLit"
            Tags { "LightMode"="UniversalForward" }

            HLSLPROGRAM
            #pragma target 2.0
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature_local _NORMALMAP
            #pragma multi_compile _ _MAIN_LIGHT_SHADOWS _MAIN_LIGHT_SHADOWS_CASCADE
            #pragma multi_compile _ _ADDITIONAL_LIGHTS
            #pragma multi_compile_fragment _ _ADDITIONAL_LIGHT_SHADOWS
            #pragma multi_compile_fragment _ _SHADOWS_SOFT _SHADOWS_SOFT_LOW _SHADOWS_SOFT_MEDIUM _SHADOWS_SOFT_HIGH
            #pragma multi_compile_fragment _ _REFLECTION_PROBE_BLENDING
            #pragma multi_compile_fragment _ _REFLECTION_PROBE_BOX_PROJECTION
            #pragma multi_compile_fragment _ _SCREEN_SPACE_OCCLUSION
            #pragma multi_compile _ _FORWARD_PLUS
            #pragma multi_compile_instancing

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            struct Attributes
            {
                float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
            struct Varyings
            {
                float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 positionWS : TEXCOORD1;
                half3 normalWS : TEXCOORD2; half4 tangentWS : TEXCOORD3;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            Varyings vert(Attributes v)
            {
                Varyings o = (Varyings)0;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                VertexPositionInputs pos = GetVertexPositionInputs(v.positionOS.xyz);
                VertexNormalInputs nrm = GetVertexNormalInputs(v.normalOS, v.tangentOS);
                o.positionCS = pos.positionCS; o.positionWS = pos.positionWS;
                o.normalWS = nrm.normalWS;
                o.tangentWS = half4(nrm.tangentWS, v.tangentOS.w * GetOddNegativeScale());
                o.uv = TRANSFORM_TEX(v.uv, _BaseMap);
                return o;
            }

            void BuildData(Varyings i, out SurfaceData s, out InputData d)
            {
                s = (SurfaceData)0;
                s.albedo = SampleAlbedoAlpha(i.uv, TEXTURE2D_ARGS(_BaseMap, sampler_BaseMap)).rgb * _BaseColor.rgb;
                s.alpha = 1; s.metallic = _Metallic; s.smoothness = _Smoothness; s.occlusion = 1;
                s.normalTS = SampleNormal(i.uv, TEXTURE2D_ARGS(_BumpMap, sampler_BumpMap), _BumpScale);
                s.emission = _EmissionColor.rgb;

                d = (InputData)0;
                d.positionWS = i.positionWS; d.positionCS = i.positionCS;
            #if defined(_NORMALMAP)
                float3 bitangent = i.tangentWS.w * cross(i.normalWS, i.tangentWS.xyz);
                d.normalWS = TransformTangentToWorld(s.normalTS, half3x3(i.tangentWS.xyz, bitangent, i.normalWS));
            #else
                d.normalWS = i.normalWS;
            #endif
                d.normalWS = NormalizeNormalPerPixel(d.normalWS);
                d.viewDirectionWS = GetWorldSpaceNormalizeViewDir(i.positionWS);
                d.shadowCoord = TransformWorldToShadowCoord(i.positionWS);
                d.bakedGI = SampleSH(d.normalWS);
                d.normalizedScreenSpaceUV = GetNormalizedScreenSpaceUV(i.positionCS);
                d.shadowMask = half4(1, 1, 1, 1);
            }

            // 与 LightingPhysicallyBased 相同的计算,只是把漫反射和高光分开累加
            void AccumulateDirect(BRDFData brdf, Light light, InputData inputData, inout half3 diffuse, inout half3 specular)
            {
                half NdotL = saturate(dot(inputData.normalWS, light.direction));
                half3 radiance = light.color * (light.distanceAttenuation * light.shadowAttenuation * NdotL);
                diffuse  += brdf.diffuse * radiance;
                specular += brdf.specular * DirectBRDFSpecular(brdf, inputData.normalWS, light.direction, inputData.viewDirectionWS) * radiance;
            }

            half4 frag(Varyings i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i);
                SurfaceData surfaceData; InputData inputData;
                BuildData(i, surfaceData, inputData);

                if (_Component < 0.5)
                    return UniversalFragmentPBR(inputData, surfaceData);   // 原版,作为对照

                BRDFData brdf;
                InitializeBRDFData(surfaceData, brdf);
                half4 shadowMask = CalculateShadowMask(inputData);
                AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData);
                Light mainLight = GetMainLight(inputData, shadowMask, aoFactor);
                MixRealtimeAndBakedGI(mainLight, inputData.normalWS, inputData.bakedGI);

                // ---- 间接光:与 GlobalIllumination() 相同,只是拆成两项 ----
                half3 reflectVector = reflect(-inputData.viewDirectionWS, inputData.normalWS);
                half NoV = saturate(dot(inputData.normalWS, inputData.viewDirectionWS));
                half fresnelTerm = Pow4(1.0 - NoV);
                half3 envSpecular = GlossyEnvironmentReflection(reflectVector, inputData.positionWS,
                                        brdf.perceptualRoughness, 1.0h, inputData.normalizedScreenSpaceUV);
                half3 indirectDiffuse  = inputData.bakedGI * brdf.diffuse * aoFactor.indirectAmbientOcclusion;
                half3 indirectSpecular = envSpecular * EnvironmentBRDFSpecular(brdf, fresnelTerm) * aoFactor.indirectAmbientOcclusion;

                // ---- 直接光:主光 + 附加光 ----
                half3 directDiffuse = 0, directSpecular = 0;
                AccumulateDirect(brdf, mainLight, inputData, directDiffuse, directSpecular);

            #if defined(_ADDITIONAL_LIGHTS)
                uint pixelLightCount = GetAdditionalLightsCount();
            #if USE_FORWARD_PLUS
                // Forward+ 下,附加的“方向光”不进分簇列表,要单独循环
                for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
                {
                    Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
                    AccumulateDirect(brdf, light, inputData, directDiffuse, directSpecular);
                }
            #endif
                // 注意:Forward+ 版本的 LIGHT_LOOP_BEGIN 宏内部直接引用名为 inputData 的变量
                LIGHT_LOOP_BEGIN(pixelLightCount)
                    Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
                    AccumulateDirect(brdf, light, inputData, directDiffuse, directSpecular);
                LIGHT_LOOP_END
            #endif

                half3 c;
                if      (_Component < 1.5) c = directDiffuse;
                else if (_Component < 2.5) c = directSpecular;
                else if (_Component < 3.5) c = indirectDiffuse;
                else if (_Component < 4.5) c = indirectSpecular;
                else if (_Component < 5.5) c = directDiffuse + directSpecular + indirectDiffuse + indirectSpecular + surfaceData.emission;
                else                       c = mainLight.shadowAttenuation.xxx;
                return half4(c, 1);
            }
            ENDHLSL
        }

        Pass
        {
            Name "ShadowCaster"
            Tags { "LightMode"="ShadowCaster" }
            ZWrite On ZTest LEqual ColorMask 0
            HLSLPROGRAM
            #pragma vertex ShadowPassVertex
            #pragma fragment ShadowPassFragment
            #pragma multi_compile_instancing
            #pragma multi_compile_vertex _ _CASTING_PUNCTUAL_LIGHT_SHADOW
            #include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
            ENDHLSL
        }

        Pass
        {
            Name "DepthOnly"
            Tags { "LightMode"="DepthOnly" }
            ZWrite On ColorMask R
            HLSLPROGRAM
            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment
            #pragma multi_compile_instancing
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
    }
}