unity3d 动态合批设置_Unity3D SkinnedMeshRenderer合批优化
最近做了性能優化相關的工作,其中一些是關于戰斗模塊的渲染的。主要是對場景中使用的基于SkinnedMeshRenderer的網格進行了一些合批優化(降DC),記錄如下。項目使用的Unity版本為5.6.4p1。
游戲中的戰斗模塊是這樣的:戰斗邏輯由服務器承擔,戰斗瞬間完成,將戰斗過程發給客戶端,客戶端負責播放;
場景中有敵我雙方最多12個方陣(雙方各2排3列),每個方陣由12個小兵組成(3排4列);
戰斗基于回合制,敵我各個方陣依次攻擊,攻擊時會出列,沖到目標方陣前,攻擊完成后返回;
小兵使用的是帶動畫的3D模型,使用AnimationController控制動作;
每個方陣的小兵是完全一致的(含模型、貼圖、動作)
綜上,使用戰場管理器根據后端發來的戰斗數據,改變各個方陣的position以及控制AnimationController來實現戰斗過程的播放。
戰斗模塊中的渲染部分,從最初的版本到目前的一個可接受的版本,總共經歷了多次迭代,以下詳細記錄,本文中使用的截圖非項目截圖(單個模型面數小于項目中使用的面數,此處使用的模型來自AssetStore,僅供演示使用)。本文的工程可以在github上看到所有源碼。
原始版本
每個方陣12個小兵,實實在在地放了12個SkinnedMeshRenderer(后邊簡稱為SMR),在方陣的腳本中保存了所有的AnimationController(后邊簡稱AC),當方陣要移動時則移動所有的SMR,播放動畫時同時為所有AC設置狀態。
此方案每方陣共計12個SMR和12個AC。
兩輪迭代
迭代一:共享網格
每個方陣放1個帶SMR的小兵模型,每幀Bake網格,同時有12個MeshRenderer,其MeshFilter指向的Mesh即SMR每幀Bake出的Mesh。考慮到SMR的AC會控制SMR及父節點的運動,保留了所有的AC,僅僅把實際用來展示的SMR換成了普通的MeshRenderer。
此方案每方陣共計1個SMR和12(或13)個AC,節省了一些骨骼動畫的計算。所有的MeshRenderer使用相同的網格和材質,但是由于單個模型頂點數的原因無法合批繪制。
迭代二:GPU Instancing
每個方陣1個帶SMR的小兵模型,每幀需要Bake,并將Bake的Mesh使用Graphics.DrawMeshInstanced在指定的位置繪制12份。這一操作需要系統支持GPU Instancing并且材質(shader)也要支持。大概是在shader中增加以下的代碼。1#pragma multi_compile_instancing
在頂點著色器的輸入的結構體中增加:1UNITY_VERTEX_INPUT_INSTANCE_ID
在頂點著色器函數中增加:1UNITY_SETUP_INSTANCE_ID(v);
其中v是頂點著色器的輸入。這里是本文中使用的支持Instancing的shader。
最后不要忘了將材質是否支持GPU Instancing屬性設置為true。
此方案每方陣共計1個SMR和1個AC,但需要系統和材質支持GPU Instancing。12個小兵只需一個DrawCall即可繪制出來。當整個方陣移動時,需要手動更新這些Instancing的變換矩陣。
目前項目中使用了結合了迭代一和迭代二的方案,首先會根據系統和材質是否支持GPU Instancing做出判斷,如果支持則使用迭代二的方案,否則使用迭代一的方案。
備選方案:GPU Instancing的共享網格
如果系統支持GPU Instancing且使用迭代二中支持GPU Instancing的材質,在使用迭代一共享網格方案時也可以合批(按照GPU Instancing處理,可以突破最大頂點數對合批的限制)。
與迭代二相比,這種備選方案不需要計算Instance繪制時的變換矩陣,但是卻依賴AC來控制各小兵中SMR及其父級節點的變換。
方案匯總
在一臺支持GPU Instancing的機器上對比四種方案如下:方案A方案B方案C方案D原始版本迭代一迭代二備選方案
以下依次是方案ABCD場景statistics:
方案A:
方案B:
方案C:
方案D:
可以看到基本上方案A和方案B是同一個level的,方案C和方案D是同一個level,FPS由于一直波動所以截取到的數值只能表現出大概的趨勢。
下邊依次是方案ABCD繪制Frame截圖(使用FrameDebugger截取):
方案A:
方案B:
方案C:
方案D:
可以看到,方案B中因為超出300頂點這一限制所以無法合批,但是修改材質之后(方案D),支持GPU Instancing,不再受此限制的約束。
包含以上四個方案的項目工程都放在github。
一些彎路
除了前邊的一些方案之外,還曾經走過一些彎路。在使用基于共享網格的迭代一方案時,發現會因為頂點數量超出限制而導致網格和材質都相同的多次繪制無法合批。就想到使用Mesh.Combine來合并網格,初始化時合并一次并在每幀更新頂點和變換矩陣,或者每幀合并。
使用這種方案,確實可以合批降低DC,但是大大增加了CPU的計算負荷,相比之下整體性能降低,在移動平臺上FPS不升反降。于是拋棄了此方案。
其它思路
另外有一些思路,做了一些簡單嘗試,但是未落實成具體方案:
實時三渲二
使用另外的相機將小兵模型繪制到RenderTexture,然后將此RT作為紋理,在場景中使用12個2D的Sprite來顯示。
目前項目中使用的是正交相機且固定相機位置,此方案是可行的。但是后期需求變化可能會替換為透視相機且相機允許轉動,每個小兵呈現出的2D圖像不同,這一方案將失效。
拋開無法滿足未來需求這一點,還有其它的一些因素需要考慮,這種繪制方式可能增加額外的性能開銷,如多了一個相機和RT,各個Sprite的空間位置關系、繪制層級控制,小兵與HUD的繪制層級控制等。
幾何著色器
在shader中增加幾何著色器(Geometry shader),以控制將一個模型的頂點(三角形)繪制出更多份。由于項目最終是部署到Android和iOS平臺,所以很快放棄了這一思路。
如果將來會有PC或者主機平臺的項目,或許可以嘗試。將原來的坐標轉換(MVP矩陣變換)的工作從頂點著色器中移出,放入幾何著色器。并且幾何著色器中將輸入的頂點(三角形)繪制多份,最終送達片段著色器一并繪制。由于對于Unity及圖形學的研究尚淺,不知這種方案是否可以實現,如果真的可以實現,也不了解這種方法是否會帶來更大的性能開銷,待后續有時間再研究研究這個。
Animation Instancing
最后一個方案是Animation Instancing,這種方法是在網上看到的(鏈接),由于與項目中的需求有些差異,并且時間關系也沒有去做更多的探索。這種方案可以針對更高要求的SMR合批繪制,不限制各個SMR必須有相同的動作。預先生成動畫保存到紋理中,然后運行時讀取紋理來播放動畫,大幅降低了CPU的負載(提升了少量GPU的負荷和內存占用),整體的渲染性能大大提升。但是目前無法支持動畫狀態切換,不過作者表示很快會支持這一功能。
REFERENCE
總結
以上是生活随笔為你收集整理的unity3d 动态合批设置_Unity3D SkinnedMeshRenderer合批优化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CEdit 控件 更新内容的 方法(可以
- 下一篇: ios 监听一个控制器的属性_ios -