GameTech/URP 渲染实战
源码 / Ch24/CustomLighting.hlsl
Ch24/CustomLighting.hlsl
// 第 24 章:给 Shader Graph 的 Custom Function 节点(Type = File)用的光照函数。
// 规则回顾:
//   1) 节点里填的 Name 是 "MainLight",Shader Graph 会按节点精度调用 MainLight_float 或 MainLight_half,
//      所以两个后缀的版本最好都提供;
//   2) 所有输出都用 out 参数,函数返回 void;参数顺序 = 节点上 Inputs 在前、Outputs 在后,名字随意但顺序必须一致;
//   3) 节点预览(SHADERGRAPH_PREVIEW)由 Shader Graph 自己生成,不包含 URP 的 Lighting.hlsl,
//      GetMainLight 等函数在那里根本不存在,必须用 #if 给预览一个替身。
#ifndef COOKBOOK_CH24_CUSTOM_LIGHTING_INCLUDED
#define COOKBOOK_CH24_CUSTOM_LIGHTING_INCLUDED

void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color,
                     out float DistanceAtten, out float ShadowAtten)
{
#if defined(SHADERGRAPH_PREVIEW)
    // 预览球:给一个固定的斜上方光,不算阴影
    Direction = normalize(float3(0.5, 0.5, -0.3));
    Color = float3(1, 1, 1);
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
    // 在片元阶段逐像素求阴影坐标:同时兼容无级联、级联和屏幕空间阴影三种关键字
    float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    // 带 positionWS 的重载会顺带做“阴影距离淡出”,与 URP Lit 行为一致
    Light light = GetMainLight(shadowCoord, WorldPos, half4(1, 1, 1, 1));
    Direction = light.direction;
    Color = light.color;
    DistanceAtten = light.distanceAttenuation;
    ShadowAtten = light.shadowAttenuation;
#endif
}

void MainLight_half(half3 WorldPos, out half3 Direction, out half3 Color,
                    out half DistanceAtten, out half ShadowAtten)
{
    // half 版本直接转调 float 版本。注意:世界坐标一旦以 half 传进来就已经丢了精度(离原点远时阴影会抖),
    // 所以实际项目里建议把这个节点的 Precision 设成 Single,让 Shader Graph 调用 _float 版本
    float3 d, c; float da, sa;
    MainLight_float(WorldPos, d, c, da, sa);
    Direction = d; Color = c; DistanceAtten = da; ShadowAtten = sa;
}

// 所有附加光(点光、聚光)的 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;
}

#endif