由淺到淺入門批量渲染(四)
前言
試想一下,當我們在遊戲場景中放置大量(成百上千)帶有骨骼蒙皮動畫的單位時,會發現幀數已經開始下降,這是為什麼呢?
經過多年研究,我發現,造成這種情況的根本原因是:放的太多了。
然而,在開發某些型別的遊戲(如策略或即時戰略等)時,通常又需要儘可能的多放些小兵或者怪物,來烘托戰場氣氛。
需要呈現大量的角色,又需要保證效能,是一件挺麻煩的事情;如果你也在嘗試解決這個問題,並且暫時還沒有找到合適的方法,那接下來要講的內容可能會幫到你;因為我們將進入這一系列(從淺到淺)的下半部分:總結目前較為成熟的針對骨骼蒙皮動畫的優化方案。
骨骼蒙皮動畫的開銷
“欲練神功,必先自宮”是笑傲江湖中非常有名的一句臺詞,它也是神功《葵花寶典》和《辟邪劍法》武功祕籍上的第一句話。以前聽到它時只是感覺這就是邪魔外道武功的一個符號罷了;但現在想來,當時還是年輕了,沒看懂。它其實是一條學習任何技能、知識的訣竅,也是一把開啟成功之門的鑰匙。
葵花寶典上的斑斑血跡記錄了多少悲傷故事
其實,這句話想傳達的真正意思是:做事情,要打好基礎。
所以,要優化骨骼蒙皮動畫,就要先簡單瞭解下它的效能瓶頸所在。
骨骼蒙皮動畫的流程
可以簡粗(簡單粗暴)的將骨骼蒙皮動畫的工作流程分為以下幾個階段:
播放動畫階段
動畫控制器會根據關鍵幀資訊等,調整骨骼的空間屬性(旋轉、縮放、平移)。
計算骨骼矩陣階段
從根骨骼開始,根據層級關係,逐一計算出每一根骨骼的轉換矩陣。
這個矩陣連線的是這根骨骼的本地座標系和角色座標系(通常會是角色腳下);也就是說通過它可以在某一幀動畫結束後,將(某一根)骨骼座標系下的座標或向量,轉換到角色座標系下。
蒙皮階段
更新網格上每個頂點的屬性。
由於動畫改變的是骨骼而不是頂點的空間屬性;而且網格中的頂點是相對於網格座標系下的,並非在角色座標系下。所以在這一階段,我們首先要依據建立骨骼蒙皮動畫時,被記錄下來的頂點和骨骼的關係,找到對應的骨骼(Unity中通過Mesh.boneWeights獲取,一個頂點最多可受到四根骨骼影響)。
其次,通過網格座標系到骨骼本地座標系的轉換矩陣(Unity中通過Mesh.bindposes獲取),來建立從網格座標系到骨骼座標系的橋樑;結合上階段得到的骨骼座標系到角色座標系的轉換矩陣,實現將動畫對骨骼的影響最終作用到頂點上,並將其更新到角色座標系下。
頂點的屬性被動畫控制器“間接”更新
渲染階段
當頂點變換到角色座標系下後,就可以進行渲染了。這裡與一次普通的渲染沒什麼太大差別,唯一需要注意的是,Unity不會對蒙皮網格渲染器進行合批,所以每一個骨骼蒙皮動畫例項都至少需要一次DrawCall。
骨骼蒙皮動畫的開銷
可以將骨骼蒙皮動畫的主要開銷,也簡粗的分成以下幾個部分:
- 更新動畫的開銷
- 計算骨骼矩陣的開銷
- 蒙皮開銷
- 渲染開銷
這裡我建立了一個簡單的場景,來簡單測試下這些開銷。
使用Unity 2018.4.14f1版本建立一個測試場景,場景中包含500個使用相同模型的骨骼蒙皮動畫角色,並迴圈播放空閒、移動、攻擊動畫;測試機是華為P20(Geekbench5得分約為1400),通過Profiler檢視執行耗時。
簡單的步兵模型
場景執行後
我們把與骨骼蒙皮動畫有關的主執行緒運算開銷整理出來,看一下在骨骼蒙皮動畫工作時哪些計算耗時最多。
與骨骼蒙皮動畫有關的主執行緒耗時佔比
可以發現,動畫更新的耗時佔比最高,其次是蒙皮網格的更新(計算矩陣、蒙皮等),最後是渲染。
需要指出的是,我使用的Unity(版本2018.4.14f1)將動畫更新和蒙皮放到了工作執行緒中;所以像蒙皮這種“逐頂點、理論上應該開銷很大的操作”帶來的耗時增加,並沒有體現在主執行緒中;而且我在打包時也沒有勾選多執行緒渲染(Multi-threadedRenderer),所以渲染指令的呼叫也都發生在主執行緒。
動畫更新被放在工作執行緒中執行
蒙皮被放在工作執行緒中執行
渲染指令在主執行緒中呼叫
常見優化方式
Unity下可以通過以下兩種方式快速優化骨骼蒙皮動畫:
- 在匯入模型時進行的優化
- 在打包設定中開啟GPU蒙皮
這兩者的優化效果怎麼樣呢。
匯入模型時的優化
勾選模型匯入設定進行優化
在相同的測試環境下,再次進行測試後可以發現,這種方法確實可以產生一定效果。
其原因我認為主要有以下兩點:
不再為骨骼建立不必要的遊戲物件
對匯入模型進行優化後,Unity將不會為骨骼建立實際的遊戲物件了(我們也可以暴露出一些骨骼作為掛點)。
這些消失的遊戲物件一定程度上也減少了CPU的效能開銷
計算矩陣被移到工作執行緒
除此之外,Unity還會將計算骨骼矩陣的操作放到工作執行緒中,來減少主執行緒耗時。
計算矩陣在工作執行緒中進行
主執行緒耗時不再包含計算矩陣
GPU蒙皮
開啟GPU蒙皮,Unity會通過ComputeShader的方式,使用GPU進行蒙皮。
勾選設定開啟GPU蒙皮
GPU上有大量的ALU(算數邏輯單元),可以並行大量的數值計算,效率較高,應該很適合這種針對頂點屬性的數值計算。
但是實際情況是:在移動裝置上使用GPU蒙皮反而會使主執行緒的耗時增加。
開啟GPU蒙皮後主執行緒耗時增加
通過Profiler可以發現,CPU把更多的時間放在了執行ComputeShader上,由於骨骼動畫的例項很多(500個),所以這個呼叫時間本身成為了效能熱點。
開啟GPU後,蒙皮網格更新中增加了GPU蒙皮
執行GPU蒙皮耗時較高
所以,以目前的情況來看,這種在移動裝置上使用GPU蒙皮的方式,似乎不適合處理大量骨骼蒙皮動畫例項(也許是我使用的方式存在問題)。
寫在最後
我們簡單總結了骨骼蒙皮動畫的工作方式,分析了主要的效能開銷及耗時,以及比較了Unity中常用的兩種優化方式。
但在面對數以千計的角色表現需求上時,無論使用何種Unity自帶的優化,都顯得有些力不從心;所以下次的更新,將介紹兩種目前較為有效、成熟的“奇技淫巧”,來(一定程度上)解決這個問題。
相關閱讀:
由淺到淺入門批量渲染(三)
由淺到淺入門批量渲染(二)
由淺到淺入門批量渲染(一)
來源:騰訊遊戲學院
原文:https://gameinstitute.qq.com/community/detail/133615
相關文章
- 由淺到淺入門批量渲染(一)
- 由淺到淺入門批量渲染(二)
- 由淺到淺入門批量渲染(完)
- promise由淺入深Promise
- RabbitMQ由淺入深入門全總結(二)MQ
- RabbitMQ由淺入深入門全總結(一)MQ
- MySQL索引由淺入深MySql索引
- JavaScript Promise由淺入深JavaScriptPromise
- 物件導向-由淺入深物件
- 純手寫Promise,由淺入深Promise
- 由淺入深理解 IOC 和 DI
- Vue.js 2.0 由淺入深Vue.js
- iOS架構由淺入深 | MVVMiOS架構MVVM
- 由淺到深瞭解工廠模式模式
- 淺入深出Vue:資料渲染Vue
- Vue入門淺析Vue
- Elasticsearch從入門到放棄:淺談算分Elasticsearch
- lua淺淺入門瞭解一下
- 第十八節:Skywalking由淺入深
- [轉帖]由淺入深瞭解GC入門篇(一):什麼是垃圾回收?GC
- 【Fastjson】Fastjson反序列化由淺入深ASTJSON
- 由淺入深 docker 系列: (6) 映象分層Docker
- 由淺入深 docker 系列: (2) docker 構建Docker
- 由淺入深 docker 系列: (3) docker-composeDocker
- 由淺入深理解Dubbo的SPI機制
- 由淺入深完全理解Java動態代理Java
- 由淺入深 學習 Android Binder(三)- java binder深究(從java到native)AndroidJava
- 由淺入深 docker 系列: (5) 資源隔離Docker
- Git 由淺入深之細說變基 (rebase)Git
- 淺入kubernetes(1):Kubernetes 入門基礎
- 淺入淺出webpackWeb
- 零基礎深度學習入門:由淺入深理解反向傳播演算法深度學習反向傳播演算法
- 第十一篇.HFM規則入門(四:深入淺出For語句)
- 【由淺入深_打牢基礎】HOST頭攻擊
- MVP架構由淺入深篇一(基礎版)MVP架構
- 前端如何理解正則-由淺入深的學習前端
- C#非同步程式設計由淺入深(一)C#非同步程式設計
- 淺入淺出 MySQL 索引MySql索引