UE4 的性能瓶颈定位、性能分析和优化方法
计算机散修
2022年05月07日 19:48

瓶颈定位

瓶颈定位,就是要找到造成性能开销的最大元凶,也就是确定优化的基本方向,才能深入和落实到细节层面,进行后续的分析和优化工作。

而要搞清楚开销主要发生在哪个阶段,不可避免地还是要对 Game, Draw, GPU 对应的三类线程以及它们之间的关系有更详细的认识

Game Thread, Draw Thread, GPU Thread 的关系

stat unit 显示的数值,都是在 一帧之内 的耗时,那么在这一帧期间,Game, Draw, GPU 这三者是如何先后执行的,可以参照下图加以理解

这几个线程是并行运行的。

当游戏按照图中的流程执行时,

Game线程会计算所有的游戏逻辑,game线程计算的所有数据都会被储存起来,并被Draw线程使用,它会算出不需要渲染的内容(这些内容不会显示在屏幕上)。

完了之后GPU线程会在屏幕上实际渲染出最终的像素

如果 GPU Thread 率先完成了它的工作,而其他二者仍在工作中(e.g. 已经绘制好了当前帧,但下一帧的数据还没拿到),那么 GPU 就会等待 CPU 的指令而导致下一帧的画面姗姗来迟;反之如果 GPU 耗时更严重,导致 CPU 输送的数据没有被及时处理,使得画面没能被及时渲染,同样会导致卡顿

Game Thread 首先会对整个游戏世界进行逻辑层面的计算与模拟(e.g.Spawn 多少个新的 actor、每个 actor 在这一帧位于何处、角色移动、动画状态等等),所有这些信息会被输送到 Draw Thread

Draw Thread(也叫 Rendering Thread) 会根据这些信息,剔除(Culling)掉不需要显示的部分(e.g. 处于屏幕外的物体),接着创建一个列表,其中包含了渲染每个物体必备的关键信息(e.g. 如何被着色、映射哪些纹理等等),再将这个列表输送给 GPU Thread

GPU Thread 在获取了这个列表之后,会计算出每个像素最终需要如何被渲染在屏幕上,形成这一帧的画面

综上,对于每一帧来说,这三者的执行顺序依次为:Game Thread → Draw Thread → GPU Thread

Notes

一帧的总耗时,取决于三者中开销最严重、即耗时最长的线程

Game Thread 和 Draw Thread 在 CPU 上运行,GPU Thread 在 GPU 上运行

所有的东西 从游戏逻辑所有的Actor的位置,场景中所有物体的位置 动画,实际动画帧,物理效果,AI,一切与场景最终效果有关的计算,在进行处理时都会在Game线程中计算。

这步完成后,整个游戏世界就被计算完毕了。

引擎就会知道什么东西该干什么。

在下一步“Draw”线程他会过滤掉(剔除掉)所有不在相机范围内的却还需要引擎去渲染的对象,然后创建一个列表(包含所有的对象Object,着色器Shaders,材质Materials,纹理贴图Textures和所有需要发送给GPU的数据)。

然后GPU会处理这些数据(包括顶点Vertex,着色器Shaders,纹理Textures等各种数据),然后会在屏幕上绘制最终像素。

手游瓶颈

基于UE的手游客户端的性能主要由这七大部分构成:CPU逻辑CPU渲染图形API(提交)GPU渲染内存带宽加载时间

这几个基本元素又会合力衍生出一些新的性能指标,例如功耗(往往同gpu负载和带宽紧密相关)。

同时这七部分又构成一个闭合的木桶,最长的一块是主要瓶颈,并且瓶颈可以在这几块转移流动。

作为开发者我们解决性能问题的步骤一般都是按照做性能剖析,解读结果,定位问题,增加剖析代码,优化问题,重复剖析的迭代过程来执行。

而高效准确详细的对性能进行剖析得到结果是第一步,在任何引擎上,只要我们能做到在任意时刻准确的获取想要的性能剖析结果,那么才会胸有成竹不会慌

优化准备

在寻找性能优化和找到运行瓶颈时,需要根据项目实际运行的平台上测试,游戏的运行环境越接近目标硬件和目标平台,结果越准确。

PC端游戏

当游戏在是PC端运行时,可在在编辑器中运行,并且需要准备一些环境,剔除一些因素的影响:

1、需要关闭垂直同步 (为确保引擎中最大的帧速没有被限制先设置下帧率)

2、需要关闭平滑帧-将参数置为false

3、需要关闭实时光照

4、需要在standalone 下运行

5、最小化Editor

手游

当运行在手机时,需要打包合适的版本并在实际的运行平台上测试,以保证结果的准确性。

命令行查看性能瓶颈:

1. Show FPS 查看游戏帧率

60.00FPS 表示 刷新率(单位为Hz),就是每一秒屏幕刷新的帧数,帧数越高,刷新的越多即越流畅

16.67ms 表示刷新一帧所需要的时间,时间约少,刷新越快,

1000ms(1s) /16.67ms = 59.98即FPS

2. Show Stats 拆分Fps

Frame: 表示刷新每帧所需要的时间。

(刷新时间是由多个线程同时工作的最长的那个线程决定的)

Game(CPU 游戏逻辑线程):处理游戏逻辑所耗费的时间

这一步完全不考虑渲染问题,表现的是整个游戏世界在一帧之内,只在逻辑层面处理所有的变化需要花多长时间——Compute Game Context

这是一个任意的CPU逻辑,它不与渲染直接关联。如果该组很慢,通常的情况是程序员有需要修正的内容,但是它可以是与美术相关的,比如:屏幕上有太多颗粒。内置在CPU分析器中的Frontend工具可以用来研究CPU性能并且观察正运行缓慢代码。特定的、与复杂任务相关的游戏设置只能在CPU上执行,像A.I.和Navigation的设置。

Draw(绘图): 准备好所有必要的渲染所需的信息,并把它从 CPU 发送给 GPU 所耗费的时间

承接上一步,在游戏世界在逻辑层完成所有的计算和模拟后,收集渲染所需的信息,并剔除非必要信息,通知 GPU 进行画面渲染—— What to Render

这是一个与GPU上渲染设置相关的CPU逻辑。它包括图形API和绘图调用的设置,或者如果渲染代码已经以非最佳的方式修正了,它就可能与渲染代码相关。

GPU:接收到渲染所需信息之后,将像素最终的表现画在屏幕上的耗时

GPU渲染帧花费多长时间。它包括:执行图形API调用、绘图调用、着色器和后过程着色器的执行、获取纹理……这里的问题通常与资源相关,它可能是场景中类似着色器这样非常复杂的东西,或者场景中有许多不同的网格,结果就是每帧中发生太多的绘制调用。这可能会让一个专业美工或程序员找到问题根源,但是通常情况下需要一些决策——为了达到预期性能,应该做哪些权衡。这里能帮到您的最棒的工具是GPU分析器和着色器复杂性视图,这些会在之后进行讨论,同时下面还会讨论“Advance”目录中显示的特定stats。

如果一帧花的时间跟逻辑线程的时间比较接近,那么瓶颈在逻辑线程,相反如果跟渲染线程的时间比较接近,那么瓶颈在渲染线程。如果两个时间 都不接近,但跟GPU时间比较接近,那么瓶颈在显卡上。图中我们可以看到 GPU 是限制主因(三者最大的一个)。为了取得更少的 单帧 时间,在这个情形下必须先优化 GPU 的负载。

Stat Unit/Stat UnitGraph

这三个部分越接近Frame的时间,说明瓶颈就在哪部分

3.通过屏幕分辨率判断

表示为原来分辨率的10%

若游戏突然变快,则瓶颈出现在GPU,因为分辨率变低,GPU渲染量计算量变少,游戏加快。

优化方向

如果一帧花的时间跟逻辑线程的时间比较接近,那么瓶颈在逻辑线程,相反如果跟渲染线程的时间比较接近,那么瓶颈在渲染线程。如果两个时间 都不接近,但跟GPU时间比较接近,那么瓶颈在显卡上。

当然也可以使用一些第三方工具,比如intel vtume,、aqtime等,移动平台上可以使用Apple Instruments、NVIDIA Tegra System Profiler、ARM DS-5等 。

主要从CPU 和 GPU 两个方面考虑:

CPU 优化

一、Game Thread

Game Thread 造成的开销,基本可以归因于 C++ 和蓝图的逻辑处理,瓶颈常见于Tick 和代价昂贵的逻辑实现(Expensive Functionality)

1、Tick

① 大量物体同时 Tick 会严重影响 Game Thread 的耗时,因为Tick 是每一帧都会执行,计算引擎中所有东西状态的函数。

但事实是 蓝图 至少是GamePlay蓝图,它们很少需要每帧更新。

因为大部分游戏脚本都是基于事件的。或者不一定需要每帧都更新,虽然也有例外的时候,但大部分情况下你不需要那么做。

要记住如果你的场景和世界中,有许多Actor的“Tick”函数在运行。那就会严重拖累游戏的流畅度。

stat game:显示 Tick 的耗时情况

它能查阅游戏逻辑在特定情况下的每帧更新耗时。

dumpticks:可将所有正在 tick 的 actor 打印到 log 中

它能列出正在更新的所有Actor,以及它们的总数。

如果非不得已要在Tick里面添加复杂的逻辑,请慎重斟酌,是否真的需要这么做。

下面列出几个替代Tick的方案:

  1. Timeline

2. Timer

3. manual toggling of Actor Tick 手动切换Tick

手动禁用和开启Tick,比如禁用那些距离玩家太远的Actor,它们不在摄像机里,在你靠近他们的时候再重新启用。

4. reducing the tick interval 增加Tick的时间间隔.(分帧处理)

5. Event drien systems(use dispatchers!)事件驱动.

材质效果方面,假如是要做简单的淡出效果或者动画,也就是像素着色器实现这类效果,这是个很简单的例子,是要经过了开始的时间,算法就会完成剩下的工作,即再两个值之间插值,你就完全不会用到CPU线程,这都在GPU上运行。对这类效果来说,运行速度会非常快。

在游戏开发中,我们还会遇到大量的包含简单循环移动逻辑的对象,所以如果是某些和游戏玩法关系不大的逻辑:例如一些和关卡美化有关的逻辑,一些让场景更好看的逻辑,可以让材质实现,把它们改用顶点着色器实现(vertex shader),甚至用原生代码来赋予它们旋转动画。

C++的“RotatingMovementComponent”就是个很好的方法。它用于让对象自旋,但假如你还想进一步榨取性能,你就可以用顶点着色器实现它。

性能消耗大的几个函数

GetAllActorsOfClass

ForLoop

SpawnActorXXX

用这些函数的时候一定要多加谨慎。

如果真的要调用这个函数,请在基于事件的逻辑中调用它,比如在游戏启动时调用它。或者编写某种基于事件的逻辑。在只有需要访问数据的时候才调用这个函数。然后把数据全部存到数组中,请记住这些好习惯!

如果用到for loop,尤其是涉及多重循环时,记得要及时中断循环,这样等你找到需要的对象后就不用运行其余的循环了。

SpawnActor实际上官方已经优化完善了许多,但是在生成Actor的时候还是会占用很多资源,因为这同样需要占用当前平台的IO接口。所以如果你在游戏中需要频繁生成Actor,可以考虑把场景中的Actor保存在缓存池中,还有要注意如果你真的要在Tick函数中实现复杂算法的和运算请考虑使用原生代码 或者说C++。

也不需要把所有的功能都挪到C++里面,只需要把复杂代码的那部分移过去,因为可能只需要把有复杂代码的那部分移过去因为它们需要做各种复杂计算只把那部分移到C++然后把它作为函数公开给蓝图然后就可以了。

如果你制作的3D游戏会用到动画蓝图,记得要使用Fast Path,基本上动画蓝图中的这种闪电图标越多就越好。

2、复杂逻辑

需要借助 Unreal Frontend Profiler / Unreal Insights 等工具对游戏逻辑中开销较大的代码进行定位

Unreal Frontend Profiler工具

Unreal Insights 工具

二、Draw Thread (Rendering Thread)

Draw Thread 的主要开销来源于 Visibility CullingDraw Call

1、Visibility Culling

Visibility Culling 会基于深度缓存(Depth Buffer) 信息,剔除位于相机的视锥体(Frustum)之外的物体和被遮挡住(Occluded)的物体,当游戏世界中可见的物体过多,剔除所需的计算量也将变大,导致耗时过长

stat initviews:显示 Visibility Culling 的耗时情况,同时还能显示当前场景中可见的 Static Mesh 的数量(Visible Static Mesh Elements)

2、Draw Call

一般理解:CPU 准备好一系列渲染所需的信息,通知 GPU 进行一次渲染的过程

stat SceneRendering 可查看 Mesh Draw Call 的数量

即便场景中模型面数多,只要合批机制完善,Draw Call 的数量也可以非常少

相比于面数,Draw Call 对性能开销的影响要大得多

stat scenerendering命令 场景渲染

Draw调用对性能有很大的影响。每当渲染器完成时,它需要接收来自渲染线程的命令,这增加了开销。在许多情况下,DrawCall比polycount有更大的影响。在来回调用时,每当渲染器需要发送命令,或者你增加了开销它实际的影响polycount(多边形数量)影响还大。

你可以用RenderDoc来调试绘制调用.

那要如何减少DrawCall?

使用更少更大的模型,把不同模型不同对象合并成一个模型(MergeActors)。当然这也不是绝对这么做就好,例如这样做程序就不好做删减,例如玩家在高楼大厦门口,只是看一眼一楼,就要将整栋楼一起渲染,引擎没法将模型拆分成多个部分,因为这是个完整的模型。这对光照贴图很不利,如果你使用了静态光照,你就需要更大的光照贴图(larger lightmaps)以便显示更多细节。这对碰撞计算很不利,相比拆分成多个小型模型。碰撞效果的精度会差一些。当然这对内存也不利,要加载更大的资源,可能会导致卡顿。

瓶颈在逻辑线程

可以通过性能分析来确定,通过~打开控制台里面输入"stat startfile&#​34;,让它运行一会至少10s来获取一个多帧的平均值。如果时长过长,那么生成的文件就会很大。通过stat stopfile来结束性能分析。一个后缀为ue4stats的文件会在工程的路径下产生,如果是android的话会在你安装的目录下面生成 一个profile目录。如果想要查看分析结果,必须把这个文件拷贝到pc上,可以使用adb pull {ue4stats 完整路径} {pc 保存路径}来拷贝文件到pc上。

这个时候你就可以使用UnrealFrontEnd(跟UE4Editor在同级目录)来打开分析的结果,或者在UE4Edtior里面通过window-->Developper ToolsàSession Frontend,打开后切换到Profiler面板,通过load来打开ue4stats文件。

当打开后你就可以自己来查看耗费时间的地方了

如果要查看卡顿,可以在时间线上查看高峰的地方,通过选择Maximum而不是Average,这样它就会显示一些峰值,如下图所示。

GPU分析

如果是在PC平台上可以使用ProfileGPU命令或者使用快捷键Ctrl+Shift+,

可以看出影响GPU瓶颈最主要的是BasePass和PrePass ,

PrePass / Depth only pass: RenderPrePass / FDepthDrawingPolicy 。渲染遮挡物,对景深缓冲区仅输出景深。该通道可以在多种模式下工作:禁用、仅遮蔽,或完全景深,具体取决于活动状态的功能的需要。该通道通常的用途是初始化 Hierarchical Z 以降低 Base 通道的着色消耗(Base 通道的像素着色器消耗非常大)。

Base pass : RenderBasePass / TBasePassDrawingPolicy。渲染不透明和遮盖的材质,向 GBuffer 输出材质属性。光照图贡献和天空光照也会在此计算并加入场景颜色。

Lighting : 阴影图将对各个光照渲染,光照贡献会累加到场景颜色,并使用标准延迟和平铺延迟着色。光照也会在透明光照体积中累加。

Fog : 雾和大气在延迟通道中对不透明表面进行逐个像素计算。

Post Processing : 多种后期处理效果均通过 GBuffers 应用。透明度将合成到场景中。

其中BasePass 0 =不透明网格。

BasePass 1 =用于Z深度的Alpha蒙版不透明网格。

BasePass Dynamic =动画顶点,如Skeletal,GeoCache(Alembic)等。

几个值得注意的数据项:

Base Pass

Deferred Decals

Lighting

SSR(环境反射)

Translucency(半透明)

Postprocessing(后期处理效果)

Particle(粒子)

  当Base Pass很高,可以使用命令行打开Early Z Pass 可以降低 Base Pass 但同时会少量增加DRAW CALL

  检查影响GPU效率的内容查看有无超标现象

  比如分辨率、HMD SP、投影贴图大小

1:basepass消耗高的话,就需要了解下哪些模型,贴图,材质开销太大。 面数过高的模型就减面;半透明用的多的物件就斟酌下是否必要;材质是GPU消耗过高的一大元凶,比较耗的材质可以检查下节点,关闭一些非必要的效果。材质复杂程度在这里可以查看,越红的越消耗,原则上减少使用点动画和曲面细分等一些效果。

红色:意味着性能消耗非常高 绿色:意味着性能消耗最低半透明:意味着增加性能消耗

可在材质里查看着色器的说明数量尽可能减少

另外,场景里摆放的模型如果不需要参与碰撞计算的话,最好关闭碰撞,减少运算消耗。

游戏运行时在控制台里使用showflag(隐藏)命令可以帮我们快速定位具体是模型?特效?光照?等等哪个消耗高,消耗高的就优化,列举几个常用的”showflag.”命令“0”关闭“1”打开:

ScreenSpaceReflections: 切换屏幕空间的反射效果,可能会非常影响性能,对那些达到一定粗造度的像素有效

AmbientOcclusion: 屏幕空间环境遮罩

AntiAliasing: 切换各种抗锯齿(TemporalAA 和 FXAA,FXAA更快,但效果较差)

Bloom: 影响那些受到 lensflares 和 bloom 功能的画面。

DeferredLighting: 切换所有延迟光照通道。

DirectionalLightsPointLightsSpotLights: 切换不同的光照类型(检查光照类型影响性能时有用)

DynamicShadows: 切换所有的动态阴影(阴影贴图的渲染,以及阴影的过滤和投影)

GlobalIllumination: 切换预烘培和动态间接光照(LPV)

LightFunctions: 切换光照函数渲染

PostProcessing:  切换所有后处理效果

ReflectionEnvironment: 切换环境反射效果

Refraction: 切换折射效果

Rendering: 切换整体渲染

Decals: 切换贴花渲染

LandscapeBrushes StaticMeshesSkeletalMeshes Landscape: 轮询切换几种不同的几何体的渲染

Translucency:  切换透明度渲染

Tessellation:切换曲面细分(仍将运行曲面细分 shader,但生成更多三角面)

IndirectLightingCache: 切换是否动态物体或者静态物体具有使用间接光照 Cache 时无效的光照贴图。

Bounds : 显示编辑器中当前选中物体的边界框。

VisualizeSSR :屏幕空间反射像素显示为亮橙色是计算较慢的区域

关闭stuff查看效率 r.SetRes: 调整渲染分辨率

r.VSync 开启/关闭垂直同步(可能依赖于是否原生全屏)。

r.AllowOcclusionQueries 用于禁用遮挡(可以让场景运行的更慢)。

r.TiledDeferredShading 能够关闭基于 Tile 的延迟光照技术(GPU粒子的光影则没有退回方法)

.TiledDeferredShading.MinimumCount 能够调整使用多少灯光应用在基于 Tile 的延迟光照技术(视觉上并没有差异但性能会有不同)

r.SeparateTranslucency 这是一个用于修复半透明情况下景深的问题的功能,如果不需要的时候可以把它关闭,并有其他影响(查阅 SceneColor)。

r.TonemApper.GrainQuantization 用于关闭在 TonemApper 中添加的噪点来避免 Color Banding,由于 8bit 量化和较小的质量改进在输出为 10:10:10 并不必须。

r.SceneColorFormat 能够选用不同的 SceneColor 格式(默认是 64bit 的最佳质量,并支持屏幕空间子表面散射)。

FX.AllowGPUSorting 禁用粒子排序(在大量粒子的使用可以妥协使用)。

FX.FreezeParticleSimulation 禁止粒子的更新。

r.MaxQualityMode: 最高质量

r.MipMapLODBias: Mipmap Bias

r.MobileContentScaleFactor: 画面缩放比

r.ScreenPercentage: 用于减小内部实际渲染分辨率,画面会在重新放大

r.ShadowQuality: 移动端Stationaary灯光动态阴影质量,调整其值查看帧速变化,以判断瓶颈

r.Shadow.MaxResolution: 移动端Movable灯光动态阴影质量,调整其值查看帧速变化,以判断瓶颈

StatMemory:提供关卡中内存使用情况

2:灯光消耗高的话,需要检查动态光照数量(固定光也可以投射动态光照),是否有过多重叠的照射区域,照射范围参数是否开的太大。由于静态光照Build后已将灯光信息存储进了Lightmap,游戏中不再计算,所以灯光的主要消耗来自动态光源。先在世界大纲里查看所有灯光类型,确定有几盏动态光和固定光,前面有红点的是动态,黄点的是固定。

再进一步查看固定光的照射范围的重叠部分是否太多,重叠的越多,交集处越亮越红。用灯的原则是能不用动态光就不用(消耗主要来自被投照射的Mesh),灯光照射范围尽量不重叠,且同一个地图里固定光不能超过4盏。

光照复杂度视图模式基于动态光源的数量来对场景进行着色。

黑色:意味着没有收到动态光源影响。

不同颜色:从绿到红,表示受到动态光源的影响逐步增加。

关闭灯光的投射动态阴影也可以降低一些消耗,甚至一些灯光可以直接关闭投射阴影功能。

3:后期处理是另一个GPU消耗过高的元凶,需要慎用,原则是尽可能的把一些不必要的参数关掉,尤其是SSR,后期AO,Bloom等。一些参数默认会自带一些数值,没必要的全部清零,抗锯齿模式切换成FXAA。 使用Alt+0/Light Map Density可以对场景中的光照贴图密度进行分析。

其他数据分析 ShadowDepths

这个生成通过光源进行阴影投射的深度数据的pass。

作用与这里的消耗主要受到开启了投影的光的数目、动态光照影响的面数、以及阴影的质量的影响。

阴影的质量可以通过Sg.shadow quality进行全局的调节。

Memory-bound

如果有大量的材质使用了不同的贴图,导致Texture Sample的数量爆炸的话,就会自然的变成瓶颈。UE4有使用Texture Streaming,如果存储空间爆炸了的话,就会出现贴图模糊的情况,这时候可以使用Stat Streaming指令进行分析。

PrePass DOM_…

EarlyZPass,对非透明物体进行的早期的深度计算。

数据似乎被用于遮蔽计算,如果不使用Dbuffer Decals的话可以关掉。但是早期的深度计算可以在BasePass之前进行遮蔽计算,能让basepass以及之后所有的通道的计算减少很多。而且即便在这里不进行深度计算,会影响这里的运算量的变量依然会作用与后面的深度计算阶段,因此关闭EarlyZPass还是需要多做考虑的。另外要使用DBuffer Decals的话必须使用Opaque and masked的zpass计算,否则应该会出现奇怪的现象。

性能上受到非透明物体的面数的影响,同时根据上面的选项不同也受到Masked的材质的影响。

HZB

Hierarchical Z-Buffer,用于计算HZB遮蔽,同时也会被屏幕空间内的射线演算使用,例如屏幕空间反射计算、AO等。同时被用于Mip的设置。受屏幕空间的大小影响。据官方描述,HZB拥有较高的固定性能消耗,每个物体所造成的消耗较小。可以通过r.HZBOcclusion来调整运算的类型。

Base Pass

对非透明的物体进行演算并填充到GBuffer,使用缓冲区可视化模式可以在视图中看到效果。几乎所有的延迟渲染都受到其影响,因此才叫基础通道。

其计算结果包括base color, metallic, specular, roughness, normal, sss profile,并且Decals、Fog以及Velocity的计算也在此处。其开销受到屏幕空间尺寸、物体数量、面数、Decals的数量、Shader的复杂度,生成的过程中包含光照贴图的推送,因此也会受到光照贴图的大小的影响。可以通过Stat rhi指令检查各种贴图和triangle的消耗。

另外,前向渲染的光照也在这里进行,此时光照的数量也会影响到这里的消耗。

Translucency

半透明的材质以及光照演算,通过Stat gpu中的Translucency and Translucent Lighting可以进一步查看。消耗受到屏幕空间大小以及屏幕内的半透明物体的数量影响,半透明物体的光照计算要尽量减少过度绘制。以及避免过多的需要进行半透明光照计算的光的数量。

Particle Simulation/Injection

粒子模拟,这里只展示GPU粒子的消耗,性能主要受粒子数量以及是否开启了基于深度的粒子碰撞影响。粒子的优化主要通过LOD以及设计上的优化进行。

Post process

UE4的后期处理功能比较多,AA、DOF、自动曝光以及很多其他的功能都在其中。每种PP特效都会产生额外的性能消耗,如果使用了PP材质的话,其复杂度也会影响性能。

Relection Envirionment

反射捕捉控件的计算缓存可以将显示模式调整为Reflections来查看各个控件对缓存的影响通常的建议是,放一个大范围的低精度反射捕捉,然后在需要的地方尽量不重叠的放置高精度的捕捉控件。影响性能的主要就是捕捉控件的数量及范围,也受屏幕空间的大小影响。

Render Velocities

速度主要用于TAA以及Motion Blur,受到移动物体的数量以及其面数的影响。主要的优化策略是使用LOD。

Screen Space Reflections

屏幕空间反射通过以下连个指令来进行调节:

r.ssr.maxroughness 0.0-1.0

r.ssr.quality 0..4其中Maximum roughness决定着计算的范围的大小。

也可以使用一些第三方工具来测试,pc平台上如 Intel GPA、Nvidia NSight visual Studio edition,移动平台比如高通的adreno profiler、NVIDIA Tegra Graphics Debugger、ImgTec PVRTune and PVRTrace、ARM Mali Graphics Debugger等,苹果的XCode等均可以用来分析。

一些常用的命令

  • stat unit

  • stat scenerendering

stat engine

stat initviews

stat game

Stat Slow

ViewMode ShaderComplexity

Stat UnitGraph完整的stat命令参考https://docs.unrealengine.com/latest/CHN/Engine/Performance/StatCommands/index.html

几个对分析最有用的变量:

  • r.SetRes 改变屏幕,或窗口的分辨率。

  • r.VSync 开启/关闭垂直同步(可能依赖于是否原生全屏)。

  • r.ScreenPercentage 用于减小内部实际渲染分辨率,画面会在重新放大。

  • r.AllowOcclusionQueries 用于禁用遮挡(可以让场景运行的更慢)。

  • r.TiledDeferredShading 能够关闭基于 Tile 的延迟光照技术(GPU粒子的光影则没有退回方法)。

  • r.TiledDeferredShading.MinimumCount 能够调整使用多少灯光应用在基于 Tile 的延迟光照技术(视觉上并没有差异但性能会有不同)。

  • Pause 暂停游戏或者 Matinee(分析时更加稳定,但禁用了 Update/Tick)。

  • Slomo 能够对游戏进行加速或者减速播放。

  • r.VisualizeOccludedPrimitives 显示被裁剪掉的物件的外盒框。

  • StartFPSChart StopFPSChart 请看下文。

  • r.SeparateTranslucency 这是一个用于修复半透明情况下景深的问题的功能,如果不需要的时候可以把它关闭,并有其他影响(查阅 SceneColor)。

  • r.TonemApper.GrainQuantization 用于关闭在 TonemApper 中添加的噪点来避免 Color Banding,由于 8bit 量化和较小的质量改进在输出为 10:10:10 并不必须。

  • r.SceneColorFormat 能够选用不同的 SceneColor 格式(默认是 64bit 的最佳质量,并支持屏幕空间子表面散射)。

  • FX.AllowGPUSorting 禁用粒子排序(在大量粒子的使用可以妥协使用)。

  • FX.FreezeParticleSimulation 禁止粒子的更新。

  • r.SSR.MaxRoughness 调整屏幕空间反射(SSR)粗造度的最大值,并覆盖后处理中的该设置。请查阅 Show Flag VisualizeSSR。

命令行选项

有些功能可以在命令行中进行关闭,比如 UE4.exe –NoSound

几个对分析比较有用的开关是:

  • -NoSound 禁用声音和音乐系统。

  • -NoTextureStreaming

  • 关闭贴图 steaming(对于隔离问题时很有帮助)。

  • -NoVerifyGC 否则需要预期在 Release 版本中每 30 秒会遇到的性能波动。

  • -NoVSync 能够更快的渲染但会导致画面撕裂,尤其是在高帧数下。

  • -Streaming 在使用 StartFPSChart/StopFPSChart 很有用,能够从一个非 windows 设备上来获取数据并用于进一步检测(假设我们是实时的 cook 数据)。

GPU 优化

一、GPU Thread

1、顶点处理(Vertex-bound) 导致的瓶颈

①:Dynamic Shadow

目前动态阴影(Dynamic Shadow)的生成主要依赖 Shadow MApping,一种在光栅化阶段计算阴影的技术,Shadow MApping 每生成一次阴影需要进行两次光栅化,因此当顶点数过多(可能源于多边形数量巨大,也可能源于不适当的曲面细分) 时,Dynamic Shadow 将成为 GPU 在光栅化阶段的一大性能瓶颈

ShowFlag.DynamicShadows 0: 使用该指令可关闭场景内的动态阴影(0表示关闭,1表示开启),可在开启和关闭两种状态间反复切换,查看卡顿情况是否发生明显变化,以此判断 Dynamic Shadow 是否确实造成了巨大开销

2、着色(Pixel-bound) 导致的瓶颈

运行指令 r.ScreenPercentage 50,表示将渲染的像素数量减半(也可替换成其他 0-100 之间的数),观察卡顿现象是否明显减缓,以此判断瓶颈是否 Pixel-bound

①:Shader Complexity

显示对每一个像素所执行的着色指令数量,数量越多,消耗越大

场景中存在过多的半透明物体(Translucent Object),会显著增加 Pixel Shader 的计算压力,使用 stat SceneRendering 可查看 Translucency 的消耗情况;使用 ShowFlag.Translucency 0 来关闭(0表示关闭,1表示开启)所有半透明效果

当着色器(材质连线)的实现逻辑过于复杂或低效时,也会导致较高的 Shader Complexity

在 Viewport 中选择 Optimization Viewmodes → Shader Complexity,可视化 Shader 造成的开销

3、Quad Overdraw 重复渲染绘制

着色期间 GPU 的大部分操作不是基于单个像素,而是一块一块地绘制,这个块就叫 Quad,是由 4 个像素 (2 × 2) 组成的像素块

当模型存在较多狭长、细小的三角形时,有效面积较小,但可能占用了很多 Quad,Quad 被多次重复绘制,会导致大量像素参与到无意义的计算中,引起不必要的性能开销

进入 Optimization Viewmodes → Quad Overdraw,显示 GPU 对每个 Quad 的绘制次数

4、Light Complexity 关照重复

场景内的动态光源(Dynamic Lights) 数量过多时,会产生大量动态阴影(Dynamic Shadow),如上述所说,容易引起较大开销

动态光源的半径过大,导致多个光源的范围出现大量交叠,也可能导致严重的 Overdraw 问题

进入 Optimization Viewmodes → Light Complexity,查看灯光引起的性能开销

5、内存(Memory-bound)引起的瓶颈

有时性能瓶颈还在于过高的内存占用,其中最常见的是大量的纹理(Texture)加载和采样

使用 stat streaming overview,查看当前纹理对内存的占用情况

对于纹理的优化,后续将另开新篇加以详细介绍

性能优化实践

https://blog.csdn.net/hechao3225/article/details/113742336