手遊特效太多怎麼辦?這裡有一份效能優化方案可參考
本期分享嘉賓: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內。
- / MobileBasePassVertexShader.usf
- Output.BasePassInterpolants.PixelPosition.w = Output.Position.z / Output.Position.w;
- void TranslucentSetupPS_ES2(
- float4 InUVs[2] : TEXCOORD0,
- out float OutDepth : SV_Depth,
- out half4 OutColor : SV_Target0
- )
- {
- OutColor = half4(0, 0, 0, 1);
- OutDepth = SceneColorTexture.Sample(SceneColorTextureSampler, InUVs[0].xy).w;
- }
跨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
來源: 騰訊GWB遊戲無界
原地址:https://mp.weixin.qq.com/s/hRzHoLXyN2F8K6EJXjOdaQ
相關文章
- 一張Web效能優化參考圖Web優化
- 企業該如何做大資料的分析挖掘?這裡有一份參考指南大資料
- SQL優化參考SQL優化
- 影片結構化怎麼玩?杉巖給您一份參考答案
- EndNote裡參考文獻的期刊名顯示錯誤怎麼辦?
- win10開啟軟體太多如何優化 win10程式太多怎麼優化Win10優化
- win10 n卡優化遊戲效能介面在哪裡_win10 n卡優化遊戲效能介面怎麼設定Win10優化遊戲
- 頁面效能優化辦法有哪些?優化
- WebApiClient效能參考WebAPIclient
- 怎麼做好Java效能優化Java優化
- MySQL 效能優化方案MySql優化
- 【文章筆記】效能最佳化技巧參考筆記
- 2020年遊戲春節檔表現如何?這裡有一份手遊活躍度榜單遊戲
- win10 廣告彈框太多怎麼遮蔽_win10廣告太多怎麼辦Win10
- 一份平民化的MySQL效能優化指南MySql優化
- Roguelike 遊戲的這些共性問題怎麼優化?遊戲優化
- mysql許可權參考MySql
- 哪種 Python IDE 最適合你?這裡有一份優缺點列表PythonIDE
- React效能優化方案之PureRenderMixinReact優化
- 前端開發效能優化方案前端優化
- React效能優化方案之PureComponentReact優化
- 【什麼值得抄】史萊姆太多啦,這可咋整?
- 如何守護資料安全? 這裡有一份RDS災備方案為你支招
- JavaScript物件參考手冊JavaScript物件
- 天津哪裡有開手撕發票‘怎麼開哪裡有開’
- 微信小程式效能優化方案微信小程式優化
- 這個遊戲檔案館裡有什麼?遊戲
- 相親原始碼的效能為何這麼重要,該怎樣實現優化?原始碼優化
- MySQL裡 沒有 boolean型別 怎麼辦?MySQL 裡的 tinyint(1)MySqlBoolean型別
- RAKsmart日本伺服器效能怎麼樣?有什麼優勢伺服器
- Mac開不了機怎麼辦?彆著急看這裡Mac
- 原生canvas遊戲效能優化Canvas遊戲優化
- Android 效能優化(十二)之我為什麼寫效能優化Android優化
- DOJO API 中文參考手冊API
- 跟大家聊一下前端效能怎麼優化前端優化
- 效能優化是個手藝活優化
- 自動化 Web 效能優化分析方案Web優化
- 被優化了怎麼辦?優化