手游特效太多怎么办?这里有一份性能优化方案可参考
導語專家坐診欄目,是騰訊游戲學院專家團打造的新欄目。面向行業中小團隊,分享騰訊學院專家團在過往指導中所提煉的共性問題總結。
本期分享嘉賓:KM,圖形圖像優化渲染方面專家。
在ACT游戲中華麗的特效是不可或缺的部份,但渲染這類半透明特效時往往帶來的性能上的開銷,特別在最高畫質打開HDR及MSAA后情況更為嚴重。本篇文章將從移動端GPU的運作特性分析半透明特效在高畫質的設定下造成性能問題的原因,并分享一個在UE4中實現的優化方案和結果。
移動端GPU運作特性
與桌上/主機GPU常見的IMR(Immediate-Mode Rendering)不同,現時市場上通用的移動端GPU(例如Adreno/Mali/PowerVR等)都采用了TBR(Tile Based Rendering)的方案來節省數據傳輸的帶寬;借此減少訪問片外內存(Off-chip/External Memory一個在移動平臺上十分消耗電量和耗時的操作)的次數。
盡管每個硬件廠商在實現TBR的細節上有所不同,但運作原理都大致如下:[1]
?
首先,GPU的Tiler會將畫面分成一個個二維的Tile(矩形區塊)。模型的頂點經過Vertex Shader/Clipping/Back Face Culling以后會變成一個個屏幕空間的三角形,這些三角形會被緩存在一個Triangle Cache里面。假如某三角形需要在某個Tile里面繪制,那該Tile的Triangle List中存一個索引;以上步驟稱為Binning。
生成的Triangle Cache與Triangle List等數據會保存在System Memory中的Intermediate store內。
當一幀里所有的渲染命令都經執行完Vertex Shader并生成Triangle List后,GPU會把逐個Tile的Triangle List從System Memory傳回GPU內并執行Raster/Pixel Shader/Blending等運算。[2]
?
對GPU的性能影響
HDR/MSAA
GPU的On-Chip Memory有非常高的讀寫速度,能大大提升MSAA/Alpha混合的效率;但由于成本昂貴,因此On-Chip Memory的空間非常有限。例如從Google開源的Andriod驅動代碼中可以得知,即使是旗艦級的Adreno 630亦只有1 MiB的GMEM(即Adreno系列的GPU On-Chip Memory)。[3]
由于打開HDR與MSAA需要更多空間來保存渲染結果,GPU只能夠透過縮小Tile的尺寸來乎合On-Chip Memory的固定大小。進行渲染的Tile數量會因此而增加。
換言之,從System Memory傳送Raster數據到GPU/把渲染結果從GPU傳回Framebuffer的次數會增加,為帶寬造成壓力及延遲(Latency)。[4]
例子:假如GPU On-Chip Memory大小為1MB同樣以1920 x 1080的分辨率16-bit Depth進行渲染的情況下,使用LDR(RGBA)以及沒有MSAA,Framebuffer約需要:
·(1+1+1+1+2)Bytes 1 1920*1080=12441600 Bytes=11.87MB
·即需要拆分為~12個Tile來進行渲染
而使用FP16 HDR以及打開4x MSAA Framebuffer約需要:
·(2+2+2+2+2)Bytes 4 1920*1080=82944000 Bytes=79.10MB
·即需要拆分為~80個Tile來進行渲染
因此HDR+4x MSAA會比LDR的多消耗6倍帶寬。
Alpha混合
即使Alpha混合是在高速的On-Chip Memory內進行,但是帶Alpha混合的像素與像素之間不能啟用早期Early Z優化,因此Overdraw的像素會對性能造成一定影響。
此外,手游賬號賣號平臺移動端GPU的Output Merger(或者ROP)進行定點數(UNORM)的Alpha混合會比浮點數(FP16)有更佳的性能,因為一般的移動端GPU Output Merger都是模擬浮點數的混合。與此同時,移動端GPU在進行MSAA的浮點數Alpha混合時是需要逐個樣本計算混合。即是說4x MSAA的FP16 Alpha混合每個Fragment便需要進行4遍Alpha混合計算。[5]
UE4的移動端渲染管線
了解到移動端GPU的HDR及MSAA特性后,我們再分析一下UE4在移動端的渲染管線。
首先我們使用RenderDoc抓一幀的數據。
我們可以觀察到UE4是直接以FP16+MSAA的SceneColorMobile RT(Render Target)來渲染所有帶Translucency的物件(粒子系統/半透特效)。
之后會把FP16+MSAA的SceneColorMobile RT進行Resolve,并運行后處理效果(此時只有HDR,不帶MSAA)。最后把后處理結果拷貝到屏幕的Back Buffer上并渲染UI/HUD等(這階段都不帶HDR與MSAA)。
?
因此在一個放置~70個Translucency Drawcall的場景中,Draw Time由~14 ms(不帶HDR/MSAA)上升到~20 ms(帶HDR&MSAA)。
優化方案
MSAA的特性
由于MSAA的抗鋸齒效果是針對三角形的邊沿部分而設計,對使用貼圖定義透明度的特效基本上起不了什么作用。
?
[6]
所以優化思路就是把半透明的特效先渲染到另一個沒有帶MSAA的Render Target(RT)內,之后再以后處理的方式混合到場景內。但這衍生另一個問題,如何在另一個RT渲染半透特效時使用現有場景的深度(Z-Buffer)來作Depth-Test呢?
移動端MSAA
在桌上GPU我們可以把帶MSAA的Z-Buffer Resolve到另一個相同尺寸但不帶MSAA的Buffer中,但移動端GPU一般都不帶這功能。
在移動端GPU,MSAA一般是先把MSAA樣本先暫存在On-Chip Memory之后馬上進行Resolve,最后在整個Tile完成渲染時把結果傳回System Memory的RT內。因此移動端的Color RT都不會帶MSAA樣本。
針對以上特性,UE4的移動端渲染管線在打開HDR(FP16)支持后會把已線性化的場景深度(Linear SceneDepth)直接保存到Color RT的Alpha通道內,以便在后處理效果(例如
epth of Field/Sun Shaft)中能夠訪問場景深度。
因此在我們的方案中,是把Color RT的Alpha通道改為保存未線性化的深度(UE4是Reversed Z),在渲染半透特效之前把SceneDepth以后處理的Shader復制到半透RT的Z-Buffer內。
?
跨RT的Alpha混合
另一個需要解決的問題是如何把半透RT的Alpha混合結果再次混合到場景RT內。
假如我們需要混合三個輸出的像素s1,s2,s3,其Alpha值為a1,a2,a3,當前Framebuffer的顏色是d0;混合結果為d1,d2,d3:
d1=d0*(1-a1)+s1*a1;
d2=d1*(1-a2)+s2*a2;
d3=d2*(1-a3)+s3*a3;
把以上公式分別以上一步代入:
d2=[d0*(1-a1)*(1-a2)]+[s1*a1*(1-a2)+s2*a2];
d3=[d0*(1-a1)*(1-a2)*(1-a3)]+[s1*a1*(1-a2)+s2*a2]*(1-a3)+s3*a3;
從d3的公式我們可以觀察到d3是由兩個部分相加而成:
·[d0*(1-a1)*(1-a2)*(1-a3)]
·[s1*a1*(1-a2)+s2*a2]*(1-a3)+s3*a3
因此我們以半透RT的
·Alpha通道保存fx.a=(1-a1)*(1-a2)*(1-a3)
·RGB通道則保存fx.rgb=[s1*a1*(1-a2)+s2*a2]*(1-a3)+s3*a3
·對應渲染特效的Blending Factors則設為:
·AlphaBlendEnable=true;
·SrcBlend=SrcAlpha;
·DestBlend=InvSrcAlpha;
·SeparateAlphaBlendEnable=true;
·SrcBlendAlpha=Zero;
·DestBlendAlpha=InvSrcAlpha;
最后便可以透過d0*fx.a+fx.rgb;把特效混合回場景的RT內。[7]
其他細節
·為了在中端機型上也能夠支持渲染大量的半透特效,我們會進一步把半透RT的面積調整至場景RT的1/4大小(即W/2及H/2)。由于我們項目的鏡頭與場景距離不近,一般較難察覺Bleeding的缺陷,把半透RT混合到場景RT基于性能考慮,我們只采用了雙線性過濾(Bilinear Filtering)。
·由于在移動端GPU的浮點數Alpha混合比較慢(在S820上以1280 x 720進行全屏的FP16 Alpha混合占用~2ms),因此我們選擇在后處理的Tone Mapping階段把半透與場景RT混合。
結果
優化后的渲染管線
?
性能
在Snapdragon 820(Adreno 530)的手機中錄得以下結果:
?
此外,我們發現在一些更低階的移動GPU(例如Snapdragon 650的Adreno 510)上,使用半透RT的優化效果會更顯著。
總結
本文分析了移動端GPU的運作特性,以及半透特效為何在打開HDR及MSAA之后會造成性能問題的原因;亦建議了一個在虛幻4引擎中的優化方案。
由于現時的方案是把所有的半透Draw Call全都渲染到另一個RT,在使用1/4面積的情況下一些非特效的半透物件(例如Billboard樹,植皮…等)會顯示得比較模糊。因此這類物件建議在Editor中標注為以Alpha to Converage的方式直接渲染到場景RT里。
另外,在現時方案中,當使用一半大小的半透RT時會有場景像素“漏”(Leaking)到特效里的情況,這可以透過在復制Scene Depth到半透RT的Z-Buffer時加上采鄰近2x2的Scene Depth的最大值來解決。但我們的項目因為性能的考慮沒有加入這個功能。
參考
[1]三星:移動端GPU Tiler運作原理
[2]三星:移動端GPU架構簡介
[3]Google開源的Adreno驅動:第373行
[4]Occlus Rift Adreno的開發注意事項
[5]ARM:registered:Mali:tm:Application Developer Best Practices:JUST14
[6]戰神系列(God of War)Lead Graphics Programmer關于MSAA運作原理的文章
[7]GPU Gems 3中關于Off-screen Particles的文章
[8]CSDN-Adreno GPU Architecture
總結
以上是生活随笔為你收集整理的手游特效太多怎么办?这里有一份性能优化方案可参考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 建立海盗的天堂:盗贼之海的AI(一)
- 下一篇: 横版游戏的摄像机移动理论与实践(上)