研發(fā)日志技術(shù)篇(上)——如何死磕GPU
各位船長好,今天我們有備而來,給大家展示一個“核爆級”秘密武器——同屏十萬只怪!給各位觀眾姥爺開開眼。如果非專業(yè)人士看后有“不明覺厲”的感覺,那就對了?。ㄊ謩庸奉^)

立項之初,為了使《異星前哨》在市場上更有競爭力,制作人設(shè)定了這個看起來比較瘋狂的目標(biāo)。

十萬只怪!技術(shù)組開了一禮拜的會,要達(dá)成這個目標(biāo),我們得面臨CPU+GPU(圖形處理器,顯卡的核心)的雙重壓力。因為海量物體的繪制,需要GPU承擔(dān)。這些物體的尋路、戰(zhàn)斗、AI等行為又需要CPU的計算,這幾乎是一個極限挑戰(zhàn),那么…

咳咳,盡管在公司的另一個項目《鐵甲雄兵》已經(jīng)實現(xiàn)了同屏數(shù)百人戰(zhàn)斗的大戰(zhàn)場,但同屏十萬的難度跨越著實有點大,經(jīng)過了對各類性能問題漫長的死磕,初步總結(jié)了一些方法,本篇主要從GPU角度的兩個主要方案來談一下。
?
一、GPU大批量動畫優(yōu)化渲染技術(shù)
由于游戲中存在海量怪物,每只怪物都需要播放各種動畫,勢必會對運行效率產(chǎn)生嚴(yán)重影響,所以需要一種新的動畫渲染技術(shù),能夠承受海量的物體。

現(xiàn)代顯卡的性能增長非常快,瓶頸逐步變成GPU和CPU之間的數(shù)據(jù)同步,每一次同步都需要互相等待,雙方浪費大量時間。之前的很多游戲包括手游,都把減少drawcall作為一個重要優(yōu)化手段,因為每一次drawcall都會導(dǎo)致一次CPU上傳數(shù)據(jù)+GPU繪制的同步。這里要注意的是,并不是說drawcall本身效率很低,而是常規(guī)drawcall導(dǎo)致的數(shù)據(jù)同步效率極低,當(dāng)然drawcall本身也是有開銷的,能減少是最好。

因此這次的優(yōu)化方向是,減少CPU與GPU的數(shù)據(jù)同步。怪物盡可能由GPU繪制,最大可能減少數(shù)據(jù)同步,以達(dá)到最高異步效率。知易行難,定了方向但是還有很多技術(shù)細(xì)節(jié)需要克服。
?
傳統(tǒng)骨骼動畫計算(CPU計算骨架+GPU計算蒙皮)

傳統(tǒng)的做法是CPU按照動畫序列計算當(dāng)前各骨架位置(BoneMap),然后把BoneMap傳遞給GPU,GPU通過VertexShader,結(jié)合各mesh頂點數(shù)據(jù),對應(yīng)BoneMap數(shù)據(jù)以及骨骼權(quán)重等進(jìn)行插值計算,得到新頂點位置。傳統(tǒng)動畫處理由于需要CPU+GPU兩個步驟順序進(jìn)行計算,每個動畫物體需要每次單獨調(diào)用繪制,同時獨立物體動畫計算時需要在Drawcall時傳遞不同材質(zhì)參數(shù),不利于合批高效繪制,另外CPU與GPU之間頻繁進(jìn)行數(shù)據(jù)交互也會產(chǎn)生很大開銷,所以我們必須換一種方式。

貼圖骨骼動畫

為了將計算挪入GPU,必須把相關(guān)數(shù)據(jù)存入顯存供GPU讀取,首先是美術(shù)輸出的骨骼動畫信息,我們選擇將各骨骼運動的動畫幀變化存到貼圖RGBA Float格式中作為貼圖骨骼動畫文件,在VertexShader中通過函數(shù)tex2dlod采點獲取貼圖顏色數(shù)值,通過將顏色數(shù)值轉(zhuǎn)換為變換矩陣,從而與本地mesh頂點等信息,產(chǎn)生動畫變換。(注:在材質(zhì)設(shè)置貼圖采樣模式時選用point,避免貼圖采樣受其他模式干擾)

其次,游戲運行時,每只怪物都有自己的當(dāng)前動畫信息,這些信息也需要同步給GPU,考慮后續(xù)的批量繪制等優(yōu)化技術(shù),我們還是選擇貼圖做為載體。先將各動畫集如idle,run,attack轉(zhuǎn)換為貼圖動畫紋理,在將這些貼圖動畫作為一個材質(zhì)的各貼圖通道。
另外由于批量繪制中各個角色播放的動畫第幾幀以及播放哪個動畫集不確定,所以在調(diào)用draw call前,通過CPU計算各個單位動畫系統(tǒng)得到一張全單位mask圖,圖上每個像素代表一個單位,一個像素RGBA信息記錄,該單位播放哪個動畫集以及播放第幾幀。這樣,一張貼圖就包含了所有單位的實時狀態(tài)信息,可以直接傳輸給GPU,只有一次數(shù)據(jù)同步。
總之,就是想辦法把骨骼計算壓力轉(zhuǎn)給GPU,把CPU閑出來。

大批量實例化繪制
普通drawcall繪制函數(shù)一次只能繪制當(dāng)前的一個mesh,而繪圖系統(tǒng)還提供了批量繪制的接口,可以繪制一個mesh多次,出現(xiàn)在不同位置坐標(biāo)。函數(shù)類型形如:
void DrawMeshInstanced(Mesh mesh, int submeshIndex, Material material, List<Matrix4x4> matrices);
這個函數(shù)一般只能拿來加速靜態(tài)物體,比如場景里的石頭之類。因為動態(tài)物體有動畫,幾個動態(tài)物體很難完全一致。

我們發(fā)現(xiàn)由于游戲里怪物數(shù)量非常多,總有一些怪物的動畫幀當(dāng)前相同,正好使用這個函數(shù)進(jìn)行加速。舉個例子,最常見的怪物進(jìn)攻,假設(shè)移動動畫長2秒,當(dāng)前運行速度每秒60幀,無論多少只怪物,理論上最多調(diào)用60x2=120次就可以畫完全部怪物。因為怪物最多只有120種狀態(tài)。當(dāng)然實際情況更復(fù)雜,因為每臺機(jī)器幀率都不一樣。
?
動畫物體大批量實例化繪制
要使用動畫物體大批量實例化繪制接口,首先要使多單位物體材質(zhì)合批繪制,另外為了提高骨骼動畫效率采用貼圖動畫技術(shù),因此最終方法為:



二、大批量物體陰影優(yōu)化渲染技術(shù)
?
陰影在游戲效果中能體現(xiàn)物體的更真實表現(xiàn),在實際游戲中由于單位眾多,每個單位所需陰影效果也是需要一種優(yōu)化渲染技術(shù)。?
?
傳統(tǒng)陰影功能
在傳統(tǒng)繪制陰影功能中,需要將產(chǎn)生陰影的物體額外繪制一遍ShadowMapDepth,最后將所生成的ShadowMap與畫面中的接收陰影物體比較深度從而確定是否繪制陰影著色。
在多單位的情況下這里的額外繪制ShadowMapDepth處理成為影響效率的瓶頸,如何降低這個Pass繪制次數(shù)成為了關(guān)鍵問題所在。
?
批量化GPU陰影技術(shù)
首先按照屏幕大小創(chuàng)建一個RT,控制渲染次序在場景不透明物體渲染之后,獲取當(dāng)前場景繪制完的深度圖,調(diào)用實例化繪制接口繪制多單位陰影,將深度圖等信息傳入所需大批量單位繪制ShadowCasterPass,從而在RT中獲得當(dāng)前屏幕內(nèi)多單位最終陰影效果貼圖,將該RT貼圖與屏幕當(dāng)前像素合并從而實現(xiàn)陰影最終效果。

進(jìn)一步改進(jìn),通過在繪制單位中預(yù)留代理陰影體,在VertexShader階段中做動態(tài)切換本體頂點或代理陰影體頂點,切換最終哪些頂點光柵化在pixel shader著色計算陰影提高效率。(調(diào)Shader可真是個體力活)

本篇結(jié)尾
這期《異星前哨》技術(shù)部關(guān)于GPU的吐槽就到這里了,后面我們會分享CPU相關(guān)的優(yōu)化經(jīng)驗,還請大家多多加入心愿單,以及游戲?qū)⒃?2月16日正式發(fā)售,歡迎大家捧場!
