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

最新UE5优化文档: UE5 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内部扩容,并没有重新开闭物理内存,甚至比栈内存更快。这应该是UE3甚至UE2时代的遗产。

  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. UE的Visibility计算分为4种,消耗从低到高依次是:Distance,View Frustum,Precomputed Visibility,Dynamic Occlusion(也就是常说的OC:遮挡剔除)。详情见文档:Visibility and Occlusion Culling

  2. 开启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

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

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

  5. 移动端禁用硬件级OC。HZBO(Hierarchical Z-Buffer Occlusion)是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

  6. 将mask材质的depth计算放到Early Z-pass(利用硬件级特性early depth testing,无需在BasePass中计算)中执行。
    UE4为了减少depth pass (又叫depth prepass,属于UE的PrePass)的计算,对于final depth不确定的材质(mask材质是最主要的一种),UE会让GPU自己计算(post z中计算出depth。不同平台,不同厂商,不同时期规格,其计算depth的性能开销也差异巨大),而depth计算很可能触发alpha test(原因见下方参考链接),而alpha test是tile-based gpu(主要是移动端gpu)的阿喀琉斯之踵(详情见:Early Z and Discard on PowerVR)。
    这也是为什么r.EarlyZPassOnlyMaterialMasking没有默认开启的原因:一旦场景中有alpha-tested shader,则early z pass计算会更加复杂且更慢。最常见的就是mask类型材质的大面积植被,材质若含有参数化的Opacity Mask,则开启EarlyZPassOnlyMaterialMasking后性能反而下降。
    除此之外,还有另一个问题:计算depth时,opaque材质可以只需early z(UE4的叫做PreZ),无需pixel shader,而mask材质需要pixel shader计算, 且会导致pixel pass (属于UE的BasePass)的overdraw增加。
    所以,需要根据实际项目,测试此开关对性能是正收益还是负收益。
    优化选项开关(需要重启,不支持运行时切换):

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

    UE4.16做了优化:在Early Z-pass中写入全场景的depth,然后在BasePass中将mask材质写入一张opaque材质,最后用EQUAL取代LESS来执行DepthTest,以此跳过pixel pass的depth计算。
    更多细节参考: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. 动态光的Shadow Map Caching。这个是UE默认开启的功能,当动态点光源或聚光灯静止不动时,则存储当前的shadow map给下一帧复用。详情见:Movable Lights

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

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

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

    r.VolumetricFog.GridPixelSize
    r.VolumetricFog.DepthDistributionScale
    r.VolumetricFog.GridSizeZ
    
  6. 如果场景没有静态光 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帧。因为没有静态光,禁用后光影效果亦无任何损失。

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

  8. 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视觉效果变粗燥。
  9. 如果场景中有大量点光源和聚光灯且都是动态的,此时通过Distance Field动态地对LightComponet执行SetVisibilitySetHiddenInGame,那么性能可提高30%到60%。这个结论是基于对官方商城一款付费插件 Dynamic Lighting Portal System (Performance Booster) 的源码研读。注:只有当场景中点光源不仅数量多,而且cast shadow开启的数量也多,此时该插件的优化效果才明显。

  10. 灯光有个属性:Max Draw Distance,表示当摄像机超过这个距离后,自动关闭灯光,另外Max Distance Fade Range表示:光照在Max Distance Fade RangeMax Draw Distance之间做线性衰减,用于关闭灯光前的自然过度。示例:Max Draw Distance

阴影优化
  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。

  6. 由于dynamic shadow消耗很高,而且shadow mapping和mesh面数正相关(因为shadow map存储的是当前light所能看见的所有surface的depth),所以若参与计算shadow map的surface(也就是三角面)数量越少,则计算越小。为此UE提供了优化工具,为结构复杂的mesh提供低模mesh来计算shadow map:Proxy Geometry Shadows

  7. Proxy Geometry Shadows适用于static mesh,skeletal mesh有没类似的优化?答:Shadow Physics Asset,也就是Capsule Shadows。

  8. 严格控制产生dynamic shadow的灯光数量。初学者有个严重误解:deferred rendering在渲染成百上千的点光源时,依然不影响性能。网上一些show case看起来也确实如此,数千个光源下,帧率依然很高。但问题是,这些光源没有cast shadow,这种demo不适用于工业生产。除了前一条讲到的shadow map受mesh面数影响外,每多一个动态光(非static类型的light),framebuffer(主要是d-buffer)就会额外再build一次,以生成最终的shadow map(atlas图集)。
    哪些情况下会产生大量dynamic shadow?

    • Movable Light数量多且cast shadow;(这种情况较少,因为很好排查)
    • Stationary Light数量不多,但受其影响的动态物体多(比如大量怪物、会摇动的树林),因为每个可移动物体都会创建两个dynamic shadow,分别用于自身投射阴影到场景中,以及静态物体投射阴影到自身,并且combine到最终的shadow map;详情见:Stationary Lights
  9. 植被若要投射阴影,则要改用Screen Space Contact Shadows

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

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

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

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

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

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

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

贴图优化
  1. r.MipMapLODBias,容易被忽视却对性能影响极大的优化项。此选项对中低端移动设备的性能影响极大,因为他减小了采样贴图的大小,降低了带宽和显存访问延迟。但OpenGLES不支持(但可以强切mesh lod来变相地支持,如何强切,见“Geometry优化”第3条),Vulkan和Metal支持。
后处理 (Post Process)优化
  1. MeshComponent上有个属性Render CustomDepth Pass,用于控制自定义深度(比如描边),但Custom Depth开销较大,不需要时需及时关掉(比如射击游戏中,只有在红外成像仪激活状态下才开启custom depth)。运行时开启关闭CustomDepth的命令:r.CustomDepth 0(1表示开启)。
    如何确定CustomDepth是否生效?先执行命令ViewMode VisualizeBuffer,再执行命令r.BufferVisualizationTarget CustomeDepth,如果能看见深灰色像素,说明custom depth已开启。
Geometry优化
  1. 最常见的是LOD生成:Setting Up Automatic LOD Generation

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

  3. 指定某个skeletal mesh强制切换低LOD。虽然有console命令r.SkeletalMeshLODBias,但该命令是一刀切,若只是降低特定角色的lod,使用SetMinLod()

    int32 MeskAssetLodNum = SkMeshComp->GetSkeletalMeshAsset()->GetLODNum();
    //设置mesh资产的lod,引用该mesh的所有mesh component都将使用低级别的lod。
    SkMeshComp->GetSkeletalMeshAsset()->SetMinLod(MeskAssetLodNum - 1);
    
    //设置mesh的lod,但不影响mesh资产的lod。
    SkMeshComp->SetMinLOD(SkMeshComp->GetNumLODs() - 1);
    

    切换mesh lod的额外收益:贴图的mipmap也会跟着切换,材质性能提升明显。

植被优化
  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

  5. 若植被数量较多,且材质类型为mask时,开启:Early Z Pass Only Material Masking。详情见前面章节Occlusion Culling下的第6条。

  6. 植被若要开启阴影,不要用传统的shadow map(会导致计算开销和内存占用都很大),一个妥协方案是Screen Space Contact Shadow,Contact Shadows并不是专门为植被而设计,而是为了提升阴影细节(摄像机拉近后的阴影细节)。针对植被阴影时,调整合理的Contact Shadow Length,以牺牲一定的阴影效果来提升阴影性能。参考文档:Virtual Shadow Maps in Fortnite Battle Royale Chapter 4

物理与碰撞优化
  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;

  4. 启用Component Use Fixed Skel Bounds(位于SkeletalMeshComponent中),避免Bounding Box实时更新。

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

  6. 将Skeletal Mesh的动画烘焙成贴图:Vertex Anim Toolset。UE5官方自带插件:AnimToTexture

  7. SkeletalMesh的Physics Asset当不需要时及时销毁掉,比如仅用于对话的NPC。

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

  9. 开启GPU Skin Cache。Skin Cache的最大优点是避免了vertex在各个pass中来回传递(depth pass, bass pass, shadow pass, velocity pass),将position和normal计算从vertex shader转嫁到compute shader中异步计算(UE4由于D3D12是软适配,没有异步计算,所以性能提升没有UE5明显),避免了因vertex数量过多而阻塞3D图形管线的问题。缺点也很明显,显存开销较大。参考:What is the purpose of the GPU Skin Cache?

Animation Optimization
https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-optimization-in-unreal-engine

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张贴图。即使通过材质实例的static switch将不需要的贴图排除,渲染时依然会将这些贴图上传到RAM(Texture 2D)和VRAM(GPU Texture Pool));
    • gameplay加载贴图不合理(比如,PVP场景加载了PVE场景的UI);
    • 硬引用导致的加载贴图不合理(事项1中已阐述);

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

Texture Streaming作用是为低端机器节省显存,代价是切换lod时开销较大(CPU多了异步加载,GPU多了buffer copy),且画质下降(因为远处贴图变模糊,且能看到高低精度贴图瞬切)。如果是高配机器,比如显存6G以上,可以关闭texture streaming以获得更好游戏体验。Disable Texture Streaming to improve performance and image quality

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

  2. Shadow map内存占用:

    • 场景内静态物体的数量越多,则shadow map res越大,内存占用也越大;
    • cast shadow的灯光数量越多,shadow map res越大;
    • RHI memory(显存)中受阴影质量影响的数据类型包括:Render target memory Cube, Render target memory 2D, Render target memory 3D
  3. Light map内存占用受light map res大小影响,不过lightmass默认有开启压缩,以节省内存。另外UE会将shadow map和light map捆绑并写入多个图集(texture atlas)中,详情见:Unwrapping UVs for Lightmaps

  4. 过多的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卸载,以节省显存,但UE4官方没做这种优化)。
    这还没完:PSO数量增加则需要更多的draw和dispatch指令(由引擎将PSO绑定到command buffer并派发),进而增加CPU开销。

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

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

  6. 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在N卡下的测试结果,A卡下更高),而UE4不到1%,几乎为0。

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

  3. Shaded GS primitives数量正常在10k以内(Gears 5平均在3k左右),如果范围异常(比如十万以上),说明有两方面问题:

    • 启用Cast Shadow的灯光数量较多(PointLight),且阴影质量过高(r.ShadowQuality=5,阴影质量过高会增加阴影投射范围的全局distance scale,低阴影质量下引擎会自动关闭远处灯光的cast shadow)。另外cast volumetric shadow(DirectionalLight、SkyLight之外其他光源)也会导致GS primitives, vertex, pixel数量急剧增长甚至翻倍。
    • cast shadow的pointlight数量不多,但是其影响的mesh数量太多,没有对大量零散的mesh做merge,引擎自带mesh merge工具:Actor Merging

point light的shadow map不是普通的framebuffer,而是3D的cube-based shadow map(6张framebuffer),而这个cubemap的生成是在geometry shader中发起,并通过Layered rendering指定当前mesh(也就是primitive)上的所有vertex所属layered image(cubemap的六个面之一)的layer index。
另外mesh merge也不是合并的越多越好,一次性合并太多,形成一个超大mesh,会影响视锥剔除,可能得不偿失。

  1. CopyTextureRegion从几个到几百都是正常范围,贴图尺寸过大会影响copy性能。一般刚进入新场景时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


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