URP 渲染实战 / 第 24 章 Shader Graph / 案例 24.3
案例 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)。