GameTech/URP 渲染实战
源码 / shaders/Ch24/CustomLighting.glsl
shaders/Ch24/CustomLighting.glsl
// 第 24 章:给 Shader Graph 的 Custom Function 节点(Type = File)用的光照函数(GLSL 版,对应 CustomLighting.hlsl)。
// 规则回顾:
//   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(vec3 WorldPos, out vec3 Direction, out vec3 Color,
                     out float DistanceAtten, out float ShadowAtten)
{
#if defined(SHADERGRAPH_PREVIEW)
    // 预览球:给一个固定的斜上方光,不算阴影
    Direction = normalize(vec3(0.5, 0.5, -0.3));
    Color = vec3(1, 1, 1);
    DistanceAtten = 1.0;
    ShadowAtten = 1.0;
#else
    // 在片元阶段逐像素求阴影坐标:同时兼容无级联、级联和屏幕空间阴影三种关键字
    vec4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    // 带 positionWS 的重载会顺带做“阴影距离淡出”,与 URP Lit 行为一致
    Light light = GetMainLight(shadowCoord, WorldPos, vec4(1, 1, 1, 1));
    Direction = light.direction;
    Color = light.color;
    DistanceAtten = light.distanceAttenuation;
    ShadowAtten = light.shadowAttenuation;
#endif
}

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

#endif