|
导语:
转载了三篇文章,各有不同的侧重点,写的都很好
一、性能瓶颈定位
原文地址:小能猫吃牙膏:UE4 性能 - (一)瓶颈定位
- P.S. 对于某个具体问题,我个人偏向于遵循 WHY → WHAT → HOW 的思考方法(重要性逐级递减) 加以理解。因为如果找不到做某件事情的意义(WHY)所在,或是对这件事情本身的定义(WHAT)都模棱两可,那么即便经过大量实践(HOW),知识体系也难以成型,且一旦相应的工作被不可避免地中断,已经掌握的知识也容易被迅速遗忘、难以回溯。
WHY
stat fps
- 首先,感知上我肯定是觉得这个游戏很“卡”,才会萌生要搞清楚它 “为什么卡” 的问题,这个 “卡” 的感觉,可以用 UE4 里最常用的命令之一来描述:stat fps
显示 fps 与当前帧的总耗时
stat unit
- 其次,之所以需要花精力去“定位”,是由于造成卡顿的原因有多种,而在 UE4 体系中,造成卡顿的因素大致分为三类,隐含在另一个最常用的命令之中:stat unit
- Frame: 即一帧所耗费的总时间,这个值越大,fps 就越小,二者相乘恒等于 1
- Game: 处理游戏逻辑所耗费的时间
这一步完全不考虑渲染问题,表现的是整个游戏世界在一帧之内,只在逻辑层面处理所有的变化需要花多长时间——Compute Game Context
- Draw: 准备好所有必要的渲染所需的信息,并把它从 CPU 发送给 GPU 所耗费的时间
承接上一步,在游戏世界在逻辑层完成所有的计算和模拟后,收集渲染所需的信息,并剔除非必要信息,通知 GPU 进行画面渲染—— What to Render
- GPU: 接收到渲染所需信息之后,将像素最终的表现画在屏幕上的耗时
WHAT
Overview
- 瓶颈定位,就是要找到造成性能开销的最大元凶,也就是确定优化的基本方向,才能深入和落实到细节层面,进行后续的分析和优化工作。而要搞清楚开销主要发生在哪个阶段,不可避免地还是要对 Game, Draw, GPU 对应的三类线程以及它们之间的关系有更详细的认识
Game Thread, Draw Thread, GPU Thread 的关系
- stat unit 显示的数值,都是在 一帧之内 的耗时,那么在这一帧期间,Game, Draw, GPU 这三者是如何先后执行的,可以参照下图加以理解
- 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 上运行
- 如果 GPU Thread 率先完成了它的工作,而其他二者仍在工作中(e.g. 已经绘制好了当前帧,但下一帧的数据还没拿到),那么 GPU 就会等待 CPU 的指令而导致下一帧的画面姗姗来迟;反之如果 GPU 耗时更严重,导致 CPU 输送的数据没有被及时处理,使得画面没能被及时渲染,同样会导致卡顿
HOW
Overview
- 要定位开销发生在哪个线程,最直接的方法是根据 stat unit 给出的信息,比较 Game, Draw, GPU 三者哪一个与 Frame 的数值最接近(如上述所说,一帧的总耗时取决于三者中的最大值),则它就是造成开销的主要因素
- 利用 UE 内部丰富、强大的各种命令,还可定位出开销具体发生在哪个线程的哪个阶段
- 同时善用 控制变量法,对判断加以验证
Game Thread
- Game Thread 造成的开销,基本可以归因于 C++ 和蓝图的逻辑处理,瓶颈常见于Tick 和代价昂贵的逻辑实现(Expensive Functionality)
- Tick
- 大量物体同时 Tick 会严重影响 Game Thread 的耗时
- stat game:显示 Tick 的耗时情况
- dumpticks:可将所有正在 tick 的 actor 打印到 log 中
- 复杂逻辑
- 需要借助 Unreal Frontend Profiler / Unreal Insights 等工具对游戏逻辑中开销较大的代码进行定位,后续将详细说明它们的使用方法
Draw Thread (Rendering Thread)
- Draw Thread 的主要开销来源于 Visibility Culling 和 Draw Call
- Visibility Culling
- Visibility Culling 会基于深度缓存(Depth Buffer) 信息,剔除位于相机的视锥体(Frustum)之外的物体和被遮挡住(Occluded)的物体,当游戏世界中可见的物体过多,剔除所需的计算量也将变大,导致耗时过长
- stat initviews:显示 Visibility Culling 的耗时情况,同时还能显示当前场景中可见的 Static Mesh 的数量(Visible Static Mesh Elements)
- Draw Call
- 一般理解:CPU 准备好一系列渲染所需的信息,通知 GPU 进行一次渲染的过程
- 想象 CPU 指挥 GPU 拿起一支笔刷,蘸好颜料,给某个(或者某一些)多边形(polygon)涂上颜色,来自 CPU 的这条指令就是一次 Draw Call
- 很多情况下,不同的多边形(可能属于不同的 mesh)需要的是同一种颜色(材质),那么在给笔刷蘸好颜色之后,可以一次性给这些多边形上色,而不需要做无谓的重复操作,这个过程就叫做 合批(batching)
- UE 官方解释:a group of polygons sharing the same material (一组使用相同材质的多边形)
- 这个解释虽然准确,但乍一看非常抽象。首先举例来理解:场景中有 100 个多边形(polygon),其中 10 个共同使用材质 A,10个共同使用材质 B,剩余 80 个共同使用材质 C,100 个多边形被分成了 3 组,于是 Draw Call 就等于 3
- 结合之前的一般理解,也可以理解为:CPU 命令 GPU 将笔刷蘸上某一材质对应的颜料,然后一次性给若干个 polygon 上色,这条 CPU 下达的指令就是一次 Draw Call,而这些 polygon 就是 one group of polygons sharing the same material,有多少组这样的 polygon,就等于发生了多少次 Draw Call
- stat SceneRendering 可查看 Mesh Draw Call 的数量
- 即便场景中模型面数多,只要合批机制完善,Draw Call 的数量也可以非常少
- 相比于面数,Draw Call 对性能开销的影响要大得多
GPU Thread
- 顶点处理(Vertex-bound) 导致的瓶颈
- Dynamic Shadow
- 目前动态阴影(Dynamic Shadow)的生成主要依赖 Shadow Mapping,一种在光栅化阶段计算阴影的技术,Shadow Mapping 每生成一次阴影需要进行两次光栅化,因此当顶点数过多(可能源于多边形数量巨大,也可能源于不适当的曲面细分) 时,Dynamic Shadow 将成为 GPU 在光栅化阶段的一大性能瓶颈
- ShowFlag.DynamicShadows 0: 使用该指令可关闭场景内的动态阴影(0表示关闭,1表示开启),可在开启和关闭两种状态间反复切换,查看卡顿情况是否发生明显变化,以此判断 Dynamic Shadow 是否确实造成了巨大开销
- 着色(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 造成的开销
- Quad Overdraw
- 着色期间 GPU 的大部分操作不是基于单个像素,而是一块一块地绘制,这个块就叫 Quad,是由 4 个像素 (2 × 2) 组成的像素块
- 当模型存在较多狭长、细小的三角形时,有效面积较小,但可能占用了很多 Quad,Quad 被多次重复绘制,会导致大量像素参与到无意义的计算中,引起不必要的性能开销
- 进入 Optimization Viewmodes → Quad Overdraw,显示 GPU 对每个 Quad 的绘制次数
- Light Complexity
- 场景内的动态光源(Dynamic Lights) 数量过多时,会产生大量动态阴影(Dynamic Shadow),如上述所说,容易引起较大开销
- 动态光源的半径过大,导致多个光源的范围出现大量交叠,也可能导致严重的 Overdraw 问题
- 进入 Optimization Viewmodes → Light Complexity,查看灯光引起的性能开销
- 内存(Memory-bound)引起的瓶颈
- 有时性能瓶颈还在于过高的内存占用,其中最常见的是大量的纹理(Texture)加载和采样
- 使用 stat streaming overview,查看当前纹理对内存的占用情况
- 对于纹理的优化,后续将另开新篇加以详细介绍
二、性能分析
原文链接:唤作无名:UE4性能分析和优化
Profiling and Optimization in UE4
介绍理解什么会导致游戏的性能问题。
概述可以帮助您找到瓶颈的内置工具。
分享一些提示,你应该开始寻找问题。
您还可以使用stat unitGraph,它可以显示线形图回放。主要用于发现重复的故障。
在理想情况下,当你做性能分析时,当你寻找游戏存在的瓶颈时,游戏的运行环境越接近目标硬件和目标平台,你活的的数据就越准确。
如果可以的话,要避免在编辑器里面进行性能分析。特别是主机和手机平台,因为你不是在实际的运行平台上做调试。
PC和主机它们在渲染方便非常类似。
但是移动平台它采用正向渲染路径,情况完全不同。一定要使用合适的打包版本。并且在目标硬件上做测试。
如果你是做PC游戏,并且必须在编辑器里做性能分析,
1.记得在独立模式下运行(standalone Mode)
2.记得最小化编辑器
3.切记关闭帧率平滑turn off Frame rates smoothing
4.并且用控制台命令r.VSync=0关闭垂直同步。
框架分析Analysis of a frame
这几个线程是并行运行的。
当游戏按照图中的流程执行时,
Game线程会计算所有的游戏逻辑,game线程计算的所有数据都会被储存起来,并被Draw线程使用,它会算出不需要渲染的内容(这些内容不会显示在屏幕上)。完了之后GPU线程会在屏幕上实际渲染出最终的像素。
所有的东西 从游戏逻辑所有的Actor的位置,场景中所有物体的位置 动画,实际动画帧,物理效果,AI,一切与场景最终效果有关的计算,在进行处理时都会在Game线程中计算。这步完成后,整个游戏世界就被计算完毕了。引擎就会知道什么东西该干什么。在下一步“Draw”线程他会过滤掉(剔除掉)所有不在相机范围内的却还需要引擎去渲染的对象,然后创建一个列表(包含所有的对象Object,着色器Shaders,材质Materials,纹理贴图Textures和所有需要发送给GPU的数据)。然后GPU会处理这些数据(包括顶点Vertex,着色器Shaders,纹理Textures等各种数据),然后会在屏幕上绘制最终像素。
generate chart over a period of time在一段时间内生成图表
StartFPSChart和StopFPSChart
它会主要获取stat unit的输出结果记录在一个文本文件里,也就是CSV文件。(其实会有三个)
用表格的软件打开(例如Excel),然后创建一个图表(Create Graph)去分析
可以帮助你找到游戏过场动画中出现卡顿的原因或者你甚至可以执行一些自动化的任务(例如让摄像机扫拍整个关卡),然后记录所有数据,并导出到CSV,然后你可以每周或者每晚定期做一次检查是否有潜在问题或导致性能不稳定的各种问题。
CPU优化
stat Startfile 和 stat Stopfile
会在项目底下这个路径生成几个数据文件。
ProfileGPU命令
Unreal Insights 工具
Game Thread 游戏线程
通常“游戏”线程中的性能问题,都是“Tick”函数中包含了复杂的逻辑。
因为Tick 是每一帧都会执行,计算引擎中所有东西状态的函数。
但事实是 蓝图 至少是GamePlay蓝图,它们很少需要每帧更新。
因为大部分游戏脚本都是基于事件的。或者不一定需要每帧都更新,虽然也有例外的时候,但大部分情况下你不需要那么做。
要记住如果你的场景和世界中,有许多Actor的“Tick”函数在运行。那就会严重拖累游戏的流畅度。
Stat game命令
它能查阅游戏逻辑在特定情况下的每帧更新耗时。
Dumpticks命令
它能列出正在更新的所有Actor,以及它们的总数。
如果非不得已要在Tick里面添加复杂的逻辑,请慎重斟酌,是否真的需要这么做。
下面列出几个替代Tick的方案:
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上运行。对这类效果来说,运行速度会非常快。
17:33
在游戏开发中,我们还会遇到大量的包含简单循环移动逻辑的对象,所以如果是某些和游戏玩法关系不大的逻辑:例如一些和关卡美化有关的逻辑,一些让场景更好看的逻辑,可以让材质实现,把它们改用顶点着色器实现(vertex shader),甚至用原生代码来赋予它们旋转动画。
C++的“RotatingMovementComponent”就是个很好的方法。它用于让对象自旋,但假如你还想进一步榨取性能,你就可以用顶点着色器实现它。
性能消耗大的几个函数
GetAllActorsOfClass
ForLoop
SpawnActorXXX
用这些函数的时候一定要多加谨慎。
如果真的要调用这个函数,请在基于事件的逻辑中调用它,比如在游戏启动时调用它。或者编写某种基于事件的逻辑。在只有需要访问数据的时候才调用这个函数。然后把数据全部存到数组中,请记住这些好习惯!
如果用到for loop,尤其是涉及多重循环时,记得要及时中断循环,这样等你找到需要的对象后就不用运行其余的循环了。
SpawnActor实际上官方已经优化完善了许多,但是在生成Actor的时候还是会占用很多资源,因为这同样需要占用当前平台的IO接口。所以如果你在游戏中需要频繁生成Actor,可以考虑把场景中的Actor保存在缓存池中,还有要注意如果你真的要在Tick函数中实现复杂算法的和运算请考虑使用原生代码 或者说C++。
也不需要把所有的功能都挪到C++里面,只需要把复杂代码的那部分移过去,因为可能只需要把有复杂代码的那部分移过去因为它们需要做各种复杂计算只把那部分移到C++然后把它作为函数公开给蓝图然后就可以了。
如果你制作的3D游戏会用到动画蓝图,记得要使用Fast Path,基本上动画蓝图中的这种闪电图标越多就越好。
DrawThread绘制线程
stat scenerendering命令 场景渲染
Draw调用对性能有很大的影响。每当渲染器完成时,它需要接收来自渲染线程的命令,这增加了开销。在许多情况下,DrawCall比polycount有更大的影响。在来回调用时,每当渲染器需要发送命令,或者你增加了开销它实际的影响polycount(多边形数量)影响还大。
你可以用RenderDoc来调试绘制调用.
那要如何减少DrawCall?
使用更少更大的模型,把不同模型不同对象合并成一个模型(MergeActors)。当然这也不是绝对这么做就好,例如这样做程序就不好做删减,例如玩家在高楼大厦门口,只是看一眼一楼,就要将整栋楼一起渲染,引擎没法将模型拆分成多个部分,因为这是个完整的模型。这对光照贴图很不利,如果你使用了静态光照,你就需要更大的光照贴图(larger lightmaps)以便显示更多细节。这对碰撞计算很不利,相比拆分成多个小型模型。碰撞效果的精度会差一些。当然这对内存也不利,要加载更大的资源,可能会导致卡顿。
三、工具篇
原文链接:UE4 性能优化方法(工具篇) - 风恋残雪 - 博客园
本文依据UE4官方文档以及官方博客等总结而来,可能不全面,后面会陆续添加。内置工具的详细说明请参考官方文档。
游戏帧率很低,或者有卡顿的现象,可能会有很多原因,这时候不要乱猜,比如是不是人物太多了或者渲染的东西太多了,这样猜意义是不大的,可能会浪费很多时间,但是总找不到点上,当然如果运气好也可以找到瓶颈,这个时候我们可以借助相应的工具来查找性能瓶颈。此处我们仅以UE4来展开讲解。
Frame时间是产生一帧花的总时间,由于逻辑线程(Game)和渲染线程(Draw)在一帧结束的时候需要同步,一帧花的时间经常跟其中的一个线程花的时间 相近。GPU时间测量了显卡渲染当前场景花的时间。由于 GPU时间是跟当前帧同步的,所以它跟一帧花的时间也基本差不多。
如果一帧花的时间跟逻辑线程的时间比较接近,那么瓶颈在逻辑线程,相反如果跟渲染线程的时间比较接近,那么瓶颈在渲染线程。如果两个时间 都不接近,但跟GPU时间比较接近,那么瓶颈在显卡上。
当然也可以使用一些第三方工具,比如intel vtume,、aqtime等,移动平台上可以使用Apple Instruments、NVIDIA Tegra System Profiler、ARM DS-5等 。
瓶颈在逻辑线程
可以通过性能分析来确定,通过~打开控制台里面输入"stat startfile",让它运行一会至少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+,
也可以使用一些第三方工具来测试,pc平台上如 Intel GPA、Nvidia NSight visual Studio edition,移动平台比如高通的adreno profiler、NVIDIA Tegra Graphics Debugger、ImgTec PVRTune and PVRTrace、ARM Mali Graphics Debugger等,苹果的XCode等均可以用来分析。
一些常用的命令
- 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 数据)。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|