首页 >Unity3D频道 >【Unity3D研究院之游戏开发】 > URP下让美术同学困惑的多主光计算
2022
08-28

URP下让美术同学困惑的多主光计算

在URP中Input.hlsl中定义了主光的全局变量,C#管线这边需要每帧将主光的位置和颜色传递到Shader中。

float4 _MainLightPosition;
half4 _MainLightColor;

如下图所示,在FrameDebugger中可以看到,这里显示的数值是localToWorldMatrix后的结果,做了归一化它和面板中显示主光的世界坐标是不同的。
URP下让美术同学困惑的多主光计算 - 雨松MOMO程序研究院 - 1

美术同学可能会添加多盏直光, 比如一盏照场景,另一盏照角色,通过层来控制光照影响的层。显然最好是角色使用角色的主光,场景使用场景的主光。然而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数组中传入。

URP下让美术同学困惑的多主光计算 - 雨松MOMO程序研究院 - 2
这里的光包含了所有的非主光源以外的所有光,全都计算显然是错误的,因为光上是可以添加过滤层的,还有灯光衰减等因素。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#的管线代码,可能美术就不会在困惑了。

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

留下一个回复

你的email不会被公开。 必填项已用 * 标注。