在URP中Input.hlsl中定义了主光的全局变量,C#管线这边需要每帧将主光的位置和颜色传递到Shader中。
1 2 3 4 |
float4 _MainLightPosition; half4 _MainLightColor; |
如下图所示,在FrameDebugger中可以看到,这里显示的数值是localToWorldMatrix后的结果,做了归一化它和面板中显示主光的世界坐标是不同的。
美术同学可能会添加多盏直光, 比如一盏照场景,另一盏照角色,通过层来控制光照影响的层。显然最好是角色使用角色的主光,场景使用场景的主光。然而URP并不是,它会从多盏直光找找一盏来当主光,这盏光有可能是照场景的,也有可能是照角色的。
UniversalRenderPipeline.cs的 GetMainLightIndex方法中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
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显示上才不会有太大问题。
多点光的定义是什么呢? 只要不是主光,并且处于激活状态下的光都属于多点光,也就是说将这些光进行计算把结果加上去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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中记录了
1 2 3 4 |
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以内。
1 2 3 4 5 6 7 8 9 |
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] 数组中的索引。
1 2 3 4 5 6 |
uint pixelLightCount = GetAdditionalLightsCount(); for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex) { Light light = GetAdditionalLight(lightIndex, inputData.positionWS, shadowMask); |
就是这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
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程序研究院 发表