GameTech/URP 渲染实战

案例 24.3附加光:兼容 Forward 与 Forward+高阶

附加光需要遍历光源列表。Forward 路径下是普通的 for 循环;Forward+ 路径下要用 LIGHT_LOOP_BEGIN/END 遍历当前像素所在的光源簇,而这对宏要求作用域里有一个名为 inputData、填好 positionWS 和 normalizedScreenSpaceUV 的 InputData 变量(见 URP 14 RealtimeLights.hlsl 第 36 行的宏定义)。另外 Forward+ 把额外的方向光放在索引最前面,不参与分簇,要像 UniversalFragmentPBR 那样单独循环一次。

Assets/Cookbook/Ch24/CustomLighting.hlsl(节选:additional)
// 所有附加光(点光、聚光)的 Lambert 漫反射之和。
// ScreenUV 接 Screen Position 节点(Default 模式),Forward+ 的分簇查询需要它。
void AdditionalLightsLambert_float(float3 WorldPos, float3 WorldNormal, float2 ScreenUV, out float3 Diffuse)
{
    Diffuse = 0;
#if !defined(SHADERGRAPH_PREVIEW)
    // LIGHT_LOOP_BEGIN 在 Forward+ 下会读取名为 inputData 的变量,所以这里按约定造一个
    InputData inputData = (InputData)0;
    inputData.positionWS = WorldPos;
    inputData.normalizedScreenSpaceUV = ScreenUV;

    uint lightCount = 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, WorldPos, half4(1, 1, 1, 1));
        Diffuse += light.color * light.distanceAttenuation * light.shadowAttenuation * saturate(dot(WorldNormal, light.direction));
    }
    #endif
    LIGHT_LOOP_BEGIN(lightCount)
        Light light = GetAdditionalLight(lightIndex, WorldPos, half4(1, 1, 1, 1));
        Diffuse += light.color * light.distanceAttenuation * light.shadowAttenuation * saturate(dot(WorldNormal, light.direction));
    LIGHT_LOOP_END
#endif
}

void AdditionalLightsLambert_half(half3 WorldPos, half3 WorldNormal, half2 ScreenUV, out half3 Diffuse)
{
    float3 d;
    AdditionalLightsLambert_float(WorldPos, WorldNormal, ScreenUV, d);
    Diffuse = d;
}
// 所有附加光(点光、聚光)的 Lambert 漫反射之和。
// ScreenUV 接 Screen Position 节点(Default 模式),Forward+ 的分簇查询需要它(网页版不分簇,用不到)。
void AdditionalLightsLambert_float(vec3 WorldPos, vec3 WorldNormal, vec2 ScreenUV, out vec3 Diffuse)
{
    Diffuse = vec3(0.0);
#if !defined(SHADERGRAPH_PREVIEW)
    // HLSL 版在 Forward+ 下要单独循环方向光,并用 LIGHT_LOOP_BEGIN/END;网页版就是一个普通循环
    int lightCount = GetAdditionalLightsCount();
    for (int lightIndex = 0; lightIndex < lightCount; lightIndex++)
    {
        Light light = GetAdditionalLight(lightIndex, WorldPos, vec4(1, 1, 1, 1));
        Diffuse += light.color * light.distanceAttenuation * light.shadowAttenuation * saturate(dot(WorldNormal, light.direction));
    }
#endif
}

void AdditionalLightsLambert_half(vec3 WorldPos, vec3 WorldNormal, vec2 ScreenUV, out vec3 Diffuse)
{
    vec3 d;
    AdditionalLightsLambert_float(WorldPos, WorldNormal, ScreenUV, d);
    Diffuse = d;
}

节点上加一个 ScreenUV(Vector2)输入,接 Screen Position 节点(Mode = Default,输出就是 0~1 的归一化屏幕坐标)的 xy。等价 Shader 里对应的是 GetNormalizedScreenSpaceUV(positionCS)。