GameTech/URP 渲染实战
源码 / Ch11/MyPBR_Lighting.hlsl
Ch11/MyPBR_Lighting.hlsl
// MyPBR 的光照组装:直接光(主光 + 附加光,Forward / Forward+)+ 间接光(SH 漫反射 + IBL 镜面)。
// 从 URP 只拿“数据”:Light 结构体、阴影衰减、SH 系数、反射探针取样、SSAO;BRDF 全部来自 MyPBR_BRDF.hlsl。
#ifndef COOKBOOK_MYPBR_LIGHTING_INCLUDED
#define COOKBOOK_MYPBR_LIGHTING_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#include "Assets/Cookbook/Ch11/MyPBR_BRDF.hlsl"

// 分项结果:调试视图会单独显示每一项
struct MyLightingTerms
{
    float3 directDiffuse;
    float3 directSpecular;
    float3 indirectDiffuse;
    float3 indirectSpecular;
    float3 emission;
};

// 一盏灯对出射辐射率的贡献:Lo = f(l,v) * E⊥ * NoL
// Unity 的光源约定:light.color 是“白色 Lambert 表面正对灯时看到的亮度”,
// 即 Lo = albedo/π * E⊥  =>  E⊥ = π * light.color。所以这里乘 π,与 BRDF 里的 1/π 互相抵消。
void MyAccumulateLight(MyMaterial m, float3 N, float3 V, Light light, inout MyLightingTerms t)
{
    float3 fd, fs; float NoL;
    MyDirectBRDF(m, N, V, light.direction, fd, fs, NoL);
    float3 E = PI * light.color * (light.distanceAttenuation * light.shadowAttenuation) * NoL;
    t.directDiffuse  += fd * E;
    t.directSpecular += fs * E;
}

// 取预滤波环境图(split-sum 的第一项)
float3 MySamplePrefilteredEnv(float3 R, float perceptualRoughness, float3 positionWS, float2 screenUV)
{
#if USE_FORWARD_PLUS || defined(_REFLECTION_PROBE_BLENDING)
    // Forward+ 把所有反射探针打包进一张八面体图集,天空反射走 _GlossyEnvironmentCubeMap;
    // 探针混合也有一套权重逻辑。这些都是“取样”,与 BRDF 无关,直接复用 URP 的取样函数
    // (它内部同样用感知粗糙度选 mip,映射公式与 MyRoughnessToMip 相同)。
    return GlossyEnvironmentReflection(R, positionWS, perceptualRoughness, 1.0h, screenUV);
#else
    #if defined(_REFLECTION_PROBE_BOX_PROJECTION)
    R = BoxProjectedCubemapDirection(R, positionWS, unity_SpecCube0_ProbePosition,
                                     unity_SpecCube0_BoxMin, unity_SpecCube0_BoxMax);
    #endif
    float mip = MyRoughnessToMip(perceptualRoughness);
    half4 encoded = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, R, mip);
    return DecodeHDREnvironment(encoded, unity_SpecCube0_HDR);   // 解 RGBM / HDR 编码
#endif
}

MyLightingTerms MyComputeLighting(MySurface s, inout MyMaterial m, InputData inputData)
{
    MyLightingTerms t = (MyLightingTerms)0;
    float3 N = inputData.normalWS;
    float3 V = inputData.viewDirectionWS;
    float NoV = saturate(dot(N, V)) + 1e-5;

    // ---- 环境 BRDF 与能量补偿:只和 NoV、粗糙度有关,先算好给直接光也用 ----
    float2 dfg = MyEnvDFG(NoV, m.perceptualRoughness);
    m.energyCompensation = lerp(1.0, MyEnergyCompensation(dfg, m.f0), _EnergyCompensation);

    // ---- 遮蔽:材质 AO 与 SSAO 取最小值(URP 的约定)----
    AmbientOcclusionFactor ao = CreateAmbientOcclusionFactor(inputData.normalizedScreenSpaceUV, s.occlusion);
    half4 shadowMask = CalculateShadowMask(inputData);
    uint meshLayers = GetMeshRenderingLayer();

    // ---- 主光 ----
    Light mainLight = GetMainLight(inputData, shadowMask, ao);   // 已含阴影、Light Cookie、SSAO 直接光部分
    MixRealtimeAndBakedGI(mainLight, N, inputData.bakedGI);      // Subtractive 光照贴图模式下扣掉主光
#if defined(_LIGHT_LAYERS)
    if (IsMatchingLightLayer(mainLight.layerMask, meshLayers))
#endif
        MyAccumulateLight(m, N, V, mainLight, t);

    // ---- 附加光:Forward 逐物体列表 / Forward+ 分簇列表 ----
#if defined(_ADDITIONAL_LIGHTS)
    uint lightCount = GetAdditionalLightsCount();

    #if USE_FORWARD_PLUS
    // Forward+ 下,除主光外的方向光不进分簇,排在附加光数组最前面,要单独循环
    for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
    {
        FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
        Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, ao);
        #if defined(_LIGHT_LAYERS)
        if (IsMatchingLightLayer(light.layerMask, meshLayers))
        #endif
            MyAccumulateLight(m, N, V, light, t);
    }
    #endif

    // LIGHT_LOOP_BEGIN 在 Forward 下展开成 for 循环,在 Forward+ 下展开成分簇迭代器,
    // 宏内部会用到名为 inputData 的变量(normalizedScreenSpaceUV、positionWS)
    LIGHT_LOOP_BEGIN(lightCount)
        Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, ao);
        #if defined(_LIGHT_LAYERS)
        if (IsMatchingLightLayer(light.layerMask, meshLayers))
        #endif
            MyAccumulateLight(m, N, V, light, t);
    LIGHT_LOOP_END
#endif

#if defined(_ADDITIONAL_LIGHTS_VERTEX)
    // 逐顶点附加光只有漫反射:顶点里累加的是 color * atten * NoL,等价于 E/π
    t.directDiffuse += inputData.vertexLighting * m.diffuseColor;
#endif

    // ---- 间接漫反射:SH(或光照贴图)----
    // Unity 的 SH / 光照贴图里存的是 E/π(“已经除过 π 的辐照度”),直接乘漫反射色即可
    t.indirectDiffuse = inputData.bakedGI * m.diffuseColor * ao.indirectAmbientOcclusion;

    // ---- 间接镜面:split-sum = 预滤波环境 × 环境 BRDF ----
    float3 R = reflect(-V, N);
    float3 prefiltered = MySamplePrefilteredEnv(R, m.perceptualRoughness, inputData.positionWS, inputData.normalizedScreenSpaceUV);
    float3 envBRDF = m.f0 * dfg.x + dfg.y;     // F90 = 1
    float specOcc = lerp(ao.indirectAmbientOcclusion,
                         MySpecularOcclusion(NoV, ao.indirectAmbientOcclusion, m.perceptualRoughness),
                         _SpecularOcclusion);
    t.indirectSpecular = prefiltered * envBRDF * m.energyCompensation * specOcc;

    t.emission = s.emission;
    return t;
}

#endif