在URP中Input.hlsl中定义了主光的全局变量,C#管线这边需要每帧将主光的位置和颜色传递到Shader中。
float4 _MainLightPosition;
half4 _MainLightColor;
如下图所示,在FrameDebugger中可以看到,这里显示的数值是localToWorldMatrix后的结果,做了归一化它和面板中显示主光的世界坐标是不同的。
美术同学可能会添加多盏直光, 比如一盏照场景,另一盏照角色,通过层来控制光照影响的层。显然最好是角色使用角色的主光,场景使用场景的主光。然而URP并不是,它会从多盏直光找找一盏来当主光,这盏光有可能是照场景的,也有可能是照角色的。
UniversalRenderPipeline.cs的 GetMainLightIndex方法中
Light sunLight = RenderSettings.sun;
int brightestDirectionalLightIndex = -1;
float brightestLightIntensity = 0.0f;
for (int i = 0; i < totalVisibleLights; ++i)
{
VisibleLight currVisibleLight = visibleLights[i];
Light currLight = currVisibleLight.light;
// Particle system lights have the light property as null. We sort lights so all particles lights
// come last. Therefore, if first light is particle light then all lights are particle lights.
// In this case we either have no main light or already found it.
if (currLight == null)
break;
if (currVisibleLight.lightType == LightType.Directional)
{
// Sun source needs be a directional light
if (currLight == sunLight)
return i;
// In case no sun light is present we will return the brightest directional light
if (currLight.intensity > brightestLightIntensity)
{
brightestLightIntensity = currLight.intensity;
brightestDirectionalLightIndex = i;
}
}
}
上述代码中可以看到,URP会首先使用RenderSettings.sun太阳光,Lighting天空盒面板中设置的对象。如果没有的话,它会找灯光强度最强的一盏当主光。现在场景中光的结构是这样的。
Hierarchy
Directional Light 1(照场景) 灯光强度 2
Directional Light 2(照角色) 灯光强度 1
此时由于照角色的光强小于场景的光强,所以主光使用Directional Light 1。Directional Light 2则以点光的形式传入计算,当角色渲染时先用主光计算,显然不是美术想要的结果,只是因为后面在多点光的计算中又算了一遍Directional Light 2显示上才不会有太大问题。
多点光的定义是什么呢? 只要不是主光,并且处于激活状态下的光都属于多点光,也就是说将这些光进行计算把结果加上去。
#ifdef _ADDITIONAL_LIGHTS
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask);
#if defined(_SCREEN_SPACE_OCCLUSION)
light.color *= aoFactor.directAmbientOcclusion;
#endif
color += LightingPhysicallyBased(brdfData, brdfDataClearCoat,
light,
inputData.normalWS, inputData.viewDirectionWS,
surfaceData.clearCoatMask, specularHighlightsOff);
}
#endif
如下图所示,在FrameDebug中可以看到一共会把所有多点光保存在256数组中传入。

这里的光包含了所有的非主光源以外的所有光,全都计算显然是错误的,因为光上是可以添加过滤层的,还有灯光衰减等因素。Shader这边就需要知道哪些多点光是对自身有用的才需要进行计算。全局变量256数组显然没办法记录,要给每个材质单独记录。
UnityInput.hlsl中CBUFFER中记录了
real4 unity_LightData;
real4 unity_LightIndices[2];
其中 unity_LightData.y记录了影响当前材质的光源数量,unity_LightIndeices[2]则记录每个索引对应的具体光源index,然后就可以在全局变量256长度的数组中找对具体那盏光对它起了影响。这两个变量的赋值目前SRP并没有开出接口,则是在引擎层做的。
如下代码所示,min(_AdditionalLightsCount.x=4或8,unity_LightData.y是影响物体的点光数量) 最终影响物体的点光数量在4或8以内。
int GetAdditionalLightsCount()
{
// TODO: we need to expose in SRP api an ability for the pipeline cap the amount of lights
// in the culling. This way we could do the loop branch with an uniform
// This would be helpful to support baking exceeding lights in SH as well
return min(_AdditionalLightsCount.x, unity_LightData.y);
}
有了数量以后就可以通过Index找光源对应_AdditionalLightsPosition[256] 数组中的索引。
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask);
就是这里
int GetPerObjectLightIndex(uint index)
{
/////////////////////////////////////////////////////////////////////////////////////////////
// Structured Buffer Path /
// /
// Lights and light indices are stored in StructuredBuffer. We can just index them. /
// Currently all non-mobile platforms take this path :( /
// There are limitation in mobile GPUs to use SSBO (performance / no vertex shader support) /
/////////////////////////////////////////////////////////////////////////////////////////////
#if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
uint offset = unity_LightData.x;
return _AdditionalLightsIndices[offset + index];
/////////////////////////////////////////////////////////////////////////////////////////////
// UBO path /
// /
// We store 8 light indices in float4 unity_LightIndices[2]; /
// Due to memory alignment unity doesn't support int[] or float[] /
// Even trying to reinterpret cast the unity_LightIndices to float[] won't work /
// it will cast to float4[] and create extra register pressure. :( /
/////////////////////////////////////////////////////////////////////////////////////////////
#elif !defined(SHADER_API_GLES)
// since index is uint shader compiler will implement
// div & mod as bitfield ops (shift and mask).
// TODO: Can we index a float4? Currently compiler is
// replacing unity_LightIndicesX[i] with a dp4 with identity matrix.
// u_xlat16_40 = dot(unity_LightIndices[int(u_xlatu13)], ImmCB_0_0_0[u_xlati1]);
// This increases both arithmetic and register pressure.
return unity_LightIndices[index / 4][index % 4];
#else
// Fallback to GLES2. No bitfield magic here :(.
// We limit to 4 indices per object and only sample unity_4LightIndices0.
// Conditional moves are branch free even on mali-400
// small arithmetic cost but no extra register pressure from ImmCB_0_0_0 matrix.
half2 lightIndex2 = (index < 2.0h) ? unity_LightIndices[0].xy : unity_LightIndices[0].zw;
half i_rem = (index < 2.0h) ? index : index - 2.0h;
return (i_rem < 1.0h) ? lightIndex2.x : lightIndex2.y;
#endif
}
综上所述,主光的计算会让美术产生困惑,点光的计算则不会。 这段代码读懂后其实也可以自定义一组针对角色或场景的特殊光源,这样需要修改c#的管线代码,可能美术就不会在困惑了。
- 本文固定链接: https://www.xuanyusong.com/archives/4992
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表
