## Unreal Engine 4 VR 应用的 CPU 性能优化和差异化 文/王文斓 虚拟现实(VR)能够带给用户前所未有的沉浸体验,但同时由于双目渲染、低延迟、高分辨率、强制垂直同步(VSync)等特性使 VR 对 CPU 渲染线程和逻辑线程、以及 GPU 的计算压力较大。如何能有效分析 VR 应用的性能瓶颈、优化 CPU 线程以提高工作的并行化程度,从而降低 GPU 等待时间提升利用率将成为 VR 应用是否流畅、会否眩晕、沉浸感是否足够的关键。Unreal Engine 4(UE4)作为目前 VR 开发者主要使用的两大游戏引擎之一,了解 UE4 的 CPU 线程结构和相关优化工具能够帮助我们开发出更优质基于 UE4 的 VR 应用。本文将集中介绍 UE4 的 CPU 性能分析和调试指令、线程结构、优化方法和工具、以及如何在 UE4 里发挥闲置 CPU 核心的计算资源来增强 VR 内容的表现,为不同配置的玩家提供相应的视听表现和内容,优化 VR 的沉浸感。 ### 为何要把 PC VR 游戏优化到 90fps Asynchronous Timewarp(ATW)、Asynchronous Spacewarp(ASW)和 Asynchronous Reprojection 等 VR runtime 提供的技术可以在 VR 应用出现掉帧的时候,以插帧的方式生成一张合成帧,等效降低延时。然而,这些并不是完美的解决方案,分别存在不同的限制。 ATW 和 Asynchronous Reprojection 能够补偿头部转动(Rotational Movement)产生的 Motion-To-Photon(MTP)延迟,但如果头部位置发生改变(positional movement)或者画面中有运动对象,即使用上 ATW 和 Asynchronous Reprojection,也无法降低 MTP 延迟;另外 ATW 和 Asynchronous Reprojection 需要在 GPU 的 drawcall 之间插入,一旦某个 drawcall 时间太长(例如后处理)或者剩下来给 ATW 和 Asynchronous Reprojection 的时间不够,都会导致插帧失败。而 ASW 会在渲染跟不上的时候将帧率锁定在 45fps,让一帧有 22.2ms 的时间做渲染,两张渲染帧之间则以传统图像运动估算(motion estimation)的方式插入一张合成帧,如图1所示。但合成帧中运动剧烈或者透明的部分会产生形变(例如图1中红圈框起来的部分),另外光照变化剧烈的时候也容易产生估算错误,导致持续用 ASW 插帧的时候用户容易感觉到画面抖动。这些 VR runtime 的技术在频繁使用的情况下都不能够很好解决掉帧问题,因此开发者还是应该保证 VR 应用在绝大部分情况下都能够稳定跑在 90fps,只有偶然的掉帧才依赖上述的插帧方法解决。 图1  ASW插帧效果 图1 ASW 插帧效果 ### UE4 性能调试指令简介 用 UE4 开发的应用可以通过控制台命令(console command)中的“stat”指令查询各种实时性能数据。其中“stat unit”指令可以看到一帧渲染总消耗时间(Frame)、渲染线程消耗时间(Draw)、逻辑线程消耗时间(Game)以及 GPU 消耗时间(GPU)。从中可以简单看出哪部分是制约一帧渲染时间的主要原因,如图2所示。结合“show”或“showflag”指令动态开关各种功能(features)来观察分别对渲染时间的影响,找出影响性能的原因,期间可以用“pause”指令暂停逻辑线程来观察。需要注意的是其中 GPU 消耗时间包括了 GPU 工作时间和 GPU 闲置时间,所以即使在“stat unit”下看到 GPU 花了最长的时间,也并不代表问题就出在 GPU 上,很有可能是因为 CPU 瓶颈导致 GPU 大部分时间处于闲置状态,拉长了 GPU 完成一帧渲染所需时间。因此还是需要结合其他工具,例如 GPUView 来分析 CPU 和 GPU 的时间图,从中找出实际瓶颈位置。 图 2  “stat unit”统计数字 图 2 “stat unit”统计数字 另外,因为 VR 是强制开启垂直同步的,所以只要一帧的渲染时间超过 11.1ms,即使只超过 0.1ms,也会导致一帧需要花两个完整的垂直同步周期完成,使得 VR 应用很容易因为场景稍微改变而出现性能大降的情形。这时候可以用“–emulatestereo”指令,同时把分辨率(resolution)设为2160×1200,屏幕百分比(screenpercentage)设为140,就可以在没有接 VR 头显及关闭垂直同步的情况下分析性能。 而渲染线程相关的性能数据可以通过“stat scenerendering”来看,包括绘制调用(drawcall)数目、可见性剔除(visibility culling)时长、光照处理时间等。其中可见性剔除又可以通过“stat initviews”指令进一步了解和分柝各部分的处理时长,包括视锥剔除(frustum culling)、预计算遮挡剔除(precomputed visibility culling)和动态遮挡剔除(dynamic occlusion culling)等,用以判断各剔除的效率;输入“stat sceneupdate”指令查看更新世界场景例如添加、更新和移除灯光所花费的时间。另外,也可以通过“stat dumphitches”指令,指定一帧的渲染时间超过 t.HitchThreshold 时把渲染帧信息写进日志。 如果要使游戏效果能够适配不同等级的 PC,那么“stat physics”,“stat anim”和“stat particles”会是三个经常用到跟 CPU 性能相关的指令,分别对应到物理计算时间(布料模拟,破坏效果等)、蒙皮网格(skin meshing)计算时间和 CPU 粒子计算时间。由于这三个计算在 UE4 里都能够分到工作线程作并行处理,因此对这些部分进行扩展能够把 VR 应用有效适配到不同等级的硬件,使 VR 体验以及效果能够随着 CPU 的核数增加而增强。 另外,可以直接输入控制台命令“stat startfile”和“stat stopfile”采集一段实时运行的数据,然后用 UE4 Session Frontend 里的 Stats Viewer 查看运行期间各时间段 CPU 线程的利用率和调用堆栈(call stack),寻找 CPU 热点并进行相应的优化,如图3所示,功能类似 Windows Assessment and Deployment Kit(ADK)里的 Windows* Performance Analyzer(WPA)。 图3  UE4 Session Frontend里内建的Stats Viewer 图3 UE4 Session Frontend 里内建的 Stats Viewer ### 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 开发中至关重要。 如图4显示了一个性能受限于 CPU 渲染线程的 VR 游戏案例。该 VR 游戏跑在 HTC Vive 上,平均帧率为 60fps,虽然从控制台命令“stat unit”中看到 GPU 似乎是主要性能瓶颈,但渲染线程“Draw”每帧所花的时间也很高。从 Steam VR 的帧时间更能清楚看到 CPU 甚至出现了 late start 的情况,这说明渲染线程的工作负载非常重(在 SteamVR 里一帧的渲染线程会在该帧开始时的垂直同步前 3ms 开始计算,称为“running start”,原意是用 3ms 额外延迟来换取渲染线程提前工作,让 GPU 能够在该帧垂直同步后马上有工作能进行,最大化 GPU 的效率。如果一帧的渲染线程工作在下一个垂直同步前 3ms 还没完成,则会阻碍到下一帧的 running start,这情况称为“late start”)。Late start 使渲染线程工作被延后,导致 GPU bubble 的产生。另外在 Steam VR 的帧时间可以看到 GPU 每隔一帧“Other”的占用时间就会比较高,从下面的分析会看到这实际上就是 prepass 前 GPU bubble 的时间。 如果我们用 GPUView 分析图4的场景,会得到图5的结果,图5中红色箭头指的位置就是 CPU 渲染线程开始的时间。由于 running start,第一个红色箭头在垂直同步前 3ms 就开始计算,但显然到了垂直同步时 GPU 还没工作可以做,一直到 3.5ms 后 GPU 短暂工作了一下又闲置了 1.2ms,然后 CPU 才把 prepass 工作提交到 CPU context queue,prepass 完成后又过了 2ms basepass 的工作才被提交到 CPU context queue 给 GPU 执行。图5中红圈圈起来的地方就是 GPU 闲置的时间段,加起来有接近 7ms 的 GPU bubbles,直接导致 GPU 渲染无法在 11.1ms 内完成而掉帧,需要2个垂直同步周期才能完成这帧的工作,实际上我们可以结合 Windows Performance Analyzer(WPA)分析 GPU bubbles 期间的渲染线程调用堆栈并找出瓶颈是由哪些函数引起的。另外第二个红色箭头指的位置是下一帧渲染线程开始的时间,由于这一帧出现掉帧,所以下一帧的渲染线程多了整整一个垂直同步周期作计算。等到下一帧的 GPU 在垂直同步后开始工作时,渲染线程已经把 CPU context queue 填满了,所以 GPU 有足够多的工作可以做而不会产生 GPU bubbles,只要没有 GPU bubbles 一帧的渲染在 9ms 内就能完成,于是下一帧就不会掉帧。3个垂直同步周期完成2帧的渲染,这也是为什么平均帧率是 60fps 的原因。 图4  一个存在 CPU 渲染线程瓶颈的 VR 游戏例子,上面显示了Steam VR 对每帧 CPU 和GPU消耗时间的统计 图4 一个存在 CPU 渲染线程瓶颈的 VR 游戏例子,上面显示了SteamVR 对每帧 CPU 和 GPU 消耗时间的统计 从图5的分析结果可以发现在这例子中,GPU 实际上并不是性能瓶颈,只要把真正的 CPU 渲染线程瓶颈解决,该 VR 游戏就能够达到 90fps。而事实上我们发现大部分用 UE4 开发的 VR 应用都存在渲染线程瓶颈,因此熟练掌握下面几种 UE4 渲染线程优化工具可以大大提升 VR 应用的性能。 图5  如图4例子的GPUView时间视图,可以看到 CPU 渲染线程瓶颈导致了GPU闲置,从而引发掉帧 图5 如图4例子的 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),即合并左右眼视椎并历遍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]。 ##### 静态网格体 Actor 合并(Static Mesh Actor Merging) 图6  预计算遮挡剔除例子 图6 预计算遮挡剔除例子 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 上,对 PCVR 的支持要后面的版本才会加入。 在 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]。 ### UE4 VR 应用的 CPU 差异化 上面介绍了几种 VR 应用的 CPU 优化技巧,然而优化只能保证 VR 应用做到不掉帧不眩晕,无法进一步提升体验。如果想要提升 VR 体验,必须最大程度利用上硬件提供的计算能力,将这些计算资源转化成内容、特效及画面表现提供给最终用户,而这就需要根据计算能力对 CPU 提供相应的差异化内容。下面介绍其中五种 CPU 的差异化技巧。 #### 布料模拟 UE4 的布料仿真主要通过物理引擎分到工作线程进行计算,对逻辑线程影响较小。而且布料模拟是每帧都需要计算的,即使布料不在画面显示范围内,也需要进行计算来决定更新后会否显示到画面中,因此计算量比较稳定,可以根据 CPU 能力适配对应的布料模拟方案[17]。 #### 可破坏物件 可破坏对象在 UE4 里也是通过物理引擎分到工作线程进行破坏模拟计算的,因此对于计算能力较高的 CPU 这部分可以加强,比如更多对象可以被破坏、破坏时出现更多碎片或者碎片在场景中的存在时间更长等。可破坏对象的存在会大大加强场景的表现以及沉浸感,设置过程可以参考[18]。 #### CPU 粒子 CPU 粒子是另一项比较容易扩展的模块,虽然从粒子数目来说 CPU 粒子较GPU粒子少,但采用 CPU 粒子能降低GPU的负担,较好地利用多核 CPU 的计算能力,而且 CPU 粒子具备下列独有的功能: 1. 可发光; 2. 可设置粒子材质及参数(金属、透明材质等); 3. 可受特定引力控制运动轨迹(可以受点、线或者其他粒子的吸引); 4. 能产生阴影。 开发过程中可以对不同 CPU 设置相应的 CPU 粒子效果。 #### 3D 音频 – Steam Audio 对于 VR 应用来说,除了画面外制造沉浸感,还有另一个重要元素就是音频,具有方向性的 3D 音频能够增强 VR 体验的临场感,Oculus 曾经推出 Oculus Audio SDK[19]来模拟 3D 音频,但该 SDK 对环境音效的仿真比较简单,相对并不普及。Steam Audio[20]是 Valve 新推出的一套 3D 音频 SDK,支持 Unity 5.2 以上的版本及 UE 4.16 以上的版本,同时提供 C 语言接口。Steam Audio 具备下面几项特点: 1. 提供基于真实物理模拟的 3D 音频效果,支持头部相关传输函数(Head-Related Transfer Function,HRTF)的方向音频滤波以及环境音效(包括声音遮挡、基于真实环境的音频传递、反射及混音等),另外也支持接入 VR 头显的惯性数据; 2. 能够对场景中每个对象设置音效反射的材质及参数(散射系数、对不同频率的吸收率等),环境音效的仿真可以根据 CPU 的计算能力采用实时或烘焙的方式处理; 3. 环境音效中很多设置或参数都可以根据质量或性能要求做调整。例如 HRTF 的插值方法、音频光线跟踪的数目和反射次数、混音的形式等; 4. 相对于 Oculus Audio SDK 只提供鞋盒式的环境音效仿真(shoebox model)而且不支持声音遮挡,Steam Audio 的 3D 音频模拟更真实而且完整,可以提供更精细的质量控制; 5. 免费且不绑定 VR 头显或平台。 Steam Audio 从 UE4 的逻辑线程收集音源及收听者的状态和信息,通过工作线程进行声音的光线跟踪及环境反射模拟,将计算出来的脉冲响应(impulse response)传送到音频渲染线程对音源进行相应的滤波和混音工作,再由 OS 的音频线程输出到耳机(例如 Windows 的 XAudio2)。整个处理过程都是由 CPU 的工作线程进行,在加入 3D 音频的同时并不会增加渲染线程和逻辑线程负载,对原来游戏的性能并不会造成影响,可以说是非常适合 VR 的一项体验优化。详细的设置过程可以参考 Steam Audio 的说明文档[21]。 #### 可扩展性 UE4 的可扩展性设置是一套通过参数调节控制画面表现的工具,能适配不同计算能力平台[22]。对 CPU 来说,可扩展性主要体现在下面几项参数设置上: 1. 可视距离(View Distance):距离剔除缩放比例(r.ViewDistanceScale 0 – 1.0f); 2. 阴影(Shadows):阴影质量(sg.ShadowQuality 0 - 3); 3. 植被(Foliage):每次被渲染的植被数量(FoliageQuality 0 - 3); 4. 骨骼网格体 LOD 偏差(r.SkeletalMeshLODBias):全局控制骨骼网格体的 LOD 等级偏差; 5. 粒子 LOD 偏差(r.ParticleLODBias):全局控制粒子 LOD 等级偏差; 6. 静态网格体 LOD 距离缩放(r.StaticMeshLODDistanceScale):全局控制静态网格体的 LOD 等级偏差。 如图7、8、9所示分别显示了腾讯《猎影计划》这款 VR 游戏对不同 CPU 的植被、粒子和阴影差异化效果。 图7  《猎影计划》中的植被差异化效果 图7 《猎影计划》中的植被差异化效果 图8  《猎影计划》中的粒子差异化效果 图8 《猎影计划》中的粒子差异化效果 图9  《猎影计划》中的阴影差异化效果 图9 《猎影计划》中的阴影差异化效果 ### 结论 本文介绍了 UE4 的多种 CPU 性能分析工具、优化方法和差异化技巧,基于文章篇幅所限如果想进一步了解各项细节,可以参照参考部分。熟练掌握多种 CPU 性能分析工具和技巧,可以快速找到瓶颈并进行相应的优化,而事实上这对于 VR 应用非常重要。除此以外,在优化的同时如果能够充分利用闲置的多线程资源,能让应用实现更好的画面效果和表现,提供更优秀的 VR 体验。 #### 参考 [1]https://software.intel.com/zh-cn/articles/performance-analysis-and-optimization-for-pc-based-vr-applications- from-the-cpu-s [2]https://docs.unrealengine.com/latest/INT/Engine/Performance/StatCommands/index.html [3]https://docs.unrealengine.com/udk/Three/ ConsoleCommands.html [4]http://graphics.stanford.edu/~mdfisher/GPUView.html [5]http://www.gamasutra.com/blogs/LeszekGodlewski/20160721/272886/The_Vanishing_of_ Milliseconds_Optimizing_the_UE4_renderer_for_Ethan_ Carter_VR.php [6]http://timhobsonue4.snappages.com/culling- precomputed-visibility-volumes [7]https://docs.unrealengine.com/udk/Three/ PrecomputedVisibility.html [8]https://docs.unrealengine.com/latest/INT/Engine/ Actors/Merging/ [9]https://docs.unrealengine.com/latest/INT/Engine/ HLOD/index.html [10]https://docs.unrealengine.com/latest/INT/BlueprintAPI/Components/InstancedStaticMesh/index.html [11]https://developer.oculus.com/blog/hybrid-mono- rendering-in-ue4-and-unity/ [12]https://developer.oculus.com/documentation/unreal/ latest/concepts/unreal-hybrid-monoscopic/ [13]https://docs.unrealengine.com/latest/INT/Engine/Blueprints/TechnicalGuide/NativizingBlueprints/ [14]https://wiki.unrealengine.com/Multi-Threading:_ How_to_Create_Threads_in_UE4 [15]http://orfeasel.com/implementing-multithreading-in-ue4/ [16]https://docs.unrealengine.com/latest/INT/Engine/ Animation/Skeleton/ [17]https://docs.unrealengine.com/latest/INT/Engine/ Physics/Cloth/Overview/ [18]http://www.onlinedesignteacher.com/2015/03/how-to- create-destructible-mesh-in-ue4_5.html [19]https://developer.oculus.com/documentation/audiosdk/ latest/concepts/book-audiosdk/ [20]https://valvesoftware.github.io/steam-audio/ [21]https://valvesoftware.github.io/steam-audio/downloads.html [22]https://docs.unrealengine.com/latest/INT/Engine/Performance/Scalability/ScalabilityReference/