Unreal Engine 5 Character and Animation Optimizations
Unreal Engine 5 Character and Animation Optimizations
这是一篇视频记录,视频地址:Unreal Engine 5 Character and Animation Optimizations | Unreal Fest 2024
Profiling
Commands
- Stat FPS
- Stat UNIT
- Stat UNITGRAPH
- Stat ANIM
- ShowDebug ANIMATION
Size Map
检测当前资源引用到的资源大小,可分别查看内存中的和磁盘中。

MemReport
MemReport -full
输出当前内存情况,到Saved/Profiling/MemReports/
文件夹中。
Control Rig
在Control Rig的类设置中,打开以下选项:

这样可以实时看到整个Control Rig花的时间:

以及每个节点的消耗和调用次数:

GPU Visualiser

Insights
- Channels
- Animation
- AssetLoadTime
- Stat Named Events
- Open Insights After Trace
Animation Sequence
Audit
Content Audit Animation Data
- Animation Tracks:检查是否有不需要的节点被烘培进动画了,比如把脸部节点也加入到动画中了,但是该动画不需要脸部节点运动。
- Animation Curves:删除不必要的曲线
Animation Track
检查动画轨道数量,数量在66个左右是非常适合Gameplay的。
使用Asset Action Utility
编写一个简单的方法查询该动画的轨道数量,代码源文件,代码如下:

这样可以右键动画切片时,看到该方法,运行即可:

输出如下:

Animation Import Setting
导入动画切片时勾选上以下三个选项:

设置全部导入配置,在/Engine/Conifg/BaseEditorPerProjectUserSettings.ini
文件中的/Script/UnrealEd.FbxAnimSequenceImportData
章节配置:

Audit Example
以下原数据:

减去不必要轨道和曲线后:

Anim Graph
可以在动画蓝图中查看,动画传输的数据,如果除了动画数据还有其他数据在传输,可以看到有其他颜色的线,这样可以用来排查是否有无用数据在传输。

Compression
- Animation Compression Library(ACL)
- Default >= 5.3
- Unreal Marketplace 4.25 - 5.2
Level Of Detail (LOD)
根据距离来减少蒙皮的面数,同时也可以减少骨骼数量、蒙皮权重等。

在骨骼蒙皮文件中查看LOD的索引:

创建SkeletalMeshLODSettings
配置表设置LOD的细节:

主要设置以下几个选项:

Screen Size
这是用控制LOD切换的参数,他决定了在屏幕上,模型占据多大比例时切换到特定的LOD级别。ScreenSize
是一个介于0到1之间的浮点值,表示模型在屏幕上的相对大小。
值为1时,表示模型占据整个屏幕高度。
值为0.5时,表示模型占据屏幕高度的一半。
值为0时,表示模型在屏幕上不可见。
LOD切换逻辑:
当模型的屏幕大小(基于其包围盒)小于或等于 ScreenSize 时,引擎会切换到对应的LOD级别。
例如,如果 ScreenSize 设置为0.5,当模型在屏幕上的大小小于或等于屏幕高度的一半时,引擎会使用该LOD级别。
可以使用命令A.VisualizeLODs 1
查看模型在场景中的ScreenSize
,如下图:

LOD Hysteresis

在一些特殊的情况下,LOD会一直切换,即使相机是固定的。为了解决频繁的切换造成的性能和抖动的问题,为LOD切换引入了一个缓冲区域。具体来说逻辑如下:
当模型从高 LOD 切换到低 LOD 时,切换的实际阈值会比配置的
ScreenSize
更低。当模型从低 LOD 切换回高 LOD 时,切换的实际阈值会比配置的
ScreenSize
更高。
假设 ScreenSize
是 LOD 切换的阈值,LODHysteresis
是一个介于 0 到 1 之间的值,用于调整切换的缓冲范围。
从高 LOD 切换到低 LOD: 实际切换阈值 =
ScreenSize
× (1 -LODHysteresis
)从低 LOD 切换回高 LOD: 实际切换阈值 =
ScreenSize
× (1 +LODHysteresis
)
例如:
如果 ScreenSize
为 0.5,LODHysteresis
为 0.1:
从高 LOD 切换到低 LOD 的阈值是
0.5 × (1 - 0.1) = 0.45
。从低 LOD 切换回高 LOD 的阈值是
0.5 × (1 + 0.1) = 0.55
。
这意味着:
当模型的屏幕大小从大于 0.5 减小到 0.45 时,才会从高 LOD 切换到低 LOD。
当模型的屏幕大小从小于 0.5 增加到 0.55 时,才会从低 LOD 切换回高 LOD。
Bone List
设置骨骼赛选Bone Filer Action Option
:
- Remove the joints specified and children
- Only Keep the joints specified and parents
一个简单的设置例子:
- LOD 01
- Remove the joints specified and children
- Face Joints
- Remove the joints specified and children
- LOD 02
- Only Keep the joints specified and parents
- Core Skeleton Keep
- Only Keep the joints specified and parents
Reduction Settings

这里有很多设置,核心讲几个比较重要的设置:
- Remap Morph Targets:关闭。
Morph Targets
是实现模型变形的技术,通过调整顶点的位置来实现面部表情、肌肉、布料等效果。在启用LOD时,引擎会减少模型顶点的数量,那么使用顶点做的变形就需要重新映射顶点。 - Max Bone Influences:降低到3和4。最小的可以降低到0。
- Geometry
- Enforce Bone Boundaries: 保留骨骼边界的完整性,避免动画变形问题。
- Merge Coincident Vertices Bones:合并重合的顶点和骨骼,优化模型数据。
- Volumetric Correction:保持模型的体积不变,避免形状失真。
- Lock Mesh Edges:锁定模型边缘,保留硬边和轮廓特征。
- Lock Vertex Colour Boundaries:保留顶点颜色的完整性,确保颜色信息正确。
- Improve Triangles for Cloth:优化布料模拟的三角形,提高模拟质量。
Animation Blueprint
Multi-threading
启用多线程运行动画蓝图。
确保在Project Settings->Engine->General Settings
中启用以下选项:

在每个动画蓝图的中的Class Settings
中启用以下选项:

Warning
在使用Root Motion
时,多线处理会失效,因为计算根节点位移实在Game线程中。
将所有更新的逻辑移动到Blueprint Thread Safe Update Aniamtion
,并且确保该函数调用的函数是BlueprintThreadSafe
启动该选项,获取参数使用Property Access
,详情可参考:文章
Fast Path
Fast Path
(快速路径) 是动画蓝图(Animation Blueprint)中的一种自动优化机制,旨在提高动画系统的运行效率。它通过减少不必要的计算和逻辑处理,使动画蓝图能够以更高的性能运行。
启动该机制前提是打开Project Settings->Engine->General Settings->Optimize Anim Blueprint Member Variable Access
选项。
查看节点是否启用了该机制,可以查看蓝图节点上是否有闪电标识如下图:

在动画蓝图中开启Warn About Blueprint Usage
选项,可以提示开发者使用的节点是否合理。

Fast Path
是引擎自动管理的功能,开发者无法手动启用或禁用它。然而,可以通过以下方式确保动画蓝图能够充分利用 Fast Path:
简化动画蓝图逻辑:
- 尽量避免在动画蓝图中使用复杂的逻辑(如自定义事件、复杂的变量计算等)。
- 使用简单的节点(如状态机、混合节点、骨骼控制器等)来实现动画逻辑。
- 直接调用本地参数,而不是调用引用对象的参数,使用
Property Access
访问参数。
避免使用不支持
Fast Path
的节点:- 某些节点(如蓝图函数调用、自定义事件等)可能会导致
Fast Path
被禁用。 - 尽量使用动画系统内置的节点来实现功能。
- 不要在
AnimGraph
中使用And
和Or
节点 - 不要在
AnimGraph
中使用乘法
- 某些节点(如蓝图函数调用、自定义事件等)可能会导致
优化动画蓝图结构:
- 将复杂的逻辑移到
Anim Instance
或Character Blueprint
中处理,而不是直接在动画蓝图中实现。
- 将复杂的逻辑移到
AnimGraph Functions
在AnimGrap
中很多计算都不能使用,那应该怎么正确的在AnimGrap
中使用计算呢?
Functions on nodes
使用绑定方法,如下图:

Call Function Node
在AnimGrap
中直接调用方法,使用的是UAnimGraphNode_CallFunction
,并且可以设置触发的时机。

没有具体使用过该节点,发现只要是在AnimInstance
中的方法且开启了BlueprintThreadSafe
,都可以在这里选择出来。
Functions Must Be Thread Safe
函数必须打开多线程安全

Use Case - Fast Path With Logic In AnimGraph
以下方式不能触发Fast Path
自动优化机制:

可以修改为:

AnimGraph LODs
Node LODs
可以设置节点上LOD来控制节点是否运行:

比如下图的叠加动画,当前角色LOD值大于填的值,就不会执行该节点

Predicted LOD Level

使用预测LOD来控制动画播放,比如下图:

Post Process ABP LOD
在骨骼蒙皮文件中可以对动画后处理蓝图进行设置LOD

AnimGraph Practices
Space Conversion Bundles
尽量减少使用空间转化的节点,把需要同一个空间计算的节点放在一起。

State Machine Settings

Max Transitions Per Frame
和 Max Transitions Requests
可以适当的减少。
Cached Pose
多使用缓存姿势:

不要在状态机里再套状态机,可以把所有状态机放在最上层,然后把他们的姿势存起来再使用。
Skeleton Pose Update
Visibility Base Anim Tick
在SkeletonMeshComponent->Details->Optimization
中设置:

可以设置为Only Tick Pose when Rendered
,表现如下图:

统一修改Project/Config/DefaultEngine.ini
文件中:
[/Script/Engine.SkeletalMeshComponent]
VisibilityBaseAnimTickOption=OnlyTickPoseWhenRendered
这只能修改到SkeletalMeshComponent
,并不能修改Skeletal Mesh Actors
。如果想要全部修改的话,只有修改引擎文件:
SkinnedMeshComponent.cpp
构造函数中VisibilityBasedAnimTickOption
的默认值SkeletalMeshActor.cpp
构造函数中SkeletalMeshComponent->VisibilityBasedAnimTickOption
的默认值
No Skeleton Update
在一些场景中人物不需要更新骨骼,可以启用USkeletalMeshComponent->bNoSkeletonUpdate
选项,停止更新骨骼,同时动画和物理模拟也不会更新了。

可以使用一个触发盒来启用该选项,离开该盒子后就关闭该选项。
Update Rate Optimizations
打开以下两个选项:

可以看到更新的频率:
不同颜色的表示:
绿色:
- 表示骨骼网格体正在以 最高频率 更新(例如每帧更新)。
- 这意味着该骨骼网格体没有被优化,或者优化被禁用。
黄色:
- 表示骨骼网格体正在以 较低的频率 更新(例如每两帧更新一次)。
- 这是
URO
的典型优化状态,表示骨骼网格体的更新频率被降低以节省性能。
红色:
- 表示骨骼网格体 完全停止更新。
- 这意味着骨骼网格体的更新被跳过,通常是因为它被认为对当前帧的渲染不重要。
蓝色:
- 表示骨骼网格体正在以 最低频率 更新(例如每四帧更新一次)。
- 这是更激进的优化状态,适用于对性能要求极高的场景。
如果你不想使用C++来编写更新频率的逻辑,可以使用下面这个插件:

以下是简单的一个示例根据LOD数值控制刷新率:

下面是一个简单控制刷新率的例子,没有进行插值的效果如下:

在打开插值过后,可以看到20帧刷新一次的跟每帧刷新一次的没有太大的区别:

Budgeted Skeletal Mesh Component
插件Animation Budget Allocator

使用Skeletal Mesh Component Budgeted
替换所有的Skeletal Mesh Component

然后再使用命令a.Budget.Enabled 1
启用

这样就可以看到当前场景中的Skeletal Mesh Component Budgeted
使用情况:

可以手动设置动画的预算为1ms:

查看场景中的表现:

Texture
Texture Size
图片的倍数:

Size Limit
可以设置贴图中的Maximum Texture Size
来控制纹理最大分辨率,它决定了纹理在导入或运行时可以被压缩到的最大尺寸。

以下是一个4k图片设置对比:

统一修改在文件Engine\Config\BaseDeviceProfiles.ini - GlobalDefaults DeviceProfile
:

设置Texture Group
在Texture -> Level of Detail -> Texture Group Character
:

可以多选贴图使用Property Matrices
同时设置。
Micro Detail
细节纹理容易在压缩纹理之后丢失掉,看起来会很糊。

所以我们可以将这些细节纹理用Shader的方式呈现,比如在材质中Tiling这些细节纹理,而不是将细节纹理烘焙在主纹理上。

Compression
The Cost of aphla
一般导入是以下格式,不过需要手动检查以下该图片是否需要透明通道,如果不需要就需要启用以下这个选项:

以下是有透明通道和没有透明通道的对比:

压缩算法的编码不同导致储存大小不同,对比如下图。如果我们的磁盘与内存真的不够,就需要重点考虑哪些纹理确实不需要Alpha通道,哪些纹理不需要高精度的颜色表现了。

G8
有时候我们只需要单通道纹理,这时候G8压缩方法就比较好,这个方法会只留下Red通道。所有这些压缩方法可以在纹理的细节面板中调整。

Oodle
我们在打开纹理编辑器的时候可能就已经见过这个藏在角落的Oodle面板了,但这个东西到底有什么用?

这个面板可以让我们调整相关的纹理编码参数:

当我们调整出了一个看起来很满意的参数之后就可以在BaseEngine
配置文件中修改全局默认值了。

Channel Packing
通道合成 一个很常见的例子就是将AO
遮罩、粗糙度遮罩和金属度遮罩合并成一张RGB
纹理。我们甚至还可以加多一张Emissive
或者别的什么遮罩放在Alpha
通道里,随我们喜欢。这样我们就可以在材质中减少纹理采样,优化纹理复杂度。
在权衡了纹理质量的前提下,想要追求极致优化,可以合成法线纹理。首先我们需要将法线纹理的压缩设置改回默认,然后在材质中这么写。这样我们就可以往法线纹理的B通道中加东西了。

注意!这种方案非常影响纹理的质量,下图是结果,请权衡好质量与优化。

Masks RGB
蒙版-大部分人会以为只能往RGBA纹理中塞4种蒙版,但其实我们可以往里面塞9种蒙版!

Skeletal Mesh Components
Bounds
骨骼网格体组件还有更多参数设置。我们在更新骨骼网格体时会更新它的包围盒,包围盒的更新我们也可以进行一些调整。执行ShowFlag.Bounds 1
可以显示组件的包围盒,这个可以帮助我们Debug。

一般可以设置以下几个:
上文提到过,骨骼网格体可以跳过更新帧,然后通过插值对骨骼变形进行平滑。而包围盒的更新也可以跳过更新帧,并且不在平滑后更新。将包围盒更新模式调整成
Skip Bounds Update When Interpolating
即可。因为遮蔽剔除和相机剔除都是用包围盒计算的,假如包围盒进入剔除范围,这个组件就不会被渲染。骨骼网格体组件可以固定包围盒(
Component Use Fixed Skel Bounds
),但存在一定的风险,它可能会在应该渲染的时候不渲染。当然,如果确定我们的角色不会超过这个包围盒,我们就可以放心使用固定包围盒。如果我们采用了模块化角色方案,可以调整成使用父项骨骼网格体的包围盒模式(
Use Parent Bounds/Bounds From Leader Pose Component
)。
以上这些设置能在USkeletalMeshComponent
找到:

Render Static
假如我们有一个骨骼网格体,但是它在游戏内不需要动画,这时候这个Render Static
选项就很有用。并且这个选项可以在游戏内自由调节。

Nanite Skeleton
现在Nanite骨骼网格体还没出,但我们可以用点小伎俩。比如我们可以导入一个只有单个三角面的骨架(因为虚幻不允许导入没有面与蒙皮的骨骼网格体),然后将这个三角面关掉。

导入一些静态网格体,静态网格体可以用Nanite,然后再将他们绑定在骨架上。

这样我们就得到了一个“Nanite骨骼网格体”,这非常适用于各种刚性的骨骼网格体上。

Blueprints
Tick
Option
Turn it Off by Default
- Blueprint
- Component
- Blueprint
Turn it Off by Default - Source Code
- Actor.cpp - PrimaryActorTick.bStartWithTickEnabled = false;
- ActorComponent.cpp - PrimaryComponentTick.bStartWithTickEnabled = false;
- Controller.cpp - PrimaryActorTick.bStartWithTickEnabled = true;
Tick Rate

Source Code:
- Actor.cpp - PrimaryActorTick.TickInterval = 1.0f;
- ActorComponent.cpp - PrimaryComponentTick.TickInterval = 1.0f;
dumpTicks
执行dumpTicks指令可以输出现在在Tick的事件有哪些。

References
引用如果处理不当也会出现大问题,这会导致资产拖着一堆引用,导致加载不畅或者内存占用高。当我们打开Size Map
就能看到资产的硬引用,加载第三人称角色蓝图意味着要加载上Size Map
上显示的这些资产。

举一个反面案例,这是硬引用处理不当导致的。加载角色的时候加载多了一些载具相关的东西。

这是因为角色蓝图里有一个载具类引用的变量,或者Cast To
了载具类。

Interface
如何处理Cast
就很重要了,首先如果我们确实需要用到某个类,放心Cast
,比如角色与动画蓝图之间的Cast
。

但如果我们真的需要用到类里面的逻辑怎么办?可以试试用接口。

然后在蓝图中实现它即可。

Soft Reference

软引用能做的东西不多,并且我们在使用它之前得将它加载成硬引用。Blocking节点会阻塞游戏线程,而Async节点会异步完成加载任务。

Warning
要注意的是,以上这些节点加载出来的引用会卡在蓝图的生命周期里,不会被垃圾收集处理,在加载完,完成了对应的任务之后,推荐用一个Set Object Reference (by ref)
节点清掉这个引用,让垃圾收集处理它。
Parent Class Component Assignments
我们一般会将角色蓝图的子蓝图作为角色的变体,修改里面的网格体和里面的一些参数。这个例子是用Quinn
的主蓝图生成了一个Mannequin
的子蓝图。

当我们打开子蓝图的Size Map
就会发现,它加载上了主蓝图中的Quinn
骨骼网格体。

这种情况下,需要将主蓝图中的网格体或者其他一些硬引用去掉。

然后再创建两个子蓝图,分别对应Quinn和Mannequin。这样两个子蓝图就会只加载自己对应的引用资产。
