當然可以!以下是完整、詳盡、可發布的博客文章,專注講解 Unity 的靜態合批與動態合批機制,并詳細列出它們對 Shader 的要求和所有限制條件。文章結構清晰、技術深度足夠,適合發布在 CSDN、掘金、知乎等技術平臺。
urp默認隱藏動態合批,需要再這邊開啟動態合批的選項
🎯 Unity 靜態合批 & 動態合批完全解析:原理、限制與實戰優化
Unity 渲染優化的第一課,必須懂的就是 Draw Call 合批機制。本文聚焦 Unity 中的 靜態合批(Static Batching) 和 動態合批(Dynamic Batching),從底層原理到 Shader 限制、失敗原因、調試方法,帶你一文吃透。
🧱 什么是 Draw Call 與合批?
在 Unity 中,每渲染一個物體,CPU 就會向 GPU 發送一個 Draw Call(繪制調用)。Draw Call 多意味著 CPU 壓力大,可能引起幀率下降,特別是在移動端和 WebGL 平臺。
Unity 為了優化性能,會嘗試自動將多個渲染調用合并為一次 Draw Call,這就是“合批(Batching)”。
🟩 一、靜態合批(Static Batching)
? 原理
靜態合批是 Unity 在構建時將多個靜態物體的網格數據打包成一個或多個大網格,運行時作為一個整體進行渲染,顯著減少 Draw Call。
? 啟用方式
- 選中 GameObject
- 在 Inspector 中勾選
Static
(或勾選 Static →Batching Static
) - Unity 構建時會對這些對象進行合并
? 條件匯總
條件 | 是否必需 | 說明 |
---|---|---|
勾選 Static 標志 | ? | 必須設置為 Static |
使用相同材質實例 | ? | 材質必須引用同一個 Material 對象 |
使用 MeshRenderer | ? | 不支持 SkinnedMeshRenderer |
Shader 必須兼容 | ? | 見下文 Shader 限制 |
不使用 MaterialPropertyBlock | ? | 會創建獨立材質實例,破壞合批 |
?? Shader 限制(靜態合批)
靜態合批的核心規則是:所有被合批的物體不再是“獨立對象”,而是被合并成一個大網格。
所以 Shader 不允許訪問對象獨有的屬性。
? 以下 Shader 寫法會導致合批失敗:
// 錯誤示例:訪問對象唯一矩陣
float3 worldPos = mul(_Object2World, v.vertex).xyz;
? 推薦用法:
// 推薦寫法:使用 Unity 內置宏
float4 clipPos = UnityObjectToClipPos(v.vertex);
? 禁用功能列表(會導致分批):
Shader 特性 | 說明 |
---|---|
_Object2World , _World2Object | 對象被合并后,不存在單獨變換矩陣 |
GrabPass / 多 Pass Shader | 每個 Pass 是獨立 Draw Call |
Shader Keyword 不一致 | _EMISSION , _NORMALMAP 等開關變化會拆分 Shader 變體 |
使用 MaterialPropertyBlock 修改參數 | 會使每個物體擁有獨立材質實例,合批失敗 |
💡 適用場景
- 城市建筑、地形、房屋、墻體等不會移動/旋轉/縮放的對象
- 靜態 UI 元素(如背景裝飾)配合 SpriteAtlas 合批
🟨 二、動態合批(Dynamic Batching)
? 原理
動態合批是在運行時由 Unity 動態將多個小型對象的頂點數據合并成一個臨時網格,從而減少 Draw Call。
Unity 每幀會重新組合這些物體的網格,雖然提升了繪制效率,但也會帶來一定的 CPU 合批開銷。
? 啟用方式
Project Settings > Player > Other Settings
- ? 勾選
Dynamic Batching
(?? 在 URP 中需在 URP Asset 勾選)
? 條件匯總
條件 | 是否必需 | 說明 |
---|---|---|
使用相同材質實例 | ? | 必須是同一個 Material 對象 |
每個 Mesh 頂點數 ≤ 300 | ? | 官方限制,超過即失敗 |
使用 MeshRenderer | ? | 不支持 SkinnedMeshRenderer |
未啟用 GPU Instancing | ? | Instancing 和動態合批互斥 |
Shader 結構必須簡單 | ? | 頂點函數不能太復雜,不能用動畫偏移 |
縮放需一致或接近 | ?? | 非 uniform scale 可能破壞合批(如 X:2 Y:1 Z:1) |
?? Shader 限制(動態合批)
與靜態合批相比,動態合批對 Shader 要求更苛刻,因為它需要 CPU 快速合并多個對象的數據。
? 以下 Shader 特性將阻止動態合批:
特性 | 說明 |
---|---|
頂點函數復雜 | 含有頂點動畫、扭曲、動態偏移等邏輯 |
非常量矩陣 | 頂點變換使用不確定變量會中斷合批 |
使用不同 Shader Keyword | 會產生不同 Shader 變體 |
材質不同 | 即使 Shader 相同,材質參數不同也會失敗 |
GrabPass、多 Pass Shader | 強制產生多個 Draw Call,無法合批 |
🧪 示例場景
- 掉落的金幣、彈藥、碎片等小物體
- 小型動態粒子替代物(如火花、樹葉)
? 合批失敗的常見原因匯總
原因 | 靜態合批 | 動態合批 | 說明 |
---|---|---|---|
材質不同 | ? | ? | 不同材質一定不能合批 |
Shader Keyword 不一致 | ? | ? | 比如一個開啟 _EMISSION ,另一個關閉 |
使用 MaterialPropertyBlock 設置屬性 | ? | ? | 會實例化材質,打斷合批 |
Shader 使用對象獨立數據(如 _Object2World) | ? | ? | 靜態合批合并后沒有對象矩陣 |
使用復雜頂點動畫 | ? | ? | 動態合批的頂點函數必須簡單 |
Mesh 頂點數 > 300 | ? | ? | 動態合批失敗,靜態合批不限 |
使用 SkinnedMeshRenderer | ? | ? | 這類 Renderer 本身無法合批 |
非等比縮放(如 X=1.5 Y=1 Z=0.8) | ? | ?? | 有概率影響動態合批穩定性 |
🛠? 如何驗證合批是否成功?
🔧 使用 Frame Debugger(首選)
-
打開路徑:
Window > Analysis > Frame Debugger
-
點擊左上角
Enable
-
在 Draw Call 列表中查找是否有:
Batched: Static
Batched: Dynamic
-
點擊每個 Batch 可查看合批的對象和材質
🔧 使用 Profiler
-
打開:
Window > Analysis > Profiler
-
查看 Rendering 模塊中的:
- Draw Calls(總繪制次數)
- Batches(實際提交的批次數)
? 實戰優化建議
優化點 | 原因 |
---|---|
大量靜止物體 → 使用靜態合批 | 提升性能、減少運行時消耗 |
小物體頂點數 ≤ 300 → 可用動態合批 | 控制模型復雜度 |
統一使用共享材質 | 避免因材質不同導致分批 |
避免頻繁使用 MaterialPropertyBlock | 會實例化材質、拆批 |
使用 UnityObjectToClipPos 替代 _Object2World | 保證靜態合批兼容 |
用 Frame Debugger 驗證結果 | 直觀查看合批是否成功 |
📌 總結對比表:靜態合批 vs 動態合批
項目 | 靜態合批 | 動態合批 |
---|---|---|
觸發方式 | 構建時 | 運行時 |
是否勾選 Static | ? 必須 | ? 不需要 |
Mesh 頂點限制 | ? 無限制 | ? ≤ 300 |
運行時性能開銷 | 極低 | 較高(每幀打包) |
適用對象 | 靜止對象 | 小動態物體 |
材質要求 | 同材質實例 | 同材質實例 |
Shader 要求 | 不能訪問對象唯一數據 | 必須簡單、輕量 |
📣 結語
Unity 提供的靜態合批和動態合批,是開發者提升渲染性能最基本、也最有效的優化手段之一。理解它們的原理、限制與觸發機制,可以幫助你從源頭降低 Draw Call 數量,讓你的游戲在中低端設備上依舊運行流暢。
開發不是一味堆特效,而是用合適的方式,做足夠的表現。