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

#include "Ch11/MyPBR_BRDF.glsl"

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

// URP 的 GetMainLight(inputData, shadowMask, aoFactor) / GetAdditionalLight(i, inputData, shadowMask, aoFactor):
// 取灯 + 阴影(含远处淡出)+ SSAO 的“直接光遮蔽”部分。网页版核心库没有这两个重载,在这里补上。
Light MyGetMainLight(InputData inputData, vec4 shadowMask, AmbientOcclusionFactor aoFactor)
{
    Light light = GetMainLight(inputData.shadowCoord, inputData.positionWS, shadowMask);
    light.color *= aoFactor.directAmbientOcclusion;
    return light;
}
Light MyGetAdditionalLight(int lightIndex, InputData inputData, vec4 shadowMask, AmbientOcclusionFactor aoFactor)
{
    Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask);
    light.color *= aoFactor.directAmbientOcclusion;
    return light;
}

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

// 取预滤波环境图(split-sum 的第一项)
// HLSL 版在 Forward+ / 探针混合时改用 URP 的 GlossyEnvironmentReflection(反射探针图集);
// 网页版只有一个反射立方体图 unity_SpecCube0,两条路径结果相同,这里只保留手写的那条。
vec3 MySamplePrefilteredEnv(vec3 R, float perceptualRoughness, vec3 positionWS, vec2 screenUV)
{
    float mip = MyRoughnessToMip(perceptualRoughness);
    vec4 encoded = textureLod(unity_SpecCube0, R, mip);
    return DecodeHDREnvironment(encoded, unity_SpecCube0_HDR);   // 解 RGBM / HDR 编码
}

MyLightingTerms MyComputeLighting(MySurface s, inout MyMaterial m, InputData inputData)
{
    MyLightingTerms t;
    t.directDiffuse = vec3(0.0); t.directSpecular = vec3(0.0);
    t.indirectDiffuse = vec3(0.0); t.indirectSpecular = vec3(0.0); t.emission = vec3(0.0);
    vec3 N = inputData.normalWS;
    vec3 V = inputData.viewDirectionWS;
    float NoV = saturate(dot(N, V)) + 1e-5;

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

    // ---- 遮蔽:材质 AO 与 SSAO 取最小值(URP 的约定)----
    AmbientOcclusionFactor ao = GetScreenSpaceAmbientOcclusion(inputData.normalizedScreenSpaceUV);
    ao.indirectAmbientOcclusion = min(ao.indirectAmbientOcclusion, s.occlusion);
    vec4 shadowMask = inputData.shadowMask;

    // ---- 主光 ----
    Light mainLight = MyGetMainLight(inputData, shadowMask, ao);   // 已含阴影、SSAO 直接光部分
    // (HLSL 版这里还有 MixRealtimeAndBakedGI 与 Light Layers 判断;网页版没有光照贴图和灯光层)
    MyAccumulateLight(m, N, V, mainLight, t);

    // ---- 附加光:Forward 逐物体列表 / Forward+ 全部可见光 ----
#if defined(_ADDITIONAL_LIGHTS)
    // HLSL 版用 LIGHT_LOOP_BEGIN/END(Forward+ 下展开成分簇迭代器);
    // 网页版的 GetAdditionalLightsCount() 已经按渲染路径返回本像素要算的灯数,普通 for 循环即可
    int lightCount = GetAdditionalLightsCount();
    for (int lightIndex = 0; lightIndex < lightCount; lightIndex++)
    {
        Light light = MyGetAdditionalLight(lightIndex, inputData, shadowMask, ao);
        MyAccumulateLight(m, N, V, light, t);
    }
#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 ----
    vec3 R = reflect(-V, N);
    vec3 prefiltered = MySamplePrefilteredEnv(R, m.perceptualRoughness, inputData.positionWS, inputData.normalizedScreenSpaceUV);
    vec3 envBRDF = m.f0 * dfg.x + dfg.y;     // F90 = 1
    float specOcc = mix(ao.indirectAmbientOcclusion,
                        MySpecularOcclusion(NoV, ao.indirectAmbientOcclusion, m.perceptualRoughness),
                        _SpecularOcclusion);
    t.indirectSpecular = prefiltered * envBRDF * m.energyCompensation * specOcc;

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

#endif