ue4 优化CPU性能VR

3 篇文章 0 订阅

转载地址:第二部分

https://software.intel.com/zh-cn/articles/cpu-performance-optimization-and-differentiation-for-unreal-engine-4-vr-application-part2

作者:王文斓

虚拟现实(VR)能够带给用户前所未有的沉浸体验,但同时由于双目渲染、低延迟、高分辨率、强制垂直同步(vsync)等特性使VR对CPU渲染线程和逻辑线程,以及GPU的计算压力较大[1]。如何能有效分析VR应用的性能瓶颈,优化CPU线程提高工作的并行化程度,从而降低GPU等待时间提升利用率将成为VR应用是否流畅、会否眩晕、沉浸感是否足够的关键。Unreal* Engine 4 (UE4) 作为目前VR开发者主要使用的两大游戏引擎之一,了解UE4的CPU线程结构和相关优化工具能够帮助我们开发出更优质基于UE4的VR应用。本文将集中介绍UE4的CPU性能分析和调试指令、线程结构、优化方法和工具、以及如何在UE4里发挥闲置CPU核心的计算资源来增强VR内容的表现,为不同配置的玩家提供相应的视听表现和内容,优化VR的沉浸感。

UE4 VR应用的CPU优化技巧

当VR开发的过程中遇到CPU性能问题时,除了需要找出瓶颈所在,还要掌握UE4里能够帮助优化这些瓶颈的工具,熟知每个工具的用法、效果和差异才能选取最合适的优化策略,快速提高VR应用的性能。在这个章节我们会集中讲解和介绍UE4的这些优化工具。

渲染线程优化

由于性能、带宽和MSAA等考虑因素,目前VR应用多采用前向渲染(Forward Rendering)而非延迟渲染(Deferred Rendering)。但在UE4的前向渲染管线中,为了减少GPU overdraw,在basepass前的prepass阶段会强制使用early-z来生成depth buffer,导致basepass前的GPU工作量提交较少。加上目前主流的DirectX* 11基本上属于单线程渲染,多线程能力较差,一旦VR场景的drawcalls或者primitives数目较多,culling计算时间较长,基本上在basepass前的计算阶段就很可能因为渲染线程瓶颈而产生GPU闲置(GPU bubbles),降低了GPU利用率而引发掉帧,所以渲染线程的优化在VR开发中至关重要。

由于性能、带宽和MSAA等考虑因素,目前VR应用多采用前向渲染(Forward Rendering)而非延迟渲染(Deferred Rendering)。但在UE4的前向渲染管线中,为了减少GPU overdraw,在basepass前的prepass阶段会强制使用early-z来生成depth buffer,导致basepass前的GPU工作量提交较少。加上目前主流的DirectX* 11基本上属于单线程渲染,多线程能力较差,一旦VR场景的drawcalls或者primitives数目较多,culling计算时间较长,基本上在basepass前的计算阶段就很可能因为渲染线程瓶颈而产生GPU闲置(GPU bubbles),降低了GPU利用率而引发掉帧,所以渲染线程的优化在VR开发中至关重要。

如果我们用GPUView分析图3的场景,会得到图4的结果,图4中红色箭头指的位置就是CPU渲染线程开始的时间。由于running start,第一个红色箭头在垂直同步前3ms就开始计算,但显然到了垂直同步时GPU还没工作可以做,一直到3.5ms后GPU短暂工作了一下又闲置了1.2ms,然后CPU才把prepass工作提交到CPU context queue,prepass完成后又过了2ms basepass的工作才被提交到CPU context queue给GPU执行。图4中红圈圈起来的地方就是GPU闲置的时间段,加起来有接近7ms的GPU bubbles,直接导致GPU渲染无法在11.1ms内完成而掉帧,需要2个垂直同步周期才能完成这帧的工作,实际上我们可以结合Windows* Performance Analyzer(WPA)分析GPU bubbles期间的渲染线程调用堆栈并找出瓶颈是由哪些函数引起的[1]。另外第二个红色箭头指的位置是下一帧渲染线程开始的时间,由于这一帧出现掉帧,所以下一帧的渲染线程多了整整一个垂直同步周期作计算。等到下一帧的GPU在垂直同步后开始工作时,渲染线程已经把CPU context queue填满了,所以GPU有足够多的工作可以做而不会产生GPU bubbles,只要没有GPU bubbles一帧的渲染在9ms内就能完成,于是下一帧就不会掉帧。3个垂直同步周期完成2帧的渲染,这也是为什么平均帧率是60fps的原因。

从图4的分析结果可以发现在这例子中,GPU实际上并不是性能瓶颈,只要把真正的CPU渲染线程瓶颈解决,该VR游戏就能够达到90fps。而事实上我们发现大部分用UE4开发的VR应用都存在渲染线程瓶颈,因此熟练掌握下面几种UE4渲染线程优化工具可以大大提升VR应用的性能。


图 3.一个存在CPU渲染线程瓶颈的VR游戏例子,上面显示了SteamVR对每帧CPU和GPU消耗时间的统计。


图 4.图3例子的GPUView时间视图,可以看到CPU渲染线程瓶颈导致了GPU闲置,从而引发掉帧。

实例化立体渲染(Instanced Stereo Rendering)

VR由于双目渲染的原因导致drawcall数目增加一倍,容易引发渲染线程瓶颈。实例化立体渲染只要对对象提交一次drawcall,然后由GPU分别对左右眼视角施加对应的变换矩阵,就能够把对象同时画到左右眼视角上,等于把这部分的CPU工作移到GPU处理,增加了GPU vertex shader的工作但可以节省一半drawcall。因此,除非VR场景的drawcall数目较低(< 500),否则实例化立体渲染一般能够降低渲染线程负载,为VR应用带来约20%的性能提升。实例化立体渲染可以在项目设置中选择打开或关闭。

可见性剔除(Visibility Culling)

在VR应用中渲染线程瓶颈通常由两大原因造成,一个是静态网格计算另一个是可见性剔除。静态网格计算可以通过合并drawcall或者mesh来优化,而可见性剔除则需要减少原语(primitives)或者动态遮挡剔除(dynamic occlusion culling)的数目。可见性剔除瓶颈在VR应用中尤其严重,因为VR为了减低延时强制每帧CPU渲染线程计算最多只能提前到垂直同步前3ms(running start/queue ahead),而UE4里InitViews(包括可见性剔除和设置动态阴影等)阶段是不会产生GPU工作的,一旦InitViews所花时间超过3ms,就必定会产生GPU bubbles而降低GPU利用率,容易造成掉帧,所以可见性剔除在VR里需要重点优化。

在UE4里可见性剔除由4个部分组成,按照计算复杂度从低到高排序分别是:

  1. 距离剔除(Distance culling)
  2. 视椎剔除 (View frustum culling)
  3. 预计算遮挡剔除(Precomputed occlusion culling)
  4. 动态遮挡剔除(Dynamic occlusion culling): 包括硬件遮挡查询(hardware occlusion queries)和层次Z 缓冲遮挡(hierarchical z-buffer occlusion)

所以设计上尽可能将多数的primitives由1-3的剔除算法处理掉,才能减少InitViews瓶颈,因为4的计算量远大于前3个。下面会集中讲解视椎剔除和预计算遮挡剔除:

视椎剔除

UE4里VR应用的视椎剔除是对左右眼camera各做一次,也就是说需要对场景里所有primitves历遍2次才完成整个视椎剔除。但事实上我们可以通过改动UE4代码实现超视椎剔除(Super-Frustum Culling)[5],即合并左右眼视椎并历遍1次场景就能完成视椎剔除,大致可以节省渲染线程一半的视椎剔除时间。

预计算遮挡剔除

经过距离剔除和视椎剔除后,我们可以用预计算遮挡剔除进一步减少需要发送到GPU做动态遮挡剔除的primitives数目,以减少渲染线程花在处理可见性剔除的时间,同时也可以减少动态遮挡系统的帧跳跃(frame popping)现象(由于GPU遮挡剔除查询结果需要一帧后才返回,所以当视角快速转动的时候或者在角落附件的对象容易产生可视性错误)。预计算遮挡剔除相当于增加内存占用以及建构光照的时间换取运行时较低的渲染线程占用,场景越大内存占用和译码预存数据的时间也会相对增加。然而,相对于传统游戏来说VR场景一般较小,而且场景大部分对象都属于静态对象,用户的可移动区域也有一定限制,这对于预计算遮挡剔除来说都是有利因素,因此这也成为开发VR应用时必须做的一项优化。

做法上,预计算遮挡剔除会根据参数设置自动把整个场景切割成相同大小的方块(visibility cell),这些cell涵盖了view camera所有可能出现的位置,在每个cell的位置预先计算遮挡剔除并储存在该cell里会100%被剔除的primitives,实际运行时以LUT(Look Up Table)的形式读取当前位置所在cell的剔除数据,那些被预存下来的primitives在runtime时就不需要再做动态遮挡剔除了。我们可以通过控制台命令“Stat InitViews”查看Statically Occluded Primitives来得知多少primitives被预计算遮挡剔除处理掉,用Decompress Occlusion查看每帧预存数据的译码时间以及用“Stat Memory”中的Precomputed Visibility Memory查看预存数据的内存占用。其中Occluded Primitives包括了预计算及动态遮挡剔除的primitives数目,将Statically Occluded Primitives / Occluded Primitives的比例提高(50%以上)有助于大大减少InitViews花费时间。预计算遮挡剔除在UE4里的详细设置步骤以及限制可以参考 [6-7]。


图 5.预计算遮挡剔除例子。

静态网格体Actor合并(Static Mesh Actor Merging)

UE4里的“Merge Actors”工具可以自动将多个静态网格体合并成一个网格体来减少绘制调用,在设置里可以根据实际需要选择是否合并材质、光照贴图或物理数据,设置流程可以参考[8]。此外,UE4里有另一个工具-分层细节级别(Hierarchical Level of Detail,HLOD)也有类似的Actor合并效果[9],差别在于HLOD只会对远距离发生LOD的对象做合并。

实例化(Instancing)

对于场景中相同的网格体或对象(例如草堆、箱子)可以用实例化网格体(Instanced Meshes)来实现。只需提交一次绘制调用,GPU在绘制时会根据对象的位置做对应的坐标变换,假如场景中存在不少相同的网格体,实例化能有效降低渲染线程的绘制调用。实例化可以在蓝图里进行设置(BlueprintAPI -> Components -> InstancedStaticMesh(ISM))[10],如果想对每个实例化对象设不同的LOD可以用Hierarchical ISM(HISM)。

单目远场渲染(Monoscopic Far-Field Rendering)

受限于瞳距,人眼对于不同距离的对象有不同的立体感知程度(depth sensation),按照人均瞳距65mm来看,人眼对于立体感受最强烈的距离大约在0.75m到3.5m之间,超过8m人眼对立体就不太容易感知,而且灵敏程度随着距离越远而越弱。

基于这个特性,Oculus*和Epic Games*在UE 4.15的前向渲染管线中引入了单目远场渲染,容许VR应用分别根据各对象到view camera的距离设置成用单目还是双目渲染[11],如果场景中存在不少远距离对象,这些远距离对象用单目渲染就可以有效降低场景的绘制调用和pixel shading成本。例如Oculus*的Sun Temple场景采用单目远场渲染后每帧可以减少25%的渲染开销。值得注意的是目前UE4中的单目远场渲染只能用在GearVR*上,对PC VR的支持要后面的版本才会加入。

在UE4里单目远场渲染的详细设置方法可以参考[12],另外可以在控制面板输入指令“vr.MonoscopicFarFieldMode [0-4]”查看stereoscopic buffer或monoscopic buffer的内容。

逻辑线程优化

在UE4的VR渲染管线中,逻辑线程比渲染线程提早一帧开始计算,渲染线程会基于前一帧逻辑线程的计算结果生成一个代理(proxy)并据此进行渲染,确保渲染过程中画面不会发生变化; 同时逻辑线程会进行更新,更新结果会通过下一顺的渲染线程反映到画面上。由于逻辑线程在UE4里是提前一帧计算的,除非逻辑线程耗时超过一个垂直同步周期(11.1ms),否则并不会成为性能瓶颈。但问题在于UE4的逻辑线程跟渲染线程一样只能运行在单一线程上,蓝图里的gameplay,actor ticking和AI等计算都是由逻辑线程处理,如果场景中存在较多的Actor或者交互导致逻辑线程耗时超过一个垂直同步周期,那么便需要进行优化。下面介绍两个逻辑线程的性能优化技巧。

Blueprint Nativization(蓝图原生化)

在UE4预设的蓝图转换过程中,需要使用虚拟机(Virtual Machine, VM)将蓝图转化为C++代码,其间会因为VM的开销而造成效能损失。在UE 4.12开始引入了蓝图原生化,可以事先将所有或者其中一部分蓝图(Inclusive/Exclusive)直接编译成C++代码,运行时以DLL形式动态加载,避免VM开销而提高逻辑线程效率,详细设置可参考[13]。需要注意的是,如果蓝图本身已经做过优化(例如将计算较重的模块直接用C++实现),蓝图原生化能够提高的性能有限。

另外蓝图里的函数UFUNCTION是不能inline的,对于反复多次调用的函数可以用蓝图里的Math Node(可inline)或者通过UFUNCTION调用inline函数实现,最好的方法当然是直接把工作分到其他线程处理[14-15]。

骨骼网格(Skeleton Meshing)

如果场景中因为Actor太多而产生逻辑线程瓶颈,除了可以降低骨骼网格体及动画的更新频率(ticking)外,也可以用骨骼网格体LOD(Skeletal Mesh LODs)或者根据距离远近用分层(hierarchical)方法来处理跟Actor之间的交互行为。数个骨骼网格体之间共享部分骨架资源也是另一种可行办法[16]。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值