手遊特效太多怎麼辦?這裡有一份效能優化方案可參考

遊資網發表於2019-09-29
導語專家坐診欄目,是騰訊遊戲學院專家團打造的新欄目。面向行業中小團隊,分享騰訊學院專家團在過往指導中所提煉的共性問題總結。

本期分享嘉賓: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內。


  1. / MobileBasePassVertexShader.usf
  2. Output.BasePassInterpolants.PixelPosition.w = Output.Position.z / Output.Position.w;
  3. void TranslucentSetupPS_ES2(
  4.     float4 InUVs[2] : TEXCOORD0,
  5.     out float OutDepth : SV_Depth,
  6.     out half4 OutColor : SV_Target0
  7.     )
  8. {
  9.     OutColor = half4(0, 0, 0, 1);
  10.     OutDepth = SceneColorTexture.Sample(SceneColorTextureSampler, InUVs[0].xy).w;
  11. }
複製程式碼

跨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

相關文章