天天看點

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

當我們使用SRP(Scriptable Render Pipeline)之後,例如HDRP,URP或者LWRP,在SPR的asset檔案中有一個選項叫做SRP Batcher(如圖)。

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

這篇文章就讓我們來了解了解這個好東西。

作用

簡單的說,如果當你的場景中有很多的物體分别使用到了不同的Material,但是這些Material使用的Shader卻都是同一個時,SRP Batcher可以大大為我們降低DrawCall,進而加快渲染速度。

官方介紹:https://blogs.unity3d.com/2019/02/28/srp-batcher-speed-up-your-rendering/

https://docs.unity3d.com/Manual/SRPBatcher.html

中文文檔:https://connect.unity.com/p/srp-batcher-jia-su-xuan-ran

接下來我們可以通過一個簡單的示例來驗證這個效果。

示例

首先我們先通過Create->Shader->Unlit Shader建立一個簡單的無光照Shader命名為SampleUnlit。接着我們建立三個Material,分别命名為SampleMaterial1,SampleMaterial2,SampleMaterial3。這三個Material都使用我們剛剛建立好的SampleShader,并分别引用不同的Texture。

接着我們在場景中建立15個Cube,分别引用上面的這些Material,效果如圖

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

此時打開Game視圖的Statistics面闆,可以看見15個Cube一共産生了15個DrawCall(由于SkyBox會産生一個DrawCall是以顯示的有16個),而Saved By Batching為0。

我們通過Window->Analysis->Frame Debugger打開Frame Debug視窗,點選Enable按鈕,可以觀察到目前幀的繪制資訊

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

按往常我們可能會通過動态批處理或者靜态批處理來進行優化。

動态批處理:我們可以勾選SRP asset檔案的Dynamic Batching,勾選後可以發現,DrawCall數量變為12,Saved By Batching變為4。同意通過Frame Debug可以發現有些方塊的繪制被合并到了一起,進而減少了DrawCall。

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

靜态批處理:我們可以将這些小方塊全部變為靜态物體(勾選其Static屬性),運作後發現DrawCall同樣變少了(未運作狀态下不變),以及對應的Frame Debug視圖如下:

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

但是使用SRP後,這種情況我們就可以使用SRP Batcher來處理了,而且效果會更好。

SRP Batcher

首先我們勾選SRP asset的SRP Batcher選項,嗯哼,怎麼沒有任何變化,說好的會減少DrawCall呢?

通過官方的文檔,我們可以找到問題的所在,那就是Shader了。我們點選前面建立的SampleUnlit,會發現其有個SRP Batcher的屬性,顯示着not compatible,哇擦,不相容。同時下面還有個看不懂的提示。

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

官方文檔有如下一句話:

For a Shader to be compatible with SRP:

  • All built-in engine properties must be declared in a single CBUFFER named “UnityPerDraw”. For example, unity_ObjectToWorld, or unity_SHAr.
  • All Material properties must be declared in a single CBUFFER named “UnityPerMaterial”.

翻譯成白話來說,Shader中所有的内置屬性例如unity_ObjectToWorld,unity_SHAr等,都要在一個名為UnityPerDraw的CBUFFER中聲明,而所有的Material屬性都要在一個名為UnityPerMaterial的CBUFFER中聲明。

官方文檔後面也給到了一個代碼示例

Properties
 
{
 
_Color1 ("Color 1", Color) = (1,1,1,1)
 
_Color2 ("Color 2", Color) = (1,1,1,1)
 
}

//原本的寫法
//float4 _Color1;
 
//float4 _Color2;


//相容SRP Batcher的寫法

CBUFFER_START(UnityPerMaterial)
 
float4 _Color1;
 
float4 _Color2;
 
CBUFFER_END
           

可以看出Material屬性,也就是Properties中的參數在聲明時,都被包含在了一個下面這樣的文法快中

CBUFFER_START(UnityPerMaterial)
//Properties
CBUFFER_END
           

再回過頭來看前面我們Shader的提示:Material property is found in another cbuffer than "UnityPerMaterial"(_MainText_ST)。

也就是說我們Shader中的_MainText_ST屬性沒有聲明在名為UnityPerMaterial的CBUFFER中,我們将其改為如下代碼

sampler2D _MainTex;
CBUFFER_START(UnityPerMaterial)
    float4 _MainTex_ST;
CBUFFER_END
           

注:何為_MainText_ST?當Shader中使用到Texture屬性(如例子中的_MainText),Unity會自動為我們添加一個類型為float4字尾為_ST的屬性(float4 _MainText_ST),用來表示Texture的Tiling和Offset。

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

再次檢視我們的Shader會發現已經相容了SRP Batcher了,同時Statistics面闆中的Save By batching變為了-15。

SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible
SRP Batcher,Draw Call優化,Shader SRP Batcher compatible

此時檢視Frame Debug,發現由原來的RenderLoop.Draw變為了RenderLoopNewBatcher.Draw,同時底下隻有一個SRP Batch。不過這不代表隻使用了一個DrawCall來顯示了這些内容,而是對它們進行了序列上的優化。

此時我們已經實作了SRP Batcher的功能了,不過前文還提到了一個用來處理内置屬性名為UnityPerDraw的CBUFFER卻沒有用到,按照UnityPerMaterial的樣式,我們可以在Shader中添加一下測試代碼:

CBUFFER_START(UnityPerDraw)
    float4x4 unity_ObjectToWorld;
CBUFFER_END
           

注:有關内置Shader屬性的說明可以參照:https://docs.unity3d.com/Manual/SL-UnityShaderVariables.html

結果會報錯:Shader error in 'Unlit/SampleUnlit': redefinition of 'unity_ObjectToWorld',重複定義了unity_ObjectToWorld。而我們的Shader代碼中,怎麼也找不到又在哪定義了這個unity_ObjectToWorld。那麼隻可能是在通過#include引入的其他檔案當中了。

如果是CG語言(CGPROGRAM),我們會引用UnityCG.cginc,在Unity安裝目錄的Editor/Data/CGIncludes檔案夾中我們可以找到它,打開檢視内容可以發現其又引用了UnityShaderVariables.cginc,相同目錄下找到并檢視内容,我們就會發現有如下一些定義,替我們定義好了這些内置屬性

......
CBUFFER_START(UnityPerDraw)
    float4x4 unity_ObjectToWorld;
    float4x4 unity_WorldToObject;
    float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
    float4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms
    float4 unity_RenderingLayer;
CBUFFER_END
......
           

如果用的是HLSL語言(HLSLPROGRAM),在我們項目工程的Library/PackageCache/com.unity.render-pipelines.universal/ShaderLibrary目錄下有個Core.hlsl檔案,在裡面又引用了Input.hlsl,在其中又引用了UnityInput.hlsl,打開UnityInput.hlsl同樣可以看見這些定義

// Block Layout should be respected due to SRP Batcher
CBUFFER_START(UnityPerDraw)
// Space block Feature
float4x4 unity_ObjectToWorld;
float4x4 unity_WorldToObject;
float4 unity_LODFade; // x is the fade value ranging within [0,1]. y is x quantized into 16 levels
real4 unity_WorldTransformParams; // w is usually 1.0, or -1.0 for odd-negative scale transforms

// Light Indices block feature
// These are set internally by the engine upon request by RendererConfiguration.
real4 unity_LightData;
real4 unity_LightIndices[2];

float4 unity_ProbesOcclusion;

// Reflection Probe 0 block feature
// HDR environment map decode instructions
real4 unity_SpecCube0_HDR;

// Lightmap block feature
float4 unity_LightmapST;
float4 unity_DynamicLightmapST;

// SH block feature
real4 unity_SHAr;
real4 unity_SHAg;
real4 unity_SHAb;
real4 unity_SHBr;
real4 unity_SHBg;
real4 unity_SHBb;
real4 unity_SHC;
CBUFFER_END