入门顶点动画纹理的实例化绘制
這是第30篇與游戲開發有關的文章。
?
| 寫在最前
網上有多種通過GPU實現骨骼動畫的實例化繪制方法,本文介紹的是其中的一種:將頂點信息逐幀寫入紋理后,在頂點著色器中通過讀取動畫紋理,提取頂點位置并變換,最終實現角色動畫的方法。
本文將簡述其實現原理,并分享一個(完成了一半的)網格合并及實例化繪制工具。
| 如何提高繪制效率
當產生了“要將大量游戲對象呈現給玩家”的需求時,我們就會碰到這樣一個問題:如何才能提高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組件的單位嘗試使用實例化繪制以提高效率)。
但是對戰場中的小兵做這種簡單地操作就不太合適了,這個道理早在1994年上映的電影《精武英雄》中,就已經明確的告訴過我們了。
船越文夫在教導陳真 圖源網絡
這是因為小兵通常是采用骨骼動畫來實現動作的,而骨骼動畫對于蒙皮網格的驅動,是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(反向動力學)等。
雖然有不少缺點,但是如果你的目的是大批量繪制環境裝飾(樹、草、石頭)或細節要求不高的雜魚小兵、路人,它都是你實現目的優秀手段,值得你去使用它。
| 寫在最后
最后,分享一個沒有寫完的網格合并及實例化繪制工具,可以實現上述簡單的功能。
通過工具生成動畫資源文件
簡單的動畫播放
?
大批攜帶動畫角色的實例化繪制
總結
以上是生活随笔為你收集整理的入门顶点动画纹理的实例化绘制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用LitJson进行序列化和反序列化
- 下一篇: 从零点五开始用Unity做半个2D战棋小