利用GPU實現大規模動畫角色的渲染

2022-01-17 13:34:57 字數 3856 閱讀 3709

我想很多開發遊戲的小夥伴都希望自己的場景內能渲染越多物體越好,甚至是能同時渲染成千上萬個有自己動作的遊戲角色就更好了。

但不幸的是,渲染和管理大量的遊戲物件是以犧牲cpu和gpu效能為代價的,因為有太多draw call的問題,如果遊戲物件有動畫的話還會涉及到cpu的蒙皮開銷,最後我們必須找到其他的解決方案。那麼本文就來聊聊利用gpu實現角色的動畫效果,減少cpu端的蒙皮開銷;同時將渲染10,000個帶動畫的模型的draw call從10,000+減少到22個。(模型來自:rts mini legion footman handpainted)

正常情況下,大家都會使用animator來管理角色的動畫,而角色也必須使用skinnedmeshrender來進行渲染。

例如在我的測試場景中,預設情況下渲染10,000個帶動作的士兵模型,可以看到此時的各個效能指標十分糟糕:cpu 320+ms,drawcall:8700+。

因此,可以發現如果要渲染的動畫角色數量很大時主要會有以下兩個巨大的開銷:

cpu的這兩大開銷限制了我們使用傳統方式渲染大規模角色的可能性。因此一些替代方案——例如廣告牌技術——被應用在這種情況下。但是實事求是的說,在這種情境下廣告牌技術的實現效果並不好。

那麼有沒有可能讓我們使用很少的開銷就渲染出大規模的動畫角色呢?

其實我們只需要回過頭看看造成開銷很大的原因,解決方案已經藏在問題之中了。

首先,主要瓶頸之一是角色動畫的處理都集中在cpu端。因此乙個簡單的想法就是我們能否將這部分的開銷轉移到gpu上呢?因為gpu的運算能力可是它的強項。

其次,瓶頸之二是cpu和gpu之間的draw call問題,如果利用批處理(包括static batching和dynamic batching)或是從unity5.4之後引入的gpu instancing就可以解決這個問題。但是,不幸的是這兩種技術都不支援動畫角色的skinnedmeshrender。

那麼解決方案就呼之欲出了,那就是將動畫相關的內容從cpu轉移到gpu,同時由於cpu不需要再處理動畫的邏輯了,因此cpu不僅省去了這部分的開銷而且skinnedmeshrender也可以替換成一般的mesh render,我們就可以很開心的使用gpu instancing來減少draw call了。

寫過shader的小夥伴可能很清楚,我們可以很方便的在vs中改變網格的頂點座標。因此,一些簡單的動畫效果往往可以在vs中實現。例如飄揚的旗幟或者是波浪等等。

(**於bing搜尋)

那麼我們能否利用vs設定頂點座標的方式來展現我們的角色動畫呢?

答案當然是可行。只不過和飄揚的旗幟那種簡單的效果不同,這次我們不僅僅利用幾個簡單的vs的屬性來實現動畫效果,而是將角色的動畫資訊烘焙成一張貼圖供vs使用。

簡單來說,我們按照固定的頻率對角色動畫取樣並記錄取樣點時刻角色網格上各個頂點的位置資訊,並利用貼圖的紋素的顏色屬性(color(float r, float g, float b, float a))儲存對應頂點的位置(vector3(float x, float y, float z))。當然利用顏色屬性儲存頂點的位置資訊時需要考慮到乙個小問題,在下文我會再說。

這樣該貼圖就記錄了整個動畫時間內角色網格頂點在各個取樣點時刻的位置,這個貼圖我把它稱為animmap。

乙個animmap的結構就是下圖這樣的:

在實際工程中,animmap是這個樣子的。水平方向記錄網格各個頂點的位置,垂直方向是時間資訊。

上圖是將角色的animator或animation去掉,將skinnedmeshrender更換為一般的mesh render,只使用animmap利用vs來隨時間修改頂點座標實現的動畫效果。

到這裡我們就完成了將動畫效果的實現從cpu轉移到gpu運算的目的,可以看到在cpu的開銷統計中已經沒有了動畫相關的內容。但是在渲染的統計中,draw call並沒有減少,此時渲染8個角色的場景內仍然有10個draw call的開銷。因此下一步我們就來利用gpu instancing技術減少draw call。

除了使用批處理,提高圖形效能的另乙個好辦法是使用gpu instancing(批處理可以合併不同的mesh,而gpu instancing主要是針對同乙個mesh來的)。

gpu instancing的最大優勢是可以減少記憶體使用和cpu開銷。當使用gpu instancing時,不需要開啟批處理,gpu instancing的目的是乙個網格可以與一系列附加引數一起被推送到gpu。要利用gpu instancing,則必須使用相同的材質,並傳遞額外的引數到著色器,如顏色,浮點數等。

不過gpu instancing是不支援skinnedmeshrender的,也就是正常情況下我們帶動畫的角色是無法使用gpu instancing來減少draw call的,所以我們必須先完成上一小節的目標,將動畫邏輯從cpu轉移到gpu後就可以只使用mesh render而放棄skinnedmeshrender了。

很多build-in的shader預設是有開啟gpu instancing的選項的,但是我們利用animmap實現角色動畫效果的shader顯然不是build-in,因此需要我們自己開啟gpu instancing的功能。

#pragma multi_compile_instancing//告訴unity生成乙個開啟instancing功能的shader variant

...

還記得之前我說過在利用貼圖的紋素的顏色屬性儲存對應頂點的位置時需要考慮到的乙個小問題嗎?

是的,那就是顏色的精度問題。

由於現在rgb分別代表了座標的x、y、z,因此rgb的精度就要好好考慮了。例如rgba32,每個通道只有8位,也就是某乙個方向上的位置只有256種可能性,這對位置來說是乙個不好的限制。

那麼有沒有解決方案呢?

當然還是有的。既然這是乙個和顏色的精度相關的問題,那麼最簡單的方案就是增加精度。例如在寫本文的時我的demo就是採用的這種方式,我使用了rgbahalf這種紋理格式,而它的精度是每個通道16bit。當然,移動平台上渲染大量角色的需求往往對動畫的精確程度的要求沒有那麼高,因此8bit的精度問題應該也不大。

gpuinstancing

how to take advantage of textures in the vertex shader

gpu gems 3 chapter 2. animated crowd rendering

題圖來自《全面戰爭:阿提拉》

各位如果覺得有趣的話,歡迎點個贊。

-eof-

最後打個廣告,歡迎支援我的書《unity 3d指令碼程式設計》

大規模MIP的精確演算法和實現

第2部分 gurobi的python api詳解 第3部分 大規模mip的常見精確演算法及其實現 相關概念 經典的mip問題簡介 常用的高階建模技巧 大規模線性規劃的對偶問題 開源的算例資源 km演算法求解assignment problem dijkstra演算法求解spp 分支定界演算法 列生成...

學堂上新 大規模動畫模擬的一種實現方法

靜態的場景看著沒活力,別人家的場景都是動態的,還能和角色互動,我們也加乙個吧?花叢裡加上昆蟲 蝴蝶 夜晚有螢火蟲飛舞,角色走過就會散開.停停停,需求可以有,但實現起來可沒有我們想象地那麼簡單,就以在花叢中刷上昆蟲蝴蝶為例來看吧,我們需要.根據在開放場景專案中實現大規模的蝴蝶動畫來提公升場景的活力,增...

用jdbc實現對大規模資料的快速儲存

用jdbc雖然對程式開發效率較低,由於其封裝性較低,其在資料儲存訪問時具有更高的效能,這點上是其他orm框架不能比擬的,因此如果對資料訪問效能要求高,訪問量巨大時直接使用jdbc編寫程式則滿足需求。訪問大量資料時用update效率還是很低的,建議使用addbatch 例項如下 conn.setaut...