keywords:UE4性能优化、Unreal Engine Performance Optimization

说明:

  • GPUProfile统计性能消耗时,Editor模式下不是很准,因为编辑器的消耗也算在内,最好用Game模式或者打包版本来统计。
  • UE4不支持640X480的分辨率,该分辨率下运行会导致程序崩溃(4.4版本,不知最新版本是否仍有此问题)。
代码编译优化
  1. C++比蓝图快100到1000倍: [Test] Blueprint vs C++ Performance vs Nativized BP.
    感谢知乎网友金木研指正:以上结果是在编辑器模式下的测试结果(没有native蓝图),如果打包并将蓝图转换为native code,那么蓝图和C++的性能差距不超过两倍。
    大概在4.18(具体版本号忘了)之前,蓝图转native code的算法不够优化,比如一个加法操作,会被翻译成一个单独函数,这样导致执行堆栈特别长,后来Epic对蓝图转native的算法在不断改进,目前的版本已足够优化。

  2. 使用C++进行向量变换时,尽量使用FTransform::TransformXXX()FTransform::InverseTransformXXX,而不是FQuat::RotateVectorFQuat::UnrotateVector,因为前者使用了更多的当前硬件支持的矢量汇编指令(AVX),榨干了硬件性能,而后者是为了跨平台,老老实实使用C++代码来执行计算公式,虽然也调用了硬件汇编指令,但数量相对较少。
    UE4做了优化,当你使用FTransform::TransformXXX()时,如果当前硬件支持(intel 2008年之后的CPU、AMD 2011年之后的CPU),就走硬件指令,如果不支持,则走FQuat::RotateVector

  3. VS2019针对C++代码编译速度、以及CPUAVX/AVX2指令集下的矢量计算进行了更深入的优化,并且微软Xbox ATG团队使用UE4的demo工程Infiltrator对优化后的效果进行了基准测试:

    • 编译速度:完整编译速度,VS2019(16.2)是VS2017(15.9)的3.5倍,增量编译速度,VS2019(16.2)是VS2017(15.9)的1.6倍;
    • 计算优化:以游戏帧率作为测试标准,VS 16.2相对16.0提升了2%到3%,而16.0相对15.9最大可以提升2.8%,也就是说使用VS2019编译代码,相比VS2017,可以让游戏运行时帧率提升5%左右。

    更多详情见微软C++团队博客:C++ Team Blog

  4. 如何断点调试UE4中的内联函数:我们知道加了FORCEINLINE的函数无法断点调试,但FORCEINLINE_DEBUGGABLE可以,这样保证函数既能在debug模式下调试,又能在shipping版让函数内联。

  5. 开发模式下开启宏定义LOOKING_FOR_PERF_ISSUES,用来打印引擎内的潜在性能问题,比如:一帧内spawn的actor数量超过阀值;资产加载等待时长超过阀值。

  6. 加快编译速度,修改:Engine/Source/Programs/UnrealBuildTool/System/LocalExecutor.cs

     double ProcessorCountMultiplier = 1.0;
    

    改为:

     double ProcessorCountMultiplier = 2.0;
    

    另外将

     ActionProcess.PriorityClass = ProcessPriorityClass.BelowNormal;
    

    改为:

     ActionProcess.PriorityClass = ProcessPriorityClass.AboveNormal;
    

    缺点是编译时机器巨卡。
    参考: https://nerivec.github.io/old-ue4-wiki/pages/boost-compile-times.html

代码算法优化(仅限UE4 API相关)
  1. 为容器指定内存分配器(Allocator),默认是TSizedDefaultAllocator,堆上分配内存;如果分配大小可以预估且空间不大(Windows上不超过1MB,Linux上不超过8MB),可以使用TInlineAllocator,栈上分配内存,如果元素个数超过指定数量,会将旧的数据复制到新的内存空间上。例如:

     //默认为TSizedDefaultAllocator,堆内存分配空间
     TArray<Shape*> MyShapeArray;
    
     //TInlineAllocator,前16个元素在栈内存上,第17个元素添加时,会在堆内存上分配空间,
     //并将这17个元素复制到堆内存中,并销毁旧的栈内存。
     TArray<Shape*, TInlineAllocator<16>> MyShapeArray;
    
     //同上,区别是,添加第17个元素时,会重新分配内存空间,但是依然在栈内存,
     //当添加第33个元素时,则新内存空间在堆内存。(无限套娃)
     TArray<Shape*, TInlineAllocator<16, TInlineAllocator<32>>> MyShapeArray;
    

    参考:Optimizing TArray Usage for Performance
    个人认为是否指定内存分配器,性能影响不大,因为UE4默认开启了内存池,即使TArray内部容量扩展,并没有重新开闭物理内存,甚至比栈内存更快。

  2. 如果TArray元素class没有移动构造函数(move constructor)时,使用Emplace()代替Add(),避免值传递时复制内存;如果元素class自带move constructor(MyStruct(MyStruct&& src)),此时Add(MoveTemp(MyStruct01))等价于Emplace(Arg01, Arg02)

     struct MyStruct
     {
         MyStruct(int32 Arg01_, int32 Arg02_) : Arg01(Arg01_), Arg02(Arg02_) { }
    
         MyStruct(MyStruct&& src) noexcept : Arg01(src.Arg01), Arg02(src.Arg02) {}
    
         int32 Arg01;
         int32 Arg02;
     };
    
  3. 清空TArray时,如果该TArray对象还会继续使用,使用Reset()代替Empty(),因为前者不会销毁内存空间。

  4. TArray移除元素时,如果对元素的顺序不关心,可以使用RemoveAtSwap()代替RemoveAt(),前者是用数组末尾的元素来填补内存空洞(移除元素后产生的无效内存空间),而后者是对空洞后的所有元素平移。时间复杂度,前者为O(RemovedCount),后者为O(ArrayNum)。

  5. 如果是单个生产者单个消费者(SPSC)的线程环境,可以使用TCircularQueue作为消息队列来保证数据安全,比使用FScopeLock消耗低,因为前者内部使用的是atomic,而非lock(虽然atomic也算一种轻量级lock)。

Occlusion Culling 遮挡剔除(视距裁剪)
  1. 开启Occlusion Culling (Project Settings -> Engine -> Rendering -> Occlusion Culling,默认已开启),这是针对被遮挡的mesh进行剔除。
    另外,还有针对Light、Z Pass、Shadowd的剔除,如果需要增大以屏占比基准的剔除强度(代价是剔除效果比较突兀)以提升渲染效率,将以下属性值增大:
    Min Screen Radius for Lights
    Min Screen Radius for Early Z Pass
    Min Screen Radius for Cascaded Shadow Maps

  2. 使用Cull Distance Volume进行细粒度的视距裁剪。Project Settings中的Occlusion Culling只处理被遮挡的mesh,不能根据视距判断。

  3. 如果是N卡,禁用HZBOcclusion(默认是禁用),如果是A卡则启用HZBOcclusion,因为UE4游戏在N卡下启用HZBO性能损失10%,A卡中启用HZBO性能提升10%左右。 参考自:Performance boost on UE4 games for Radeon users - guru3d(说的像那么回事儿,不知真假。。有亲自测过的欢迎留言。。)

  4. 移动端禁用硬件级OC。HZBO是GPU计算且有固定开销,移动端GPU算力及其珍贵,开启反而会影响性能,UE4自带基于CPU计算的OC,适合移动端。UE5移除了CPU端OC,即软件级OC,对于中低端设备简直是灾难。好在有人将UE4的software occlusion culling搬到了UE5上:Software Occlusion Culling for UE5 - github。另外知乎上有网友说将向量计算换成汇编(x86的AVX、arm的Neon),SOC性能提升数倍:State-of-the-art Software Occlusion Culling System

  5. 对mask材质强制开启Early Z。UE4为了减少depth pass (属于UE的PrePass)的计算,会根据硬件特性来决定是否开启mask材质的Early Z pass,这样导致有些机器(特别是安卓设备)自动关闭了mask材质的Early Z,但这将大大增加pixel pass (属于UE的BasePass)的overdraw。
    需要根据实际项目,测试Early Z对性能是正收益还是负收益,测试console命令(需要重启,不支持运行时切换):

    [/Script/Engine.RendererSettings]
    ; 默认是3
    r.EarlyZPass=2
    ; 默认为false
    r.EarlyZPassOnlyMaterialMasking=True
    

    更多细节参考:Mask material only in early Z-pass

灯光优化
  1. 3种光源的性能消耗从低到高:
    定向光/平行光(Directional Light) < 点光源(Point Light) < 聚光灯(Spot Light)。
    当光源数量在场景中达到一定量级时,3种灯光的性能差距也是数量级上差距。

    Point Light 和 Spot Light 的消耗到底谁高谁低,UE4官方文档上貌似没找到明确解释。可能两种灯光在不同使用场景下,消耗对比也不一样。Unity早期官方文档给出了两种灯光在GPU上的消耗说明:
    Point Light: They have an average cost on the graphics processor (though point light shadows are the most expensive).
    Spot Light: They are the most expensive on the graphics processor.
    不考虑显存等其他因素的开销,单考虑GPU消耗,Spot Light 比 Point Light贵。

    Unity早期文档:灯光 Light
    http://www.ceeger.com/Components/class-Light.html
    感谢知乎网友刘相敬给的指导建议:
    不考虑阴影的情况下点光源的衰减计算比聚光灯简单很多,只和距离相关,射灯要计算距离衰减和内外角衰减,有cos sin指令,代价相对来说会大很多,但是实际使用过程中射灯照亮区域远小于点光源,反而在绝大多数场景消耗比点光源低。当然这个是基于延迟渲染和ClusterBased裁剪后的灯光来说的,实际Unity Forward渲染是不裁剪光源的,所有像素都会计算一遍点光源和射灯光照,所以射灯消耗会远大于点光源(Forward Add中渲染),Unity的默认射灯没有内外角一说,依靠一张贴图来做衰减模拟,所以Unity的射灯比点光源多了一次采样环节。

  2. 在建构光照贴图时,若场景中没有给予Lightmass Importance Volume,会对整个场景做间接光照的采样,产生Indirect Lighting Cache,这对大型游戏场景是相当的浪费,像是游戏角色到不了的中、远景不需要产生Indirect Lighting Cache,这时候就可以在场景中置入Lightmass Importance Volume,指定特定区域内才会产生Indirect Lighting Cache,节省不少建构光照的时间。

  3. 点光源和聚光灯尽量不要开启Cast Volumetric Shadow;默认只有平行光开启了此选项。开启后的性能消耗为不开启的性能消耗三倍。不开启表示阴影计算方式使用Shadow Mapping,开启表示使用Shadow Volume,前者计算没有后者精准,但开销小。

  4. 如果开启体积雾,建议将灯光改成静态光,这样在Build Lighting时会生成预计算的体积雾相关数据,可显著提升体积雾性能。另外,修改以下命令行参数,在效果和性能之间做取舍:

    r.VolumetricFog.GridPixelSize
    r.VolumetricFog.DepthDistributionScale
    r.VolumetricFog.GridSizeZ
    
  5. 如果场景没有静态光 Static Light(全是动态光 Movable Light 或者固定光 Stationary Light),则要禁用 Static Lighting,以节省 Static Lighting 相关的开销(比如 LightMaps和ShadowMaps的相关计算)。禁用方式:Project Settings -> Engine -> Rendering -> Lighting -> disable Allow Static Lighting

    当全动态灯光为性能瓶颈时,禁用Static Lighting可以提升性能。测试用例:我的某个游戏场景,Lighting是瓶颈之一,r.ScreenPercentage 修改为400进行压力测试,关闭 Static Lighting 后帧率提升了20帧。因为没有静态光,禁用后光影效果亦无任何损失。

  6. 关闭Support Global clip plane for Planar Reflections,默认关闭,开启后消耗巨大。

  7. AO性能优化。在超大型场景中,一般灯光会是性能瓶颈之一,特别是动态光场景下。此时关闭AO可以大幅提高帧率(AO默认为开启,早期版本默认是关闭的)。开启AO后(Project Settings -> Engine -> Rendering -> Default Settings -> Ambient Occlusion),引擎默认的AO为SSAO(Screen Space Ambient Occlusion), SSAO无法进行预计算,所以GPU性能开销较大,可以修改为DFAO(Distance Field Ambient Occlusion)以提升性能,因为DFAO可以预计算,代价是增加显存开销。
    DFAO开启方式:
    Distance Field Ambient Occlusion
    https://docs.unrealengine.com/en-us/Engine/Rendering/LightingAndShadows/DistanceFieldAmbientOcclusion
    DFAO相关的两个优化选项:

    • Compress Mesh Distance Fields: 通过压缩Distance Fields volume texture来减少显存占用,代价是当使用Level Streaming时会出现Hitch
    • Eight Bit Mesh Distance Fields: 将Distance Fields volume texture从16位格式压缩为8位格式,代价是AO视觉效果变粗燥。
  8. 如果场景中有大量点光源和聚光灯且都是动态的,此时通过Distance Field动态地对LightComponet执行SetVisibilitySetHiddenInGame,那么性能可提高30%到60%。这个结论是基于对官方商城一款付费插件 Dynamic Lighting Portal System (Performance Booster) 的源码研读。引擎本身有对灯光Occlusion Culling,至于为什么SetVisibilitySetHiddenInGame之后还能有这么大性能提升,估计需要仔细研究UE4渲染相关代码,个人不成熟的推测:以deferred shading为例,每个光源在image space处理像素时,即使某个光源没有对当前像素产生明显影响,但是对应的计算过程仍然执行了,可能渲染层并没有因上层应用是游戏项目还是影视动画渲染而去做专门优化,而这个插件通过将灯光隐藏并禁用后,那么在处理各个像素光照信息时,这些光源的计算逻辑直接跳过了,所以可以大幅提升性能。

阴影优化
  1. 如果使用了非静态的Directional Light(Stationary 或者 Movable),场景中有大量单位时,一定要开启Dynamic Shadow Distance(默认为0,表示关闭)。
    测试用例: 500 个 Actor 同屏,摄像机高度4000,Stationary类型的 Directional Light 的属性Dynamic Shadow Distance StationaryLight的值要大于摄像机到Actor的直线距离(注意:是到每个Actor的直线距离,所以值尽量要设置的大一些,比如5000),否则帧率从200 fps 下降到 100 fps。
    Dynamic Shadow Distance开启后能提升性能的原因:
    Dynamic Shadow Distance 表示在多少距离内使用动态阴影,超过这个距离则Fade成静态阴影,以提升性能。

  2. 逻辑控制Cast Shadow
    虽然灯光提供了属性DistanceField Shadow Distance来控制阴影根据摄像机距离投射,但是这种做法是一刀切。比如:假设性能瓶颈是大量怪物的阴影投射,远处山体和建筑的树木的阴影投射对性能影响很小,此时使用DistanceField Shadow Distance就会导致场景的表现效果大打折扣。推荐做法是,程序逻辑上控制:如果是怪物对象,只对离摄像机一定距离内的怪物开启阴影。
    物体投射阴影的开关:

     void UPrimitiveComponent::SetCastShadow(bool NewCastShadow)
    

    另外ForwardShading模式下的动态光,即使通过DistanceField Shadow Distance去掉远处PrimitiveComponent的阴影,如果不关掉PrimitiveComponent的cast shadow,阴影计算的消耗仍很巨大。DeferredShading和非动态光情况下未验证。

  3. 开启动态光之后消耗巨大,若既想启用动态光,又想保证性能,可以将阴影级别较低,默认是级别3(Epic),可以改成级别2(High)。

  4. 使用LightMap UV为静态物体烘焙阴影。
    自动生成: https://docs.unrealengine.com/4.27/en-US/WorkingWithContent/Types/StaticMeshes/AutoGeneratedLightmaps/
    手动生成: https://www.youtube.com/watch?v=-sJsR0y7R8U

  5. 关闭灯光的CSM(Cascade Shadow Mapping),默认关闭。开启后可以节省shadow map的内存占用(High quality级别的shadow选项设置下,大型户外场景的shadow map内存占用巨大),但是会增加计算开销,比如shadow map的culling和combining。

材质优化
  1. 材质类型的性能,从快到慢:Opaque -> Masked -> Translucent。

  2. 若场景中有大量单位,比如500个,那么这些单位一定要做材质LOD,并尽可能多的去掉半透明材质(比如在最后两级直接去掉半透明效果),否则性能消耗呈指数级增长。

  3. 如果GPUVisualizer的BasePass耗时较高,那么很大一部分原因是材质复杂度过高。

  4. Decal消耗和像素数量有关程序功能绝对不要乱用贴花,美术铺场景除外。比如程序想用贴花做一个范围标记,如果当标记范围很大时绝对不要用贴花,可以改成划线或者不通透贴图。如果场景需要大量使用贴花,根据视距动态创建和销毁贴花,仅仅SetVisibility是不够的,隐藏后还是会有巨大的开销(不过也可能是编辑器在地形编辑这块有bug,因为UE4场景编辑器有很多bug,特别当升级引擎版本后,旧版本中创建的地形在新版本中可能出现各种莫名其妙的bug)。

  5. 场景中的材质种类要提前规划好,拼场景时只在规划好的材质中选择。如果同屏的材质种类较多,会增加draw call。特别是场景美术用网上素材东拼西凑,很容易导致材质种类数量急剧上涨。

  6. 一般针对材质的优化方式是,抓帧查看shader instruction count。引擎提供了工具函数FMaterialStatistics Stats = UMaterialEditingLibrary::GetStatistics(_YourMaterialInterfaceObject_);,自己写个插件,批量导出所有材质的最贵指令数量,并支持排序。

  7. 当半透或透明材质的物体较多时,则要严格检查Overdraw。透明材质本身已经非常耗,如果再因为overdraw导致部分像素被重复绘制多次,则开销会暴涨,参考:Transparency considerations

Geometry优化
  1. 最常见的是LOD生成:Setting Up Automatic LOD Generation

  2. Octahedral Imposters烘焙:使用引擎自带插件Impostor Baker将高分模型烘焙成多视角渲染的贴图缓存。

植被优化
  1. 地形编辑时,使用Instanced Static Meshes。Intancing会增加GPU的开销,但是可以显著降低CPU的开销。注意:实际应用中,Instancing并不能作为减少CPU draw call次数的主要途径,因为实际的游戏场景不可能全是instanced mesh,即使是满屏的植被,也并非一定要用instanced mesh。要减少draw call次数,需要减少材质种类,提高材质复用率。

  2. 当Instanced Mesh的数量较多时(比如百万级),一帧内执行RemoveInstance或者UpdateInstanceTransform数次,帧率会狂泻。
    优化办法:操作Instanced Mesh之前,将UHierarchicalInstancedStaticMeshComponent::bAutoRebuildTreeOnInstanceChanges设置为false,然后执行你需要的各种Instanced Mesh操作,操作完之后,然后将bAutoRebuildTreeOnInstanceChanges设置为true,然后执行BuildTreeIfOutdated(true, false);,这样可以显著减少因操作百万级Instanced Mesh而导致的性能损失。

  3. 如果植被材质消耗成为瓶颈时,宁可增加面数,也不要使用 Translucent 材质,Masked酌情使用。比如一根草的面片,其整个形状全部使用三角面拼出来,而不要用一个三角面再加 Mask 或者 Translucent 材质的方式。

  4. 为Instanced Mesh设置合适的Cull Distance

物理与碰撞优化
  1. BoxComponent的Generate Overlap Events设置为false。如果不需要Overlap事件,那么就将该属性设置设置为false,默认为true。当BoxCompont达到一定量级时,开启Generate Overlap Events的性能消耗是关闭情况下的两倍。
    特别是可移动的物体,若开启了Overlap检测,则只要其发生位移,引擎每帧都会执行物理检测。

  2. 如果不需要物理,将 Simulate Physics 设置为false。

  3. 如果不需要Hit事件,将 Simulation Generates Hit Events 设置为false。

  4. 如果场景中物体类型(WorldStatic、WorldDynamic、Pawn等)很多,且每种数量也很多,则Collision 的 Object Response 通道设置的越少越好,把可以设置为 Ignore 的通道都设置为 Ignore 。

  5. 如果是大型RTS游戏,场景有海量单位时(比如星际2中大规模的虫族小狗),能不用UE4的 Collision 就不要用 Collision,否则帧数狂泻。
    建议自己实现一个简易的自定义Collision,比如球形Collision,然后计算该 Collision 与单位之间的直线距离,来判断是否是否发生了碰撞,并且降低检测间隔,比如 0.1秒一次。用此种方式,如果单位数量较多时,还需要自己写一个类似Distance Filed的八叉树来缓存单位列表,以降低计算单位间距时遍历单位列表的循环次数。

  6. 可移动Actor中尽量减少component数量,尤其是带有碰撞的component,因为这些component的碰撞信息每帧都会刷新,CPU开销会大幅增加。

  7. static mesh的碰撞一定要使用简单碰撞,尤其是开放世界或者沙盒游戏中。由于UE自带的convex hull生成不精准,美术喜欢省事儿直接使用复杂碰撞。可使用UE自带的凸包生成工具:Auto Converx Collsion,或者特殊形状的碰撞使用UCX自定义方式:UE4自定义碰撞盒。或者使用houdini自动生成:Generate Collision for Unreal

动画优化
  1. 打开角色蓝图 -》 MeshComponent -》 Detail 面板中的 Optimization 类别下 -》 勾选 Enable Update Rate Optimizations

  2. 只对渲染的 SkinnedMesh执行 Tick 和 RefreshBoneTransforms

     USkinnedMeshComponent::VisibilityBasedAnimTickOption = EVisibilityBasedAnimTickOption::OnlyTickPoseWhenRendered;
    

    默认是AlwaysTickPoseAndRefreshBones,表示不管是否被渲染(在可见区域内),都执行 Tick 和 RefreshBoneTransforms。
    VisibilityBasedAnimTickOption最开始叫做SkinnedMeshUpdateFlag,4.21版本之前改成了MeshComponentUpdateFlag,4.21开始叫做VisibilityBasedAnimTickOption

    如果关闭动画Tick,动画蓝图内Tick事件中的逻辑会失效;如果关闭RefreshBoneTransforms,则骨骼变换的逻辑会失效,比如Transform (Modify) Bone。AnimNotify不受这个选项影响。

  3. 动画蓝图的逻辑尽量直接访问成员变量,引擎默认开启了优化选项:动画蓝图中的成员变量在编译时会被复制到Native Code中,从而避免在运行时进入蓝图虚拟机(Blueprint Virtual Machine)执行蓝图代码,因为蓝图VM运行效率低。
    默认会被编译优化的参数类型包括:
    member variables;
    negated boolean member variables;
    members of a nested structure;
    具体说明见官方文档:
    Animation Optimization
    https://docs.unrealengine.com/en-us/Engine/Animation/Optimization
    Animation Fast Path Optimization
    https://docs.unrealengine.com/en-us/Engine/Animation/Optimization/FastPath

  4. Morph Targets的计算消耗较大,默认是在GPU上计算,如果当GPU上有性能瓶颈且CPU算力充足时,可以将Morph Targets改成CPU计算,方式:Project Settings -> Engine -> Rendering -> Uncheck Use GPU for computing morph targets.

  5. 将Skeletal Mesh的动画烘焙成贴图:Vertex Anim Toolset。UE5中官方已集成类似插件,叫做:AnimToTexture。

  6. 动画切换尽量少用Animation Trees(动画蓝图State Machine下的蜘蛛网, 位于AnimGraph中),复杂的AT会极大增加CPU开销(Unity的Blend Tree也同理)。切换动画尽可能用状态机(包括BlendPoseBy等节点),且使用带有闪电小icon的节点。

UI优化
  1. 能用HUD解决的就不要用UMG,等到需要显示时才创建Widget对象,不显示时则销毁,UMG对象较多时性能消耗巨大。
    比如场景内有一千个单位,每个单位上都创建有WidgetComponent,即使这些WidgetComponent没有显示任何东西,也会产生巨大的GPU开销。

  2. 不能使用UMG来修改鼠标光标,因为UMG来制作响应速度较高的显示逻辑时,会有肉眼可见的明显延迟(由此可见UMG的性能消耗有多高),可以使用Hardware Cursors来代替UMG制作光标。

  3. 使用Invalidation Box对控件caching。

  4. 使用Retainer Box对控件合批渲染。

  5. 使用FairyGUI代替UMG。

  6. 使用NoesisGUI代替UMG。

Epic Games工程师分享:如何在移动平台上做UE4的UI优化?
http://youxiputao.com/articles/11743

特效优化
  1. 尽量不要使用 Volume domain,使用后会显著增加GPU开销。可以通过 profilegpu 检测 Volume 开销。

  2. 大量粒子且需要碰撞时,使用GPU粒子,且开启Distance Fields:Using Particle Collision Mode for Distance Fields

AI与位移优化
  1. 如果角色不需要 Controller ,就不要给它 Spawn Controller。如果一个角色长时间停止,则先给他Unpossesed() ,等到可移动时再PossessedBy()
    测试:500个角色,AI Controller Class 设置为:null、 AIController、PlayerController 的帧数分别为 120 fps、 100 fps、75 fps。

  2. 海量Pawn(比如500个)单位移动,如果是在 Tick 中使用 AddMovementInput 移动,帧率直接下降一半(比如从90帧下降到40多帧)。对于无法移动的单位,最好停止执行 AddMovementInput() ,以提升性能。

Dedicated Server优化
  1. 服务端cook时剥离动画数据
    Project Settings -> Engine -> Animation -> 勾选 Strip Animation Data on Dedicated Server.
    如果在动画中添加了触发修改数据的 Notify Event,勾选此选项会有问题。请确保动画中挂载的 Notify 只是表现相关,不涉及游戏逻辑。

  2. Server模式下禁用角色物理模拟
    FBodyInstance->bSimulatePhysics 设置为false。默认为false。
    SkeletalMeshComponent::bEnablePhysicsOnDedicatedServer 设置为false (默认为true) ,但这样容易被外挂篡改。bEnablePhysicsOnDedicatedServer在run-time修改不生效。

  3. Server模式下禁用 Collision
    UPrimitiveComponent->bGenerateOverlapEvents 设置为false,角色蓝图中的 CollisionComponent 默认为true。

  4. Server模式下Detach角色身上所有的装饰性组件。

  5. AnimInstance的Root Motion Mode不要修改为Root Motion from Everything,尽量使用默认值Root Motion from Montage Only,以减少服务器的动画同步计算量。

  6. 4.20提供了针对Dedicated Server优化的新特性:Replication Graph,可以初略地理解为针对网络通信的LOD。没有这个特性之前,服务端调用Multicast函数,几公里外的玩家也会收到Multicast,而实际上这种远距离玩家可能不需要即时更新数据,而是出现在当前client附近位置时(Cull Distance范围内)时再手动ForceNetUpdate()。有了Replication Graph之后,这种手动优化方式可以交给引擎自己管理。

  7. 4.25提供了PushModel,通过手动同步对象属性,来降低replication模块每帧遍历actor以及属性比对的耗时。Push Model Networking

内存优化(Memory Issues)
  1. 开发中测试用的资产放到专用的文件夹下,并且通过DirectoriesToNeverCook(DefaultGame.ini)将其隔离,不参与打包。打包时引擎会递归扫描所有硬引用,并对相关资产进行cook和package。当启动游戏时,引擎会自动加载被硬引用的资产到内存,导致内存占用急剧增长(cook时的内存也会急剧增长)。DirectoriesToNeverCook会打断硬引用的关系链,避免资产被cook和package。

  2. 过多的贴图加载:

    • 没有设置为streaming(勾选了Never Streaming,默认没有勾选);
    • 没有生成mipmap(设置了NoMipmaps。默认会生成mipmap);
    • 材质中过多的纹理采样(比如,为了一个材质处理各种特效,挂了N张纹理);
    • gameplay加载贴图不合理(比如,PVP场景加载了PVE场景的UI);
    • 硬引用导致的加载贴图不合理(事项1中已阐述);

    The Coalition Studio给出了他们的技术标准:Gear4的texture pool是1500MB,Gear5的texture pool为1150MB,两者都是基于UE4开发。结论:XBox One平台为例,若贴图内存占用超过1.5GB则有性能问题。

Texture Streaming作用是为低端机器节省显存,代价是切换lod时由于加载贴图会导致卡顿,以及画质下降(因为远处贴图变模糊,且能看到高低精度贴图瞬切)。如果是高配机器,比如显存6G以上,可以关闭texture streaming以获得更好游戏体验。Disable Texture Streaming to improve performance and image quality

  1. 蓝图中过多的资源硬引用(Hard References)。推荐一个硬引用检测插件: HardReferenceFinder。避免硬引用将涉及到异步加载,异步加载又涉及到内存管理和事件管理,是一个庞大话题,这里不赘述。

  2. Shadow map(非动态光照)内存占用:

    • 场景内静态物体的数量越多,则shadow map res越大,内存占用也越大;
    • 阴影质量(Shadows quality)越高,则light map res越大,内存占用也越大;
    • RHI memory(显存)中受阴影质量影响的数据类型包括:Render target memory Cube, Render target memory 2D, Render target memory 3D
  3. 过多的shader置换(Shader Permutation,Unity中叫Shader Variants)会导致显存暴涨。
    材质蓝图(shader graph)中使用了过多的static switch(假设有10个),且基于该material创建了数量众多的material instance(假设1024),那么当instance中的static switch随机组合(true和false),极端情况下可以生成1024个permutation(实际情况更糟,因为引擎内部还为不同lighting phase生成不同的permutation),permutation数量增长则PSO数量也随之增长,而PSO吃VRAM,最终导致显存暴涨。
    这还没完:PSO数量增加则需要更多的draw和dispatch指令(由引擎将PSO绑定到command buffer并派发),进而增加CPU开销。

    Permutation设计目的是为了解决uber shader中过多的动态if分支(uniform Boolean控制)影响shader计算效率的问题。相当于用显存换时间。

  4. 手动释放Render target:UKismetRenderingLibrary::ReleaseRenderTarget2D(UTextureRenderTarget2D* TextureRenderTarget)。靠垃圾自动回收RT有较高的延迟,当显存吃紧时,应及时释放RT。

  5. PC和console设备使用共享显存(Shared GPU Memory),UE4官方没有实现,需要自己修改RHI:

    • Vulkan上叫VK_MEMORY_PROPERTY_HOST_CACHED_BIT(调用VmaAllocationCreateInfo()时在VmaAllocationCreateInfo::preferredFlags中指定);
    • D3D12上叫D3D12_HEAP_TYPE_CUSTOM (NUMA平台上MemoryPoolPreference类型为D3D12_MEMORY_POOL_L0D3D12_MEMORY_POOL_L1为VRAM);
RHI (Vulkan & D3D12)
  1. 如果dispatch数量较高(Gears 5平均每帧50左右),则GPU计算开销会显著增加。对于计算量较大的特效,建议使用Async Compute,D3D12的官方example:N-body Gravity System。因为历史原因,UE4无法实现async compute, bindless resource等重要特性(因为需要推翻整个rendering代码重写)。UE5甩掉了包袱已彻底实现:async compute开销一直保持在20%左右(Matrix Awakens测试结果),而UE4不到1%,几乎为0。

  2. 由于UE的deferred shading是multipass,而material的每种buffer(albedo, normal, material properties, ambient occlusion, worldspace reflection等等一共有16种buffer)都对应一个pass,虽然RDG中做了merge pass优化,但抵不住量大,需要的dispatch也随之增加。所以,材质若过于复杂(玻璃、半透特效、毛发等),会撑爆dispatch,而dispatch数量大则GPU负载高。另外,Insights中的STAT_FRDGPass_Execute次数和dispatch次数正相关。

  3. Shaded GS primitives数量正常在10k以内(Gears 5平均在3k左右),如果范围异常(比如十万以上),则说明阴影有问题:点光源或射灯数量过多、阴影质量过高(r.ShadowQuality=5)。另外cast volumetric shadow(DirectionalLight、SkyLight之外其他光源)也会导致GS primitives, vertex, pixel数量急剧增长甚至翻倍。

  4. CopyTextureRegion次数不影响性能,从几个到几百都是正常范围,一般刚进入新场景时CopyTextureRegion数量较高,此时正在mip slicing(个人推测)。

参考资料

Performance and Profiling
https://docs.unrealengine.com/en-us/Engine/Performance

Optimizing Your Game | Live Training | Unreal Engine Livestream
https://www.youtube.com/watch?v=U0p8EY07_mc

CPU Profiling
https://docs.unrealengine.com/en-us/Engine/Performance/CPU

GPU Profiling
https://docs.unrealengine.com/en-us/Engine/Performance/GPU

Unreal Engine 4 Optimization Tutorial, Part 1-4
https://software.intel.com/en-us/articles/unreal-engine-4-optimization-tutorial-part-1
https://software.intel.com/en-us/articles/unreal-engine-4-optimization-tutorial-part-2
https://software.intel.com/en-us/articles/unreal-engine-4-optimization-tutorial-part-3
https://software.intel.com/en-us/articles/unreal-engine-4-optimization-tutorial-part-4

Optimizing and Profiling Games with Unreal Engine 4
http://vincentloignon.com/blog/optimizing-and-profiling-games-with-unreal-engine-4/

Dynamic Lighting Portal System (Performance Booster)
https://www.unrealengine.com/marketplace/dynamic-lighting-portal-system-performance-booster

Performance Optimization: Shadows Triggering Zones
https://www.unrealengine.com/marketplace/performance-optimization-shadows-triggering-zones

Low-Level Memory Tracker(New feature in v4.18)
https://docs.unrealengine.com/en-US/Programming/Development/Tools/LowLevelMemoryTracker/index.html

Aggregating Ticks to Manage Scale in Sea of Thieves | Unreal Fest Europe 2019 | Unreal Engine
https://www.youtube.com/watch?v=CBP5bpwkO54

Unreal Insights(New feature in v4.23)
https://www.youtube.com/watch?v=TygjPe9XHTw

Virtual Texturing(New feature in v4.23)
https://www.youtube.com/watch?v=fhoZ2qMAfa4

Unreal Engine 4 Performance Guide (Recommended)
https://gpuopen.com/unreal-engine-performance-guide/

Profiling and Optimization in UE4 | Unreal Indie Dev Days 2019 | Unreal Engine
https://www.youtube.com/watch?v=EbXakIuZPFo

Maximizing Your Game’s Performance in Unreal Engine | Unreal Fest 2022
https://www.youtube.com/watch?v=GuIav71867E

Epic Games 王祢:UE4制作多人大地型游戏的优化
https://zhuanlan.zhihu.com/p/43742565

UE4 GameThread优化总结笔记
https://zhuanlan.zhihu.com/p/388070138

通过优化在UE4中实现良好性能和高质量视觉效果
http://gad.qq.com/program/translateview/7160166

Debugging and Optimizing Memory
https://www.unrealengine.com/en-US/blog/debugging-and-optimizing-memory

Expert’s guide to unreal engine performance
https://dev.epicgames.com/community/learning/tutorials/3o6/expert-s-guide-to-unreal-engine-performance

Testing and Optimizing Your Content
https://docs.unrealengine.com/5.1/en-US/testing-and-optimizing-your-content/

UE5: Optimizing Memory Usage of Textures & Meshes (Recommended)
https://www.youtube.com/watch?v=PQtrB-c6zhc

Performance recommendations for Unreal
https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unreal/performance-recommendations-for-unreal

Material recommendations in Unreal
https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unreal/unreal-materials

Optimization Deep Dive: Unreal Engine 4 on Intel
https://www.slideshare.net/IntelSoftware/optimization-deep-dive-unreal-engine-4-on-intel

Unreal Engine Game Optimization on a Budget (Recommended)
https://www.tomlooman.com/unrealengine-optimization-talk/
https://www.youtube.com/watch?v=G51QWcitCII

Project Optimization in UEFN | Unreal Fest 2023 (Recommended)
https://www.youtube.com/watch?v=k9fAXZ4U4XA
Memory Management in UEFN and Fortnite Creative | Unreal Fest 2023
https://www.youtube.com/watch?v=gtX0gPOSkbU
GPU Crash Debugging in Unreal Engine: Tools, Techniques, and Best Practices | Unreal Fest 2023 (Recommended)
https://www.youtube.com/watch?v=CyrGLMmVUAI


嗜欲深者天机浅 ---《庄子·大宗师》