接下來我們要來學習下自定義渲染管線中的合批,這一節主要學習SRP Batcher
每一次的Draw Call都需要CPU和GPU之間的通信,如果有大量的資料需要從CPU發送到GPU中,那GPU就可能因為等待資料而浪費時間,而CPU會因為忙于發送資料導緻無法做其他的事情,是以這兩個問題都會導緻幀率的降低。在目前我們的做法有點粗暴,一個物體一個Draw Call,這是非常浪費時間的,隻是目前我們發送的整體資料量較少,是以還感受不出問題。
我們可以用示例數字來說明這個問題。
整三十個球,同樣顔色,按以前的Unity肯定是能合批的,可是現在需要31個Draw Call,通過合批減少的DrawCall數量(Saved by batching)為0,其實就是天空盒一個,剩下的就是30個球的了。
(為什麼Clear的DrawCall沒了?哈哈,這就是我們之前做ClearRenderTarget的小優化,對于Skybox的Flag不用進行清理也是可以的,具體可以檢視《自定義渲染管線基礎學習》-”相機的ClearFlags“這一小節)
自定義渲染管線的合批
合批是合并Draw Call的過程,減少CPU和GPU之間的通信量。
在自定義管線中最簡單的實作方法就是直接開啟SRP Batcher,SRP Batcher實際上并不是直接減少Draw Call的數量,而是簡化了流程,它将材質屬性存儲在GPU,是以不用每一次Draw Call都發送資料,這樣子既能減少CPU和GPU之間的通信,也能減少CPU每一次Draw Call所要做的資料準備工作。這樣也相當于減少了Draw Call的數量。
但是想要SRP Batcher生效,我們的Shader編寫必須按照規範的統一結構來。
我們檢視Shader的面闆可以檢視SRP Batcher的使用情況,發現有無法使用的提示,”屬性沒有定義在叫【UnityPerDraw】的cbuffer中“。cbuffer(constant buffer)。
想要SRP Batcher生效,我們Shader中的所有材質屬性不能像之前那樣直接定義,必須放在固定的記憶體緩存中。
之前SRP Batcher不生效的寫法
我們檢視Unlit的Shader,在上面可以看到提示SRP合批不适用的原因
标準寫法
(但是在一些平台上無法支援cbuffer,比如OpenGL ES 2.0)
//使用cbuffer UnityPerMaterial 包起來才能使SRP Batcher生效
//但是在一些平台上無法支援cbuffer,比如OpenGL ES 2.0
cbuffer UnityPerMaterial
{
float4 _BaseColor; //用于Shader中定義顔色屬性,名稱需相同
};
由于一些平台無法直接支援cbuffer
Core RP Library中通過CBUFFER_START和CBUFFER_END對此做了處理,是以我們可以使用這個來解決問題
使用需要include Common.hlsl
最終寫法
//CBUFFER_START和CBUFFER_END是CORE RP Library中對cbuffer做的處理,解決部分平台無法支援的問題
CBUFFER_START(UnityPerMaterial)
float4 _BaseColor;
CBUFFER_END
但仍然還有問題
我們還需要把别的變量也加到這裡
CBUFFER_START(UnityPerDraw)
float4x4 unity_ObjectToWorld; //每一次繪制GPU設定這個值,然後在一次繪制中的頂點片元函數使用期間值不變
float4x4 unity_WorldToObject;
float4 unity_LODFade;
real4 unity_WorldTransformParams;
CBUFFER_END
然後CustomRenderPipeline.cs中開啟SRP合批
public CustomRenderPipeline()
{
GraphicsSettings.useScriptableRenderPipelineBatching = true; //開啟SRP合批
}
終于适用了
但不知道為什麼我的面闆還是沒有變化
但是FrameDebuger裡面已經可以看到效果了
我們可以看到SRP Batch
但我們可以看到上面顯示着,Draw Calls 30,是以實際上SRP Batch并不是真正的合批,仍然會有30次Draw Calls,是減少了發送到GPU的資料量
到了這裡我自己有點懵了
沒用SRP之前的有合批麼?
除了要勾靜态的靜态合批外,原來的動态合批是怎麼回事?
動态合批需要在PlayerSetting中勾選Dynamic Batch功能,然後也有許多限制
但是它的合批是真的合批
多種顔色的合批
我們增加不同顔色的材質賦予這些球,但是我們發現實際上SRP的Batch并不會增加,因為資料緩存在了GPU上,每一次DrawCall隻需要包含記憶體位置的偏移資料就好了。
對于SRP的使用有一個限制是每個材質的memory layout(記憶體布局?)必須是一樣的,是以在這裡所有的球我們都使用了同一個Shader,而這些材質都隻包含了一個顔色屬性。Unity不會去詳細的比較的材質的memory layout,而是簡單的根據相同的Shader變體去合并Draw Calls。
在上面的例子中,為了有5種顔色,我們建立了5個材質球,但如果要十幾種顔色或者更多,我們總不能去建立那麼多的材質球。如果我們能直接設定每個對象的顔色,那就會友善很多。
我們通過MaterialPropertyBlock實作這個功能。
using UnityEngine;
[DisallowMultipleComponent]
public class PerObjectMaterialProperties : MonoBehaviour
{
static int baseColorId = Shader.PropertyToID("_BaseColor"); //使用Id的方式去設定屬性會更高效
static MaterialPropertyBlock block;
[SerializeField]
Color baseColor = Color.white;
void Awake()
{
OnValidate(); //OnValidate隻在編輯器下會被調用,打包後我們得自己調用
}
//OnValidate在編輯器下,在元件加載群組件修改時會被調用
void OnValidate()
{
if (block == null)
{
block = new MaterialPropertyBlock();
}
block.SetColor(baseColorId, baseColor);
GetComponent<Renderer>().SetPropertyBlock(block);
}
}
我們可以看到雖然我們對這些球隻使用了一個材質球,但是顔色卻是不一樣的,是通過元件修改單個對象的顔色
但是,我們看到SRP Batcher不生效了,這樣子雖然友善了,但是性能不行了