手游特效太多怎么办?这里有一份性能优化方案可参考

腾讯GWB游戏无界 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

最新评论
暂无评论
参与评论

商务合作 查看更多

编辑推荐 查看更多