如何快速大量繪製遊戲物件?這個方法值得一試
本文將簡述其實現原理,並分享一個(完成了一半的)網格合併及例項化繪製工具。
如何提高繪製效率
當產生了“要將大量遊戲物件呈現給玩家”的需求時,我們就會碰到這樣一個問題:如何才能提高GPU的繪製效率?
批量繪製較多的騎兵
通常情況下CPU對GPU發起的繪製命令,才是效能的瓶頸所在。CPU為繪製準備資料、視訊記憶體載入資料、為GPU設定渲染狀態等行為所花費的時間,通常比GPU繪製所花費的時間要多。這也就是為什麼我們經常會把DrawCall次數當成快速評判渲染效率的“KPI”。
反觀Unity提供的Static batching(靜態合批)和Dynamic batching(動態合批),也都是從減少CPU到GPU的呼叫次數為出發點,儘量一次傳送一個大的網格(一大堆頂點資料),以減少CPU和GPU的通訊次數,提高彼此的工作效率。
但是無論靜態還是動態合批,在大量遊戲物件繪製的需求面前,都不太合適。
靜態合批從名字上就知道不能用來繪製移動物體,而且其本身還會產生非常大的記憶體開銷(它需要額外的記憶體空間來儲存合併的網格);動態合批也有自己的問題,如頂點數量的限制、材質球限制、無法作用於蒙皮網格(SkinnedMeshRenderer)等,還會對CPU產生不小的壓力(因為它要不停地去動態計算併合併網格)。
例項化繪製
例項化繪製技術的出現,就是為了在不提高CPU負擔的基礎之上,解決CPU到GPU呼叫開銷大的問題。對於相同的物體(同一個網格),只需一次呼叫,GPU就會根據我們想要繪製的次數,啪啪啪一通畫,非常的高效。
但是簡單重複繪製一個物體多次(比如重複繪製1000次小兵),並沒有任何意義。為了能夠繪製出1000個不同的小兵,我們還需要提前為GPU準備一些額外的資料,比如1000個轉換矩陣(畫在不同的位置)、1000個混合色(呈現不同的顏色)等,最終在螢幕上呈現出千軍萬馬的畫面。
如果我們想要在遊戲世界上呈現非常多相同的、靜止不動的石頭,那到此為止就可以了。我們使用Unity提供的手動例項化繪製介面Graphics.DrawMeshInstanced,通過傳入同一個石頭的網格和每一個石頭的轉換矩陣,就可以實現需求(其實Unity也會自動為新增了MeshRenderer元件的單位嘗試使用例項化繪製以提高效率)。
但是對戰場中的小兵做這種簡單地操作就不太合適了。
這是因為小兵通常是採用骨骼動畫來實現動作的,而骨骼動畫對於蒙皮網格的驅動,是CPU即時計算出來的。每個小兵相同時刻的狀態可能都不同,也就是說相同網格同一時刻的頂點位置會有很大差別,因此無法直接進行例項化繪製。
既然CPU上即時計算的骨骼動畫無法進行例項化繪製,我們就不讓CPU計算,而讓這些計算髮生在GPU上,便可將問題解決。
它的原理很簡單
1、將骨骼動畫每一幀對網格各個頂點的變化結果存在一張紋理中,其中紋理的橫座標是頂點索引,縱座標是時間,而橫縱相交對應的值,是這一時刻該頂點在本地空間下的座標。
2、有了這張“頂點動畫紋理”,在頂點著色器中,我們就可以忽視傳入頂點著色器的頂點位置資訊;而以當前所處理的頂點索引為U,以動畫播放至此的時間刻度為V,從上一步的紋理座標中取樣。而取樣到的結果,就是當前這個頂點此時的位置。
3、接下來的步驟便與傳統繪製一樣,與MVP矩陣相乘做空間變換,傳入片段著色器中著色等...可以很容易的想象到,連續為網格上所有頂點設定不同時間下的空間位置,最終繪製到螢幕上時,就能呈現出動畫效果了。
一些相對重要的細節
1、用例項化ID來獲取差異例項單位的屬性
由於我們的最終目標是繪製多個不同動畫狀態的單位,因此從動畫紋理中,用於取樣資訊的時間刻度值,是根據例項化ID,從儲存例項化屬性的資料塊中獲取到的,這樣就可以實現每個例項化單位的動畫播放進度的差異。
2、合併多個不同的網格
手動呼叫例項化繪製介面時,只能傳入一個網格。而我們平時使用的遊戲物件,通常是由若干個蒙皮網格和若干個普通網格組成。比如一個騎兵模型:士兵和馬匹分別是兩個蒙皮網格;而士兵手持的武器通常是一個普通網格,以方便後期做武器替換。
一個遊戲物件可能會由兩種、多個網格組合而成
因此我們會在編輯器模式下,將整個物件包含的網格合併成一個網格,並將這個網格儲存成資源,以便後面呼叫繪製命令時作為實參傳入。
合併成為一個網格
3、多貼圖時處理UV
此外,有些模型上不同的網格還對應了不同的貼圖,比如網格Mesh_0,使用了貼圖Texture_0,網格Mesh_1使用了貼圖Texture_1,由於網格進行了合併,如果針對合併後的網格使用同一張貼圖,便會出現錯誤。
胯下戰馬錯誤的顏色取樣
針對這種情況我們要在合併時做特殊處理,一種處理方式是合併多張貼圖,如將Texture_0與Texture_1合併,然後偏移原本Mesh_1的uv座標,但是這要求兩張貼圖都不能太大,否則無法合併到一張貼圖中;另一種方法是仍然保留兩張貼圖Texture_0和Texture_1,但是對Mesh_0和Mesh_1的uv2做特殊處理,如使用uv2的x儲存兩張貼圖的Lerp值。這樣片段著色器中對兩張貼圖的取樣結果做二次計算後,就可以得到正確的顏色了。
為戰士和戰馬分別替換貼圖
4、動畫的混合
通過紋理實現的動畫也可以實現簡單的混合效果,它是通過在頂點著色器中對多個動畫紋理進行取樣,然後根據一個混合比例,對多個位置資訊進行計算以實現的。
根據速度一維向量進行的Locomotion狀態混合
5、脫離了Renderer的渲染
由於是直接呼叫了Graphics.DrawMeshInstanced進行的繪製,因此並沒有GameObject被建立出來,減少了物件的建立數量,一定程度上也減少了記憶體及CPU的開銷;但是需要自己在loop中組織資料的更新及渲染的更新。
脫離了GameObject+Renderer的繪製
使用動畫紋理的優缺點
優點
1、易於理解、易於實現;
2、CPU的計算(合併網格、記錄動畫資訊)發生在編輯器階段,遊戲執行時CPU沒有額外的開銷;
3、可以實現例項化繪製,充分發揮GPU的繪製效率。
缺點
1、記錄頂點動畫的紋理大小,一方面取決於模型的頂點數量,另一方面取決於動畫的長度,如果頂點數量過多,或動畫過長,生成的紋理就會很大,對視訊記憶體的佔用量也會上升;
2、實現動畫混合,需要從多個動畫紋理中取樣並進行計算,取樣次數多;
3、無法使用動畫狀態機控制動作;
4、動作資訊在儲存時會受儲存格式的精度影響,因此讀取出來的動畫可能不夠精確;
5、無法實現骨骼動畫中的IK(反向動力學)等。
雖然有不少缺點,但是如果你的目的是大批量繪製環境裝飾(樹、草、石頭)或細節要求不高的雜魚小兵、路人,它都是你實現目的優秀手段,值得你去使用它。
最後
最後,分享一個沒有寫完的網格合併及例項化繪製工具,可以實現上述簡單的功能。
通過工具生成動畫資原始檔
簡單的動畫播放
大批攜帶動畫角色的例項化繪製
工具及Demo下載地址:https://github.com/elsong823/AnimationBaker
來源:偶爾學學Unity
相關文章
- 如何提高遊戲購買量?這7點值得一試遊戲
- 如何快速成為一個遊戲測試工程師(配教程)遊戲工程師
- 國人如何打造純正和風女性向遊戲?這個方法或可嘗試遊戲
- 如何使用 Arduino 製作一個繪圖儀UI繪圖
- 如何在海外發好一款遊戲?這5個方法很關鍵遊戲
- 從零開始做一個SLG遊戲(三):用unity繪製圖形遊戲Unity
- 組織架構圖怎麼畫,這個方法能夠讓你快速繪製組織架構圖架構
- 如何選擇合適的物件儲存?這5個方面你值得思考!物件
- JavaScript WebGL 繪製一個面JavaScriptWeb
- CSS 繪製一個時鐘CSS
- 如何使用CSS繪製一個漢堡式選單CSS
- 如何繪製一個類甘特圖 (附原始碼)原始碼
- 如何在遊戲中表現玻璃的質感?這個方法效率超高遊戲
- 遊戲原畫教程:手部繪製過程遊戲
- 多個物體模型快速製作爆炸圖?試一試ThingJS!模型JS
- 如何關閉win10錄製遊戲_win10取消遊戲錄製的方法Win10遊戲
- 遊戲難以變現?或許你應該試下這5個方法遊戲
- 一分鐘教程-超橢圓快速繪製
- 使用joinjs繪製流程圖(二)-Paper物件的屬性和方法JS流程圖物件
- canvas繪製“飛機大戰”小遊戲,真香!Canvas遊戲
- 製作成功的IP遊戲,需注意這7個方面遊戲
- 如何準確評估自己的遊戲? 試著用這套方法量化分析遊戲深度和上手性遊戲
- 如何處理多個集合關聯關係時,試試這個方法?
- java面試一日一題:如何判斷一個物件是否為垃圾物件Java面試物件
- 如何繪製漂亮的架構圖,方法論+工具架構
- 這個許可權動態申請庫,值得嘗試一下
- 測試過程中如何快速定位一個 bug
- 測試過程中如何快速定位一個bug
- 如何製作一個音遊鬼畜視訊
- 10天快速複製一款能火30天的小遊戲 這套路行得通嗎?遊戲
- 如何製作室內地圖,哪裡可以快速繪製室內地圖地圖
- 怎麼自己製作地圖?如何快速實現簡單地圖繪製?地圖
- 用遊戲高手的使用者洞察法,如何複製一個拼多多遊戲
- 電商小遊戲火爆,開發者如何快速接入?看這裡遊戲
- 如何製作遊戲設計文件:讓團隊快速且高效協作遊戲設計
- CSS 繪製一個3d掘金 logoCSS3DGo
- 遊戲影片怎麼錄製,遊戲錄製軟體哪個好遊戲
- 如何用AI繪畫生成好看的畫作?教你一個方法AI