由淺到淺入門批量渲染(完)

zt枸杞憂天發表於2021-01-07
上回簡述了與優化骨骼蒙皮動畫有關的內容,接下來我們將來到《由淺到淺》系列的最後一篇:介紹兩種批量渲染骨骼蒙皮動畫單位的優化方案:烘焙頂點動畫和烘焙骨骼矩陣動畫。

簡單來說,它們基本的思路,都是將骨骼蒙皮動畫的“結果”預先儲存在一張紋理中;然後在執行時通過GPU從這張紋理中取樣,並使用取樣結果來更新頂點屬性;再結合例項化技術(GPU instancing),達到高效、大批量渲染的目的。

如果你之前對這類優化方案並不瞭解,看了上面的描述,也仍然一頭霧水;那太好了,這篇文章(沒準)可以幫助你快速入門這類優化方案。

下面我們就簡單的介紹一下它的工作流程及原理吧。

烘焙頂點動畫

可以簡單的將它的工作流程分為兩個階段:

  • 非執行狀態下的烘焙階段
  • 執行狀態下的播放階段。

烘焙階段

這個階段其實是為後面的播放階段準備動畫資源,你也可以把這個資源簡單的理解為一種特殊型別的動畫檔案。

首先,在編輯狀態下,讓攜帶骨骼蒙皮動畫的角色,按照一定幀率播放動畫。

由淺到淺入門批量渲染(完)
非執行狀態下播放動畫

我們知道:動畫播放時,角色網格會被蒙皮網格渲染器(SkinnedMeshRenderer)更新而產生變形(頂點變化);如果在動畫播放的同時,記錄下每一個頂點在這一時刻相對於角色座標系(通常是角色腳下)的位置。那麼,當動畫播放完畢時,我們會得到一張“每一個頂點在每一個關鍵幀時的位置表”,我們就叫它“幀頂點位置表”吧。

可是“幀頂點位置表”名字太長,你可能不太好記,我打字也比較麻煩,所以我們後面就叫它“表表”吧。

由淺到淺入門批量渲染(完)
播放某動畫時,記錄下的表表

除了表表,我們還能得到與它對應的動畫資訊,比如這個動畫的名稱、時長、幀率、總幀數、是否需要迴圈播放等資訊。

到這裡,第一個階段我們需要的內容就準備就緒了,可以進入下一個階段:播放動畫階段。

播放階段

在動畫播放時,通過一個變數(播放時長)來更新播放進度;結合上一階段我們記錄下來的動畫總長度、動畫總幀數資訊,就可以計算出當前動畫播放到了第幾幀(當前幀數 = 已播放時長 / 動畫總時長 x 動畫總幀數)。

一旦得到當前動畫幀,就表示可以通過表表鎖定一行頂點資料。

由淺到淺入門批量渲染(完)
通過關鍵幀找到的頂點資料

接下來,只要遍歷這行頂點資料;然後根據索引找到網格中對應的頂點並更新它的位置,就等於完成了蒙皮工作。

由淺到淺入門批量渲染(完)
每一幀都通過表表來更新頂點屬性,動畫就播放起來了

如你所見,使用這種方式來更新角色動畫,其實是直接使用了預先處理好的骨骼動畫、蒙皮網格渲染器的作用結果,是一種用空間換時間的策略。

既然通過一個播放進度、表表和一些簡單的動畫資訊,就能直接完成蒙皮(對頂點屬性的更新),那麼我們就不再需要以下內容了:

  • 骨骼資訊
  • 動畫控制器
  • 蒙皮網格渲染器

使用CPU來讀取表表,並遍歷更新所有頂點,顯然不如GPU來的高效;所以接下來,我們將這一步放到渲染管線中的頂點變換階段,藉著GPU處理頂點的契機,完成使用表表中的資料對頂點屬性進行更新。

使用紋理儲存動畫資料

首先,為了便於GPU讀取表表,我們將其儲存為一張紋理,可以稱之為動畫紋理。

比如,對於一個擁有505個頂點的模型來說,我們可以將表表中的資訊儲存到一張 512 x Height 大小的紋理中。

這其中,紋理的寬度用來表示頂點的數量,而紋理的高度用來表示關鍵幀,所以Height的值取決於動畫長度以及動畫幀率。

由淺到淺入門批量渲染(完)
將表表中的頂點位置“烘焙”到紋理中

我們通過UV座標來獲取這張紋理上的畫素,就可以被理解為:取第U個頂點在第V幀時的座標。

當然,除此之外,我們還會將動畫資訊儲存成為動畫資源,將重要資訊進行序列化(動畫名稱、動畫長度、總幀數、是否迴圈等)。

由淺到淺入門批量渲染(完)
使用ScriptableObject儲存動畫資訊

後面的事情就簡單了:在播放動畫時,CPU將當前播放的關鍵幀傳給頂點著色器;頂點著色器計算出對應的V座標;結合頂點索引及動畫紋理的寬度計算出U,既可取樣出這個頂點基於角色座標系下的座標;接下來用這個座標再進行後面的空間變換就可以了。

法向量

由於動畫播放時,頂點的實時位置是從紋理中取樣,而非從網格中讀取的(不再使用蒙皮網格渲染器,頂點緩衝區內的資料不會被修改),所以頂點屬性中的法線資訊也無法使用了(永遠是靜止狀態下的);如果需要獲取正確的法向量,那就需要在烘焙頂點座標時也同樣將法線烘焙下來,並在頂點變換階段將這個法向量也取樣出來。

由淺到淺入門批量渲染(完)
烘焙的法線紋理

存在多個動畫

通常情況下,角色不會只包含一個動畫;比如小兵通常擁有空閒、移動、攻擊三個動畫。如果對每一個動畫都烘焙一或兩張(法向量)紋理,那貼圖的數量將很快不受控制。鑑於所有動畫對應的頂點數量一致,也就是紋理的寬度都相同,我們可以將多個動畫紋理進行合併。

由淺到淺入門批量渲染(完)
三個動作被合併到了一張紋理中

將多張動畫紋理進行上下排列、合併後,我們只要將每個動畫的起始、終止的V座標範圍追加到動畫資源中即可。然後在播放、切換動畫時,根據當前動畫所在的起始位置以及播放進度,就能算出正確的V座標了。

由淺到淺入門批量渲染(完)
儲存動作資訊時需要額外記錄一些屬性

當然,如果網格的頂點數量少,而動畫數量多,我們也可以多列放置動畫(前提是放的下)。

由淺到淺入門批量渲染(完)
多列放置動作

動畫過渡

簡單的動畫過渡很容易實現,只要在切換動畫時,分別計算出當前動畫和下一個動畫的播放位置,然後傳給GPU進行兩次頂點位置取樣,再對兩次取樣的結果進行插值即可。

由淺到淺入門批量渲染(完)
帶動畫過渡

由淺到淺入門批量渲染(完)
不帶動畫過渡

使用例項化渲染

例項化渲染的特點是使用相同網格,相同材質,通過不同的例項屬性完成大批量的帶有一定差異性的渲染;而烘焙頂點恰好符合了例項化渲染的使用需求。

所以,我們只需將控制動畫播放的關鍵屬性:比如過渡動畫播放的V座標、當前和下一個動畫的插值比例等,放入例項化資料陣列中進行傳遞;再在頂點著色器中,對關鍵屬性獲取並使用即可。

由淺到淺入門批量渲染(完)
例項化渲染時獲取到的關鍵屬性

當然,如果你想實現更多不同的效果,比如你附帶了一張遮罩紋理,用來調整diffuse或做某些特殊的計算,那你也需要將控制引數加到例項化資料中就可以了。

頂點著色器取樣

在頂點著色器中是無法使用tex2D進行取樣的,需要使用tex2Dlod進行代替,但是這個特性需要shader model 3.0(#pragma target 3.0)才可以支援。

頂點索引

可以通過語義SV_VertexID來獲取頂點索引,但是在移動平臺上這個特性需要OpenGL ES3.0(#pragma target 3.5)才可以使用(當然也可以在烘焙階段將頂點索引儲存到網格屬性中)。

與蒙皮網格渲染器的比較

  • 不再需要CPU計算動畫和蒙皮,提升了效能
  • 可以通過例項化技術批量化渲染角色,減少DC

烘焙頂點的主要問題

  • 模型頂點數量受限:如果紋理的最大尺寸限制在2048 x 2048,那麼只能烘焙下頂點數在2048個以下的模型
  • 記錄頂點動畫的紋理過大:(頂點位置)紋理格式需設定為TextureFormat.RGBAHalf
  • 儲存的動作長度有限

由淺到淺入門批量渲染(完)
烘焙頂點的動畫長度參考表

烘焙骨骼矩陣

除了烘焙頂點,另一種常用的優化方案是烘焙骨骼矩陣動畫。

聽名字就知道,烘焙骨骼矩陣與烘焙頂點位置,原理十分相似;最大的差異在於它們在烘焙時所記錄的內容不一樣:烘焙頂點記錄下來的是每個頂點的位置,而烘焙骨骼矩陣記錄下來的是每一根骨骼的矩陣,僅此而已。

由淺到淺入門批量渲染(完)
記錄下每一根骨骼在每一幀動畫播放後的矩陣

烘焙骨骼矩陣最大的意義在於它補上了烘焙頂點的短板:受頂點數量限制、烘焙的動畫紋理過大 及 紋理數量較多。

動畫紋理使用量差異

烘焙頂點對於紋理(面積)的使用是受頂點數量決定的,可以簡單理解為:

紋理面積使用量 = 頂點數量 x 動畫長度 x 內容數量

所以,當模型的頂點數量過多時(數以千計),這種烘焙方式或者無法烘焙下整個模型(頂點數量>2048),或者需要一張或多張(法線、切線)大尺寸紋理(<2048 && > 1024)。

但是,烘焙骨骼矩陣主要取決於骨骼的數量,可以簡單理解為:

紋理面積使用量 = 骨骼數量 x 動畫長度 x 矩陣烘焙方式(x1、x2 或 x3)

在移動平臺上,通常20根左右的骨骼就可以取得不錯的表現效果,所以相對於烘焙頂點,烘焙骨骼可以記錄下更長的動畫,同時它也不再受頂點數量的限制,也無需對法線或切線進行特殊處理(因為可以在取樣後通過矩陣計算得出)。

由淺到淺入門批量渲染(完)
兩種方式烘焙的動畫紋理尺寸差異較大

烘焙階段

與烘焙頂點相似,烘焙骨骼也需要先在非執行狀態下,預先播放一次動畫;並在動畫播放時,記錄下每個關鍵幀下每根骨骼的轉換矩陣。

這裡有三點需要注意。

第一,記錄下的矩陣是每根骨骼從網格座標系轉換到角色座標系下的矩陣:

Matrix_meshToRole = Matrix_boneLocalToRole x Matrix_meshToBoneLocal

Matrix_meshToBoneLocal可以通過mesh的bindposes獲取;而Matrix_boneLocalToRole可以通過bone的transform計算獲得。

第二,需要將每個頂點與骨骼的關係記錄到網格資訊中,這個關係是指頂點會被哪根骨骼影響(骨骼索引)以及影響的大小(權重值)。

每個頂點最多可以受4根骨骼影響,但是被越多骨骼影響意味著播放時會有越多的取樣和矩陣計算,通常限制在2根骨骼就能得到不錯的效果;骨骼索引和權重可以通過mesh的boneWeights得知,在烘焙紋理時可以將它儲存在mesh中不用的uv中,以便在頂點著色器中獲取。

由淺到淺入門批量渲染(完)

每個uv可以儲存下一根骨骼的索引和權重,通常使用兩個uv就可以了

第三,對於不同的骨骼動畫,烘焙矩陣的方式也不一定相同。

例如,如果骨骼動畫中每根骨骼只會相對於上層骨骼進行旋轉變換,那我們烘焙一個四元數就夠了,也就是一根骨骼的一個矩陣只佔用一個畫素;但是如果骨骼除了旋轉,還有平移甚至縮放的操作,那我們就需要2-3個畫素來儲存一個骨骼的矩陣了。

由淺到淺入門批量渲染(完)
擁有特長的角色,需要特殊處理(來自《匹諾曹》)

由淺到淺入門批量渲染(完)
也可以簡粗的將一個矩陣完整的儲存在三個畫素中

播放階段

與烘焙頂點相同的是,烘焙骨骼矩陣也是在頂點變換階段,通過對動畫紋理的取樣,完成頂點座標計算的;但是它的計算方式相對於烘焙頂點要複雜一些。

這裡主要是需要根據烘焙矩陣的方式,通過取樣來還原從網格座標系到角色座標系的轉換矩陣;例如,在烘焙階段將完整的矩陣儲存在三個畫素中,那轉換時就需要取樣三次才能拼湊出一個完整的矩陣。

當然,一旦得到轉換矩陣,角色座標系下的頂點位置、法向量等就可以通過計算獲取,後面的變換就可以繼續了。

相比於烘焙頂點

正如前文所說,烘焙骨骼不再受頂點數量的限制,可以記錄下更長的動畫時間,烘焙紋理的尺寸和數量也有明顯的優勢。

由淺到淺入門批量渲染(完)
烘焙骨骼的動畫長度參考表

由淺到淺入門批量渲染(完)
相同模型下的烘焙動畫長度對比

但是,烘焙骨骼這種方式會在頂點著色器中需要進行多次頂點取樣,在模型頂點數較多、或渲染單位數量較多時,其效率會略低於烘焙頂點。

由淺到淺入門批量渲染(完)
烘焙骨骼的在頂點著色器中的取樣更多

由淺到淺入門批量渲染(完)
兩種烘焙方式分組比較

由淺到淺入門批量渲染(完)
華為P20上的兩種烘焙方式的對比

小結

以上,就是針對批量渲染骨骼蒙皮動畫單位的兩種優化方案。

針對這兩種方案,我個人認為並沒有絕對的孰優孰劣;正如你所看到的,烘焙骨骼在處理複雜模型或多動作時更具優勢;但如果渲染數量較多、模型頂點數較少、表現需求較弱(無需法向量參與計算)時,烘焙頂點也是值得嘗試的,因為它在效能上會更好一些。

其他優化方案

其實,除了上述兩種優化方案外,坊間還有一些特殊的小技巧:比如在手遊《三國志大戰M》的開場戰鬥表演中,某些帶動畫的模型角色,也通過例項化達到了批量渲染的目的。

由淺到淺入門批量渲染(完)
部分小兵不是簡單的“片”,而是帶模型的單位

但是通過GPA抓幀工具,會發現同一個模型的網格會在同一幀存在多個不同的“姿勢”;我推測這裡應該是將利用了若干個、使用相同模型的、骨骼蒙皮動畫的播放“結果”,通過例項化渲染的方式,複製到多個位置上,由於每個骨骼蒙皮動畫的播放進度都略有不同,它們混在一起後的效果就會比較自然,不失為一種巧妙的方法。

由淺到淺入門批量渲染(完)
渲染時每個模型的“姿勢”都略有差異

寫在最後

至此,這回的《由淺到淺入門批量渲染》系列就全部更新完了。

相關閱讀:

由淺到淺入門批量渲染(四)
由淺到淺入門批量渲染(三)
由淺到淺入門批量渲染(二)
由淺到淺入門批量渲染(一)


來源:騰訊遊戲學院
原文:https://gameinstitute.qq.com/community/detail/133631

相關文章