Unreal Engine 虚幻引擎,性能优化

一、Frame 帧

        帧的时间并不是其他数字的总和,并不是其他时间的总和。原因就是,这些线程,它们都是不同的线程,是并行运行的,每个线程都是按照顺序的,它们需要上一个线程的内容和结果。

二、理想情况下,做性能分析时,当寻找游戏存在的瓶颈时,游戏的运行环境越接近目标硬件和目标平台,获得的数据越准确。

        如果可以的话,要避免在编辑器里进行性能分析。一定要使用合适的打包版本,并且在目标硬件上运行。如果开发的是PC游戏,并且必须在编辑器里做性能分析,记得在独立模式(standalone mode)下运行,记得最小化编辑器,还有切记关闭帧率平滑(frame rates smoothing),并用控制台命令r.VSync=0关闭垂直同步。

三、线程

        它们是并行运行的,当游戏按图中的流程执行时,游戏线程会计算所有的游戏逻辑。“Game”(游戏)线程计算的所有数据,都会被储存起来,并被“Draw”(绘制)线程使用,它会算出所有不需要渲染的内容,这些内容不会显示在屏幕上,在这一步完成后。“GPU”线程会在屏幕上实际渲染出最终的像素。

如果这些线程,这些数据,如果它们计算每个线程的这些帧都需要耗费时间,那下一个步骤当然就要等待上一个步骤来完成工作,这样它才能开始工作。

四、Analysis of a frame ,“CPU”线程,“Game”线程

        “CPU”线程,“Game”线程,正如名字所示,负责整个游戏的模拟,所有的东西,从游戏逻辑,所有Actor的位置,场景中所有物体的位置,动画,实际动画帧,物理效果,AI,一切与场景最终效果有关的计算。与场景在某一帧中最终状况有关的计算,在进行处理时,都会在“Game”线程中计算。这步完成后,整个游戏世界就被计算完毕了,引擎就会知道什么东西该干什么,在下一步“Draw”(绘制)线程它会被过滤掉,它会剔除掉所有不在摄像机范围内,却还需要引擎去渲染的对象。我们有很多方法来做到这点,基本来说,所有不在摄像机范围内的东西,都会被我们过滤掉。然后我们创建一个列表,包含所有的Object对象、Shaders着色器、Materials材质、Textures纹理,所有需要发送给GPU的数据,然后GPU会处理这些数据。包括Vertex顶点、Shaders着色器、Textures纹理等各种数据,然后在屏幕上绘制最终的像素。

        4.1、有一种工具可以使用:有两条命令StartFPSChartStopFPSChart,它主要会获取stat unit的输出结果,把它们全程记录到一个文本文件里,也就是CSV文件。你可以用自己常用的表格编辑器打开它,然后创建一个图表,这非常有用,例如,可以帮助你找到游戏过场动画中出现卡顿的原因。或者,你甚至可以执行一些自动化任务。例如让摄像机扫拍整个关卡,然后记录所有数据,并导出到CSV文件中。你可以每周或者每晚定期做一次,检查是否有潜在问题,或导致性能不稳定的各种问题。

        4.2、CPU Profiler也非常有用,你可以利用stat Startfilestat Stopfile记录引擎的所有操作,引擎侦测到的每条命令,甚至是绘制调用的情况。这适用于任何开发版本或测试版本,所以你也可以在移动和主机平台上这么做。最棒的一点在于,这还会留下一个记录文件,你可以在电脑上加载它。在虚幻前段打开,然后你就能按照Tick组分析,或者逐帧分析,深入探索游戏线程。找到可能导致故障和性能问题的地方。

        4.3、Unreal Insights有点类似Profiler,但这是个独立应用,独立程序。它可以在电脑上作为另一个进程运行,或者,你可以在另一个电脑上运行它。在使用体验上有一些改进,它能利用代码中的踪迹,让你更容易的添加自己的性能分析数据,它还能远程记录数据。

五、Game Thread 游戏线程

        要计算所有的逻辑,变换,动画,对象位置基本上与游戏逻辑有关与构建世界有关的一切,都要有”Game”游戏线程来计算,都要在你的CPU上进行计算。

         5.1、通常“Game”线程中的性能问题,元凶就是“Tick”函数中的复杂逻辑,众所周知“Tick”每一帧都要执行,它实际上就是计算引擎中everything所有东西状态的函数。但事实是,Blueprints蓝图,至少是Gameplay蓝图,它们很少需要每帧更新,因为大部分gameplay scripting游戏脚本都是event based基于事件的,或者不一定需要每帧都更新。当然,也有例外情况,但大部分情况下,你不需要那么做。

         5.2、要记住,如果你的场景和世界中有许多Actor在“Tick”函数,那就会严重拖累游戏的流畅度。你可以用一些命令,stat game很好用,它能查阅游戏逻辑在特定情况下的每帧更新耗时。还有dumpticks命令它能列出正在更新的所有Actor以及它们的总数。

        5.3、Alternatives to Tick:在tick函数中,添加复杂逻辑之前,要谨慎评估是否真的需要这么做,去每帧更新逻辑。有很多替代方法,你可以采用基于事件的方法,因为有些游戏逻辑未必会像你假设的那样频繁更新。或者,如果真的需要以固定频率更新,你可以使用计时器,甚至降低蓝图Tick函数的调用频率。你可以使用计时器,比如也许可以每帧四分之一,甚至十分之一秒更新一次。这会有助于你,减少性能开销。你还可以使用TimeLine,你可以手动禁用Tick,比如禁用那些距离玩家太远的Actor,它们不在摄像机里。如果它们太远,就停止每帧更新,在你靠近它们的时候再重新启用。

        5.4、性能开销很大的函数,尽量避免使用那些本来就开销巨大的函数,假如你真的要使用这些函数,一定要多加谨慎。如果你真的要调用这个函数,请在event based基于事件的逻辑中调用它。比如在游戏启动时调用它,或者编写某种基于事件的逻辑,在只有需要访问数据的时候才调用这个函数,然后把数据全部缓存到数组中,所以请记住这些好习惯。

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

        SpawnActor 在生成Actor时,还是会占用很多资源,因为这同样需要占用当前平台的IO接口,如果你在游戏中需要频繁生成Actor,你就要多加小心了。你可以考虑把场景中的Actor保存到缓存池中,还有,你要注意,construction script构造脚本会增加每个Actor的生成耗时。

        如果你真的要在Tick函数中,实现复杂算法和运算,请考虑使用原生代码,或者C++。屏幕中的不是游戏逻辑代码,而是源自《堡垒之夜》的某个Animation Blueprint动画蓝图,不过动画蓝图有点类似于每帧执行复杂的数学运算。在使用动画蓝图时,你会用到一些向量,然后进行数学运行获得算法的结果,并用它来更改动画。所以,如果你真的在Tick函数中实现复杂的运算,可以考虑使用C++,使用原生代码,你可以试着使用蓝图原生化功能来这么做。

如果你想精益求精,实现最佳的性能表现,那就用C++和原生代码。 

六、Draw thred 绘制线程

        Draw thred会获得游戏世界的计算结果,也就是“Game”线程的计算结果,这个结果包含了整个游戏世界的逻辑计算结果。Draw thred会过滤掉所有不需要渲染的内容,其中会用到多种技巧,比如“Frustum culling”视锥剔除,根据平台不同,我们还能使用hardware occlusion queries硬件遮挡查询,这在移动平台上行不通。一般来说,场景中的对象越多问题就越多。这在开放式或大型场景中尤为重要,总的来说,你应当知道,如今的硬件极限是渲染10000到15000个对象。接着,在引擎获知要渲染哪些对象后,它就会创建一个命令列表,并发送给GPU让它去渲染,这些命令会渲染场景的这一部分。一个接一个的draw call绘制调用,那什么是draw call呢?

每个draw call都有固定的开销,draw call越多,你来回绘制所耗费的时间就会呈线性增加,情况会越来越糟糕。可以使用stat scenerendering 命令随时查看场景绘制。

        6.1、Draw thred 绘制调用会对性能造成极大影响,这方面要小心,再来回调用时,每当renderer渲染器需要发送命令,或者你增加了开销,它实际上的影响有时比ploycount多边形数量的影响还要严重。要想知道场景的绘制调用情况,一个好的入手点就是使用RenderDoc来调试draw call,这是个开源工具。

        6.2、如何避免draw call,大体上,为了减少draw call目前唯一的办法,就是使用更少、更大的模型。把不同的模型,不同的对象合并成一个模型,这是此类情况的常用解决办法。但问题是,你不能做的太多,因为这基本上会影响到游戏的everything方方面面,它可能会不利于occlusion。这对lightmapping 光照贴图不利,如果你使用了static lighting静态光照,你就需要更大的光照贴图以便显示细节效果。这对collision calculation碰撞计算不利,因为相比拆分成多个小型模型,碰撞效果的精度会差一些。当然,这对内存也不利,你要加载更大资源,可能会导致卡顿。

        6.3、LOD ,主要用来减少场景和模型的复杂度以及模型的顶点复杂度。但LOD也可用于减少draw call绘制调用,在常用的流程中,某个网格体上往往会有多种材质

 

七、GPU Thread

        要想找出这个阶段中的问题,一个简单的方法就是,关掉各个功能特性,关掉Static mesh静态网格体,关掉Skeletal骨骼网格体,关掉Particles粒子效果。ShowFlag命令对此非常有用,如果你开发的是PC项目,也许你可以把这些操作都绑定上快捷键,然后把它们混合搭配。试着找出可能导致GPU问题的原因。有一个建议给你,如果你把帧缓存区设置为0,就能直截了当的展示可能存在的填充率的问题。

  

        7.1、GPU Profiling,不仅可以在编辑器中运行它,还能在开发版本李运行ProfileGPU会生成一个文件。所有发送给GPU的命令,它的时间戳都会显示在其中,让你能轻易的检查特定帧的每一步渲染过程,它会按照时间顺序排列命令。并且显示相关命令的处理时间,以及那一帧的耗时,也许你就能找到场景中某个特定部分的问题原因。它也可以通过快捷键Ctrl+Shift+,来使用。这很简单,能帮你找出游戏中的后期处理反射或者体积雾是否会导致问题。

        7.2、GPU过度着色,在屏幕上绘制像素时,对于GPU要绘制的每一个像素,屏幕上只能同事显示一个ploygon多边形中的一部分。目前还没有从中间切分像素的技术,可能永远都不会这么做。但无论你的模型有多精细,如果它离玩家离摄像机很远的话,它就只占屏幕的一小部分,但你仍然需要处理所有的vertices顶点,从而绘制出少数的像素。或者说,一堆你最终无法看到的细节,所以解决Overshading过度着色的通用方式就是使用LOD。

        7.3、Quad Overdraw四边形过度绘制对于美术师来说,解决过渡着色的一大方法就是在检查创作内容时,使用所谓的“Quad Overdraw”四边形过度绘制模式,这个模式还能在很多其他平台上运行。它基本会显示所有可以用LOD简化的区域,以及需要用LOD大量优化的区域,它是一个渐变的形式从蓝到白,越接近白色情况就越糟糕。

        7.4、Shader Complexity 着色器复杂度,也很好用。它能将你在屏幕上绘制的每个像素的着色器的复杂度以视觉化形式呈现出来,它还能视觉化呈现潜在的过度绘制问题。过度绘制不仅有可能在你处理屏幕上众多顶点时发生,也有可能在你使用很多alpha通道时,在你需要处理alpha通道的各个图层时发生。引擎并不知道哪个在哪个的下面,所以引擎会处理所有的表面,才能绘制像素的最终色彩。所以着色器复杂度可以用来显示着色器的开销,要尽量避免红色和白色斑块的出现,因为它们会导致性能问题。

解决着色器复杂度问题的技巧,要计算的内容越多,着色器越复杂它的函数也越多,这么做的时候一定要谨慎。

程序化函数,例如noise噪点,procedural noise 程序化噪点可能会开销很大。如果你在开发PC游戏,并且你知道你面向的是高端平台,那这没什么问题。如果你面向移动平台或低端主机,你可能就要改为使用烘焙纹理以达到效果。众所周知,If语句的开销很大。

有一个通用的原则,尽量将复杂运算用vertex based顶点着色器而非pixel base像素着色器实现,这在移动平台上挺重要的,因为移动平台上你用的屏幕更小,所以很难看到所有的着色器效果细节,但这对PC游戏很有用。这取决于它所创建的效果种类,你不用修改模型和着色器的每一个像素,你可以通过顶点来进行所有的插值计算这样可以省去很多算力。

八、Light Complexity光照复杂度

        光照会让游戏的GPU开销大幅增加,“Light Complexity”视图模式非常适合用来发现问题,光照方面的性能问题。最重要的是,你能鉴别游戏中所有已知的静态、动态、和固定光源。颜色越接近大红色,场景的光照就越复杂,这样就便于美工和其他人发现问题。

关于光照复杂度,通用的建议是要尽量减少动态光源的数量,它们是实时渲染的,每帧都是。GPU需要做大量处理和运算,才能得出像素在光照影响下得最终色彩。所以要尽量减少动态光源的数量,如果你真的要用到它,你应该尽量缩小它的范围。这样它只会照亮对你比较重要的区域也就是关卡的某一部分,你还可以减少动态光源所影响的对象数量。你可以让对象和光源使用light channel光照分层功能,阴影投射的开销也很大。所以如果你有动态光源,但又不是真的需要投射阴影,那就关掉它。

        固定光源,尽量避免固定光源的范围发生重叠,最多只能有三个重叠的固定光源,如果你在添加一个,引擎就会显示一个红X标志,它的意思就是,它现在会变成动态光源,它的开销会大很多。我们也可以尽早的剔除动态光源,在场景中调整动态光源的开启或关闭,关闭动态光源它们就不渲染了,它们不会被计入渲染过程。总体上,如果你用的静态光源越多,性能表现就越好。动态光源和静态光源的取舍关系,如果你使用静态光源,就意味着你需要将lightmaps光照贴图写入硬盘,增加内存使用量,所以这是一种取舍和平衡,你需要把握好这种平衡。

资料来源:[虚幻独立开发日2019]UE4里的性能分析和优化(官方汉化)_哔哩哔哩_bilibili

猜你喜欢

转载自blog.csdn.net/qq_39934403/article/details/124179108