导语专家坐诊栏目,是腾讯游戏学院专家团打造的新栏目。面向行业中小团队,分享腾讯学院专家团在过往指导中所提炼的共性问题总结。

本期分享嘉宾: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

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

  1. 手游录屏直播技术详解 | 直播 SDK 性能优化实践

    直播无疑是 2016 年的大热话题,七牛云在 6 月底发布了实时流网络 LiveNet 和直播云解决方案后,我们用<直播技术详解>系列文章系统地介绍了直播各个环节的关键技术,帮助视频直播创 ...

  2. 手游网红之亲民的双机位手机游戏直播方案

    手游网红之亲民的双机位手机游戏直播方案 店铺地址:https://shop66907778.taobao.com/ 参考链接:https://blog.csdn.net/weixin_41486034 ...

  3. 复古传世手游服务器维护,新出的《复古传世》手游到底应该怎么玩,这里有篇攻略供大侠参考...

    原标题:新出的<复古传世>手游到底应该怎么玩,这里有篇攻略供大侠参考 <复古传世>新手攻略 [职业选择技巧] 游戏下载看图片水印 传奇世界手游的职业选择推荐,那些还不知道如何选 ...

  4. 天龙八部手游服务器维护公告,天龙八部手游11月29日停机更新公告 系统体验优化...

    天龙八部手游最新的停机更新公告小编为小伙伴们整理带来了,本次更新并没有修改什么太多的东西,就是修复一系列游戏中存在的问题,优化了系统体验,小伙伴们你们想看具体情况吗?那就赶紧和小编一起往下看吧! 天龙 ...

  5. 揭秘重度MMORPG手游后台性能优化方案

    本文节选自<2018腾讯移动游戏技术评审标准与实践案例>手册,由腾讯互娱工程师王杰分享<仙剑奇侠传online>项目中游戏后台的优化经验,深度解析寻路算法.视野管理.内存优化. ...

  6. 【cocos2d-x 手游研发小技巧(3)Android界面分辨率适配方案】

    先感叹一下吧~~android的各种分辨率各种适配虐我千百遍,每次新项目我依旧待它如初恋···· 每家公司都有自己项目工程适配的方案,这种东西就是没有最好,只有最适合!!! 这次新项目专项针对andr ...

  7. Unity3D手游开发日记(3) - 场景加载进度条的完美方案

    我以为做个进度条很简单,分分钟解决,结果折腾了一天才搞定,Unity有很多坑,要做完美需要逐一解决. 问题1:最简单的方法不能实现100%的进度 用最简单的方法来实现,不能实现100%的进度,原因是U ...

  8. node怎么把token放到redis_从零开始手写 redis(八)朴素 LRU 淘汰算法性能优化

    前言 java从零手写实现redis(一)如何实现固定大小的缓存? java从零手写实现redis(三)redis expire 过期原理 java从零手写实现redis(三)内存数据如何重启不丢失? ...

  9. 进入“高画质手游时代”?《天谕》手游首次采用的这项技术有多强大

    近年来,想在移动端感受高质量的游戏画面,已然不再是痴人说梦.更大的地图规模.更炫目的特效.更自然的光影效果,都让玩家对手游的期待,不再止步于打发碎片时间的休闲.要如何满足玩家对于沉浸感.代入感的需求, ...

最新文章

  1. pytorch 优化器 机器学习 调参
  2. 编译其他mysql客户程_mysql编译模块
  3. python技术是什么_学 Python 都用来干嘛的?
  4. 程序人生:提高代码运行效率的9个技巧
  5. Docker精华问答 | 如何让一个容器连接两个网络?
  6. xp计算机用户密码设置,XP怎么设置开机密码_XP系统设置开机密码教程-192路由网...
  7. SQL数据层面操作(DML)
  8. 【Tensorflow2.x】设置GPU(内存自增长、指定GPU)
  9. 关于 google 的 Percolator
  10. 线性代数同济第六版笔记:1-行列式
  11. 随机过程(联合平稳随机过程)
  12. C# Skip和Take的简单用法
  13. 文本分类上分微调技巧实战
  14. java图片加文字水印_JAVA实现图片的修改,添加文字水印效果
  15. Vue2.x动态组件的使用实现组件整合大屏展示
  16. matlab中sub2ind函数
  17. 【原创】联通网络无法使用FTP,无法使用21端口连接的解决方法
  18. MATLAB2018a 64安装
  19. 12JQuery基础笔记
  20. u盘装puppy linux,U盘及硬盘安装、配置、中文化 Puppy linux 4.00

热门文章

  1. 可以分屏吗_LED透明屏分屏是怎么一回事?
  2. 从零开始数据科学与机器学习算法-朴素贝叶斯-07
  3. php mysql随机数不重复,js生成不重复的随机数
  4. 计算机科学导论实验考试,计算机科学导论实验指导书
  5. Django model 中的 class Meta 详解
  6. Apache-Shiro-权限缓存
  7. BZOJ1026 [SCOI2009]windy数 数位dp
  8. objc_setAssociatedObject 关联的简单用法
  9. 网站“注册流程”如何“照顾用户”?
  10. docker pull 下载一半_Docker 从入门到掉坑