在 虚幻引擎(Unreal Engine) 中使用动画蓝图开发动画系统时,你可以使用多种动画优化技术来提高动画系统的性能。
一些动画优化属性和设置在虚幻引擎编辑器的框架中可用,而其他方法和工具可通过项目的C++代码库启用和控制。虽然引擎自带的工具集对于大部分用例来说功能全面,但一些情况下,C++逻辑可最直接地控制动画求值和播放。
你可以使用以下文档详细了解虚幻引擎中的动画优化技术。
帧率
项目的性能,或者说项目能够多快速地对其所有游戏系统求值并渲染其场景和角色,这取决于你的 中央处理器 (CPU) 和 显卡 (GPU) 能够在设定时间内处理的工作量。项目的帧率越高,处理数据所需的时间就越少,因为相比于于帧率更低的项目,帧与帧之间的时间更短。
在项目早期决定以哪种帧率为目标,是一个重要的步骤。常见的项目帧率从 30 到 60 不等,有时甚至更高。对于在主机和移动平台等功能不太强大的硬件上运行的项目,低帧率更常见,而在PC硬件上运行的项目通常可以采用高帧率,满足基于用户硬件可用的带宽。但是,即使是在功能强大的硬件上运行,你也可能需要考虑选择一个设定帧率来运行项目,为玩家创造更稳定一致的体验。
游戏动画常常以每秒30帧进行编写或创建。虚幻引擎能够内插每秒30帧的动画,在以每秒60帧运行时看起来流畅。但是,为了打造特定的观感体验,一些动画可能以不同帧率编写。
下面你可以查看同一个动画以每秒30帧和每秒60帧运行时的差异。每秒30帧的动画包含更少的信息,动画播放时质量更低。每秒60帧的动画以更高保真度播放,但需要更多求值,降低了项目的可用带宽,尤其是扩展到整个动画系统,涉及多个角色或对象时。
每秒30帧 | 每秒60帧 |
---|---|
![]() |
![]() |
动画会添加到动画蓝图,其中动画会在运行时使用 游戏线程 求值并在角色上播放。混合、IK求值、物理模拟等其他进程将分别从项目的性能预算中扣除,以便求值。一些进程很简单,求值时不需要多少性能预算,另一些进程可能会执行更高级的运算,生成更美观的动画,但可能需要大量性能预算。所有动画系统功能都有关联的性能成本。
使用虚幻引擎的 动画洞察(Animation Insights) ,你可以在项目模拟期间生成项目的动画性能成本的直观表示,观察你的游戏和动画系统是如何使用性能预算的。

有关设置和使用动画洞察来优化项目的更多信息,请参阅以下文档:
animating-characters-and-objects\SkeletalMeshAnimation\Debugging\AnimInsights
使用多线程动画更新
在虚幻引擎中,进程划分为多个线程,因此你的目标硬件能够在可能的情况下同时对多个事项求值。 游戏线程 是用于逐帧对项目的蓝图循序求值的主线程,这些蓝图包含游戏系统、角色动画系统、物理,等等。

虚幻引擎使用GPU上运行的 渲染线程 等其他线程来执行其他功能,例如渲染你的场景。

你可以在项目中实现 多线程 ,并使用个别蓝图将工作负载拆分为单独的子线程,同时对多个进程求值,只要你的目标硬件有多个CPU核心可供项目使用即可。这样可以同时而不是按顺序执行多个函数,可极大地减少在运行时对项目的每个帧求值所用的时间。正确实现多线程,项目就能够实现比传统计算技术更高的帧率。

所有虚幻引擎项目默认启用了多线程,并且可以在你的项目设置中切换。在菜单栏中,找到 编辑(Edit) > 项目设置(Project Settings) 。在项目设置窗口中,找到 通用设置(General Settings) > 动画蓝图(Anim Blueprints) ,然后你可以切换 允许多线程动画更新(Allow Multi Threaded Animation Update) 。

为了利用多线程动画蓝图求值,你还将在你希望利用此系统的每个动画蓝图中启用 使用多线程动画更新(Use Multi Threaded Animation Update) 属性。此属性默认启用,但可以在 优化(Optimization) 下动画蓝图的 类默认值(Class Defaults) 中切换。

你可以使用多线程动画蓝图求值,在不同线程之间更好地控制和访问项目的数据,并提高项目的动画系统性能。
此外,通过启用就蓝图使用发出警告,多线程动画蓝图更新可以与特殊警告一起使用。每当调用蓝图虚拟机时,此属性将向 编译器结果(Compiler Results) 日志窗口发出警告。
多线程函数
运行你的项目时,AnimGraph和EventGraph上的动画蓝图求值会在游戏线程上执行。如果你的项目和动画蓝图可以使用多线程,你可以选择使用单独的函数构建动画蓝图逻辑,允许同时在多个子线程之间对多个进程求值。通过将进程分成多个函数,你可以使用多个线程来减少动画系统的瓶颈。
数据结构体
你可以使用 FAnimInstanceProxy
结构体访问 AnimGraph 数据。使用此代理结构,你可以访问 UAnimInstance
结构体中的大部分数据。
在大部分用例中, UAnimInstance
结构体不应该从AnimGraph节点的 更新(Update) 或 求值(Evaluate) 进程中访问或改变,因为这些函数可能在其他线程上运行。要防止这种访问,有一些锁定包装器,例如 GetProxyOnAnyThread
和 GetProxyOnGameThread
结构体,它们在任务求值的过程中可阻止访问 FAnimInstanceProxy
。这样会强制任务排队,防止任务使线程过载,因此每个任务会等待之前的任务完成,然后才允许从代理读取数据或将数据写入代理。
AnimGraph只能从动画蓝图节点访问 FAnimInstanceProxy
,而不是 UAnimInstance
。对于每次更新,必须通过 FAnimInstanceProxy::PreUpdate
或 FAnimInstaceProxy::PreEvaluateAnimation
中的缓冲、复制或其他某个进程与代理交换数据。然后,需要由外部对象访问的数据应该与 FAnimInstanceProxy::PostUpdate
中的代理交换或从中复制。
这与通用用例 UAnimInstance
相冲突,在后者的情况下,任务正在进行中时,可从其他类访问成员变量。建议完全杜绝从其他类直接访问动画实例。动画实例应改为从别处拉取数据。
示例自定义原生AnimInstance
以下代码块示例说明了你可以如何使用新 FAnimInstanceProxy
构建自定义原生 AnimInstance
类,授予对内部工作机制的访问权限,并避免在代理与实例之间复制共享数据:
USTRUCT()
struct FExampleAnimInstanceProxy : public FAnimInstanceProxy
{
GENERATED_BODY()
FExampleAnimInstanceProxy()
: FAnimInstanceProxy()
{}
FExampleAnimInstanceProxy(UAnimInstance* Instance);
virtual void Update(float DeltaSeconds) override
{
// 更新内部变量
MovementAngle += 1.0f * DeltaSeconds;
HorizontalSpeed = FMath::Max(0.0f, HorizontalSpeed - DeltaSeconds);
}
public:
UPROPERTY(Transient, BlueprintReadWrite, EditAnywhere, Category = "Example")
float MovementAngle;
UPROPERTY(Transient, BlueprintReadWrite, EditAnywhere, Category = "Example")
float HorizontalSpeed;
};
UCLASS(Transient, Blueprintable)
class UExampleAnimInstance : public UAnimInstance
{
GENERATED_UCLASS_BODY()
private:
// AllowPrivateAccess元标记将允许此项公开给蓝图,
// 但仅公开给此类的内部图表。
UPROPERTY(Transient, BlueprintReadOnly, Category = "Example", meta = (AllowPrivateAccess = "true"))
FExampleAnimInstanceProxy Proxy;
virtual FAnimInstanceProxy* CreateAnimInstanceProxy() override
{
// 覆盖此处以仅返回此实例上的代理
return &Proxy;
}
virtual void DestroyAnimInstanceProxy(FAnimInstanceProxy* InProxy) override
{
}
friend struct FExampleAnimInstanceProxy;
}
动画快速路径
动画快速路径(Animation Fast Path) 提供了优化 AnimGraph 更新中的变量访问的一种方式。这样引擎就可以在内部复制参数,而不是执行蓝图代码,后者需要调用 蓝图虚拟机(Blueprint Virtual Machine) 。编译器目前可以优化以下结构体:
-
成员变量(Member Variables)
-
否定布尔成员变量(Negated Boolean Member Variables)
-
嵌套结构的成员(Members of a Nested Structure)
所有虚幻引擎项目默认启用了多线程,并且可以在你的项目设置中切换。在菜单栏中,找到 编辑(Edit) > 项目设置(Project Settings) 。在项目设置窗口中,找到 通用设置(General Settings) > 动画蓝图(Anim Blueprints) ,然后你可以切换 允许多线程动画更新(Allow Multi Threaded Animation Update) 。
动画快速路径选项在 项目设置(Project Settings) 中默认启用。要切换动画快速路径,请在项目设置中找到 通用设置(General Settings) > 动画蓝图(Anim Blueprints) ,再找到 优化动画蓝图成员变量访问(Optimize Anim Blueprint Member Variable Access) 属性。

要利用动画快速路径,在动画蓝图的AnimGraph中,确保没有执行蓝图逻辑。
在以下示例蓝图中,AnimGraph正在读取多个浮点值,这些浮点值用于驱动多个混合空间资产和一个混合节点,生成最终动画姿势。右上角以闪电图标表示的每个节点都在利用快速路径,因为没有执行逻辑。

如果将任意形式的计算添加到图表中,关联的节点将不再使用快速路径。在以下示例中,一个简单的乘法函数添加到浮点变量,导致Blend Space节点无法使用快速路径。编译图表之后,会删除闪电图标,表示这种变化。

快速路径方法
下面是可用于在动画蓝图中实现快速路径变量访问的方法。
直接访问成员变量
你可以直接访问和读取布尔变量的值来确定姿势,从而使用快速路径。

下面你可以看到,在执行逻辑以确定布尔值的状态时,Blend Poses by bool节点避开了快速路径变量访问,而不是直接访问。

访问嵌套结构体的成员
你可以分解嵌套结构体,例如旋转体变量,以直接访问其他组件。要分解结构体,你可以直接右键点击图表中的变量,并从上下文菜单选择 拆分结构体引脚(Split Struct Pin) 。这会将节点转换为你可以直接访问其组件值的变量。

使用Break Struct节点访问成员
你也可以使用 Break Struct 节点分解嵌套结构体,以便直接访问其组件值。

Break Transform 等一些 Break Struct 节点目前不会使用快速路径,因为这些节点在内部执行转换,而不是直接复制数据。
就蓝图使用发出警告
要确保你的动画蓝图在使用快速路径,你可以启用 就蓝图使用发出警告(Warn About Blueprint Usage) 属性。启用"就蓝图使用发出警告"后,从AnimGraph调用蓝图虚拟机时,编译器将向 编译器结果(Compiler Results) 面板发出警告。
要启用 就蓝图使用发出警告(Warn About Blueprint Usage) ,请在 优化(Optimization) 下你的 动画蓝图(Animation Blueprint) 的 类设置(Class Settings) 中启用该选项。
在启用"就蓝图使用发出警告"属性的情况下,在AnimGraph中执行蓝图逻辑时,可以使用快速路径访问变量时,"编译器结果"面板中将显示一条警告消息。你可以点击警告消息中的链接,在图表中明确指出警告的来源。这有助于跟踪需要进行的优化,并且你可以识别可能可优化的节点变量访问。

通用技巧
随着你开始考虑项目的动画系统的性能,你可以在优化过程中考虑以下指南。每个项目都有独特的优化要求,根据项目的大小和范围,可能需要更激进的更改,不过,以下指南提供了大部分项目可从中受益的通用方法。
-
确保满足并行更新的条件。
- 在UAnimInstance::NeedsImmediateUpdate结构体中,你可以看到为避免动画的更新阶段在 游戏线程 上运行而必须满足的所有条件。如果角色移动需要 根骨骼运动 ,将无法执行并行更新,因为角色移动不是多线程的。
-
避免调用蓝图虚拟机。
-
考虑将蓝图原生化到C++代码中。
-
建议不要在动画蓝图的 事件图表(Event Graph) 中执行逻辑。而是使用自定义UAnimInstance和FAnimInstanceProxy派生类,并在FAnimInstanceProxy::Update或FAnimInstanceProxy::Evaluate期间执行代理中的逻辑,因为这些是在工作线程上执行的。
-
确保你的动画蓝图的 动画图表(Anim Graph) 中的节点构造为使用 快速路径(Fast Path) 。
-
确保在 项目设置(Project Settings) 中启用了 优化动画蓝图成员变量访问(Optimize Anim Blueprint Member Variable Access) 属性,因为它控制直接访问其类的成员变量的动画蓝图节点是否应该使用避免调用蓝图虚拟机的优化路径。
-
一般而言,AnimGraph的执行中成本最高的部分是调用蓝图虚拟机。要从动画蓝图中获得最高性能,避免这些调用至关重要。
-
-
尽可能使用 更新速率优化(Update Rate Optimizations) (URO)。
-
URO将防止你的动画过于频繁地更新。控制其应用方式将取决于你的项目需要,但建议对大部分角色设定在15Hz及以下、在合适距离运行的更新速率目标,并禁用插值。
-
要启用URO,请在骨骼网格体组件的 细节(Details) 面板中找到 优化(Optimization) 分段,并选择 启用更新速率优化(Enable Update Rate Optimizations) 属性。接着,你可以使用
AnimUpdateRateTick()
结构体设置并观察蓝图的更新速率。- 你还可以选择在骨骼网格体组件的细节面板中启用 显示调试更新速率优化(Display Debug Update Rate Optimizations) 属性,启用模拟期间应用于项目的URO速率的屏幕上调试显示。
-
-
当你的角色不需要访问其物理资产时,启用 组件使用固定骨骼边界(Component Use Fixed Skel Bounds) 。
-
在骨骼网格体组件的 细节(Details) 面板中,启用 组件使用固定骨骼边界(Component Use Fixed Skel Bounds) 属性。
- 这将跳过使用物理资产,而改为总是使用骨骼网格体中定义的固定边界。
- 这还将跳过为每个帧重新计算用于剔除的包围体,从而提高性能。
-
其他注意事项
分析项目时,使用动画洞察,你可以看到,在工作线程完成其进程之后,FParallelAnimationCompletionTask在主线程上为骨骼网格体运行。满足并行更新的条件后,此进程将是你在配置文件中看到的大部分主线程工作。它通常包含几个事项,具体取决于你的设置:
-
计算项目的组件的移动,例如更新物理对象。
- 对于实际不需要更新物理的事项,尽可能避免更新物理。这样会为FParallelAnimationCompletionTask造成最大幅度的缩减。
-
启动动画通知。
-
所有通知都应该不基于蓝图,以避免调用蓝图虚拟机。
-
通知应该在 游戏线程 上执行,因为通知可能影响动画对象的生命周期。
-
-
启用URO时的动画插值。
-
使用材质或变形目标曲线时的曲线混合。