精選文章

SmallBurger Asset Home

  SmallBurger

2018年9月1日 星期六

試做自己的Unity SRP

目標:先以LightWeight SRP為參考,並在過程中更理解Unity繪圖流程的細節,最終設計出自己覺得適合的SRP。

架構流程如下:
Light List相關(Setup light constants pass):
從代碼得知,Culling list裡的Light list是目前camera view所看到的lights,經過最大燈光的
數量的限制及SetGlobalVetorArray
會造成物件燈光計算的錯誤,因為是view culling list,並不是Object List…
void SetupAdditionalLightConstants(CommandBuffer cmd, ref LightData lightData){
            List lights = lightData.visibleLights;
            if (lightData.totalAdditionalLightsCount > 0)
            {
                int localLightsCount = 0;
                for (int i = 0; i < lights.Count && localLightsCount < maxVisibleLocalLights; ++i)
                {
                    VisibleLight light = lights[i];
                    if (light.lightType != LightType.Directional)
                    {
                        InitializeLightConstants(lights, i, out m_LightPositions[localLightsCount],
                            out m_LightColors[localLightsCount],
                            out m_LightDistanceAttenuations[localLightsCount],
                            out m_LightSpotDirections[localLightsCount],
                            out m_LightSpotAttenuations[localLightsCount]);
                        localLightsCount++;
                    }
                }

                cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightCount, new Vector4(lightData.pixelAdditionalLightsCount,
                    lightData.totalAdditionalLightsCount, 0.0f, 0.0f));

                // if not using a compute buffer, engine will set indices in 2 vec4 constants
                // unity_4LightIndices0 and unity_4LightIndices1
                if (perObjectLightIndices != null)
                    cmd.SetGlobalBuffer("_LightIndexBuffer", perObjectLightIndices);
            }
            else
            {
                cmd.SetGlobalVector(LightConstantBuffer._AdditionalLightCount, Vector4.zero);
            }
            cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightPosition, m_LightPositions);
            cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightColor, m_LightColors);
            cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightDistanceAttenuation, m_LightDistanceAttenuations);
            cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightSpotDir, m_LightSpotDirections);
            cmd.SetGlobalVectorArray(LightConstantBuffer._AdditionalLightSpotAttenuation, m_LightSpotAttenuations);
        }

Object light list的處理:原來Unity底層有支援RendererConfiguration.PerObjectLightIndices8,
可以用來解決這個問題,如果有支援StructuredBuffer的話,可以走另一個流程
RendererConfiguration.ProvideLightIndices,相關代碼如下:
//這裡主要是在處理方向光需要從project light indices中移掉,交由MainLight接手。
public void SetupPerObjectLightIndices(ref CullResults cullResults, ref LightData lightData)
{
...
}


public static RendererConfiguration GetRendererConfiguration(int localLightsCount){
            RendererConfiguration configuration = RendererConfiguration.PerObjectReflectionProbes | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbe;
            if (localLightsCount > 0)
            {
                if (useComputeBufferForPerObjectLightIndices)
                    configuration |= RendererConfiguration.ProvideLightIndices;
                else
                    configuration |= RendererConfiguration.PerObjectLightIndices8;
            }

            return configuration;
        }

相關Shader如下:會利用GetLight這個函式來取得正確的Light index。
Light GetLight(half i, float3 positionWS)
{
    LightInput lightInput;

#if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA
    int lightIndex = _LightIndexBuffer[unity_LightIndicesOffsetAndCount.x + i];
#else
    // The following code is more optimal than indexing unity_4LightIndices0.
    // Conditional moves are branch free even on mali-400
    half i_rem = (i < 2.0h) ? i : i - 2.0h;
    half2 lightIndex2 = (i < 2.0h) ? unity_4LightIndices0.xy : unity_4LightIndices0.zw;
    int lightIndex = (i_rem < 1.0h) ? lightIndex2.x : lightIndex2.y;
#endif

    // The following code will turn into a branching madhouse on platforms that don't support
    // dynamic indexing. Ideally we need to configure light data at a cluster of
    // objects granularity level. We will only be able to do that when scriptable culling kicks in.
    // TODO: Use StructuredBuffer on PC/Console and profile access speed on mobile that support it.
    lightInput.position = _AdditionalLightPosition[lightIndex];
    lightInput.color = _AdditionalLightColor[lightIndex].rgb;
    lightInput.distanceAttenuation = _AdditionalLightDistanceAttenuation[lightIndex];
    lightInput.spotDirection = _AdditionalLightSpotDir[lightIndex];
    lightInput.spotAttenuation = _AdditionalLightSpotAttenuation[lightIndex];

    half4 directionAndRealtimeAttenuation = GetLightDirectionAndAttenuation(lightInput, positionWS);

    Light light;
    light.index = lightIndex;
    light.direction = directionAndRealtimeAttenuation.xyz;
    light.attenuation = directionAndRealtimeAttenuation.w;
    light.subtractiveModeAttenuation = lightInput.distanceAttenuation.w;
    light.color = lightInput.color;

    return light;
}

切割Pass原因:
主要是為容易切割處理流程、共用程式碼、以及提供足夠的時機做相關後置特效處理。另外也利用了切pass的時機,copy了render過程的結果,以便於除錯及觀察繪製流程。

接下來要做的事:
  1. 影子pass處理,預計會採用跟Unity一樣的CSM shadow map全場景投影的做法
  2. PBR材質
  3. 支持forward plus rendering,目前Unity是將這部份放在HRDP,估計應該是因為
    compute shader light list的問題,如果最後不行的話,最差的狀況至少也可以先為
    Vulkan做準備。

除錯資料(左上開始的小圖),由左而右,是繪製流程的順序,依序為
after draw opaques, depth, after draw sky, after draw transparencies.
另外也跟LWSRP一樣支持render scale,簡單來說就是不直接Render到viewport上,而是先
畫到RenderTexture,最後再Final Blt到viewport上,好處是:
  1. 讓3D採用較低的解析度,可以大大減少GPU處理像素的數量,提升效能。
  2. 可以直接使用NativeDepthBuffer,不用增加DrawCall。
  3. 由GUI一般會使用point filter,而3D大都會採用linear filter,可以切開。


相關影片如下:




Dream continues in...

沒有留言:

張貼留言