概要
UnrealEngine5 にてCPU負荷軽減における注意事項をまとめたメモです。UE5独自ではなくUE4でも使えるものもあります。
基本的な「余計なTick処理を省く」「LOD設定をやる」「複雑なコリジョンを使用しない」などの対応を行ったうえで検討してみるといいと思います。
更新履歴
| 日付 | 内容 |
|---|---|
| 2023/05/17 | 初版 |
| 2023/06/07 | PoolingMethod, MaxSimulationIterations等を追記 |
| 2023/07/07 | AIPerceptionについて追記 |
参考
以下の記事を参考にいたしました、ありがとうございます。
UE公式:CPUプロファイリング
UE公式:Task System
UE公式:Blendノード
UE公式:エフェクト タイプを使用したパフォーマンス バジェット管理
UE公式:UMGの最適化に関するガイドライン
Unreal Engine - プロファイリング&最適化の始め方
[UE5] UnrealInsights を使ってみよう
UE4で多数のキャラクターを生かすためのテクニック【CEDEC 2018】
UnrealEngineにおけるマルチスレッディング入門 タスクグラフ編【CEDEC+KYUSHU 2021 ONLINE】
『FINAL FANTASY VII REMAKE』におけるプロファイリングと最適化事例 【UNREAL FEST EXTREME 2021 SUMMER】
【UE4】URO(UpdateRateOptimizations)でアニメーションの最適化
関連する過去記事
UE4 CPU負荷軽減についてのメモ
UE4 Tickの処理順序についてのメモ
UE4 アニメ―ションノードの作成(ボーンのトランスフォーム)を試してみる
UE5 アニメーションのマルチスレッド処理についてのメモ
環境
Windows10
Visual Studio 2022
UnrealEngine 5.1.1
関連ソース
"Engine\Plugins\Experimental\ChaosCaching\Source\ChaosCaching\Public\Chaos\CacheManagerActor.h"
"Engine\Source\Runtime\Engine\Classes\GameFramework\CharacterMovementComponent.h"
"Engine\Source\Runtime\Engine\Private\Components\CharacterMovementComponent.cpp"
"Engine\Source\Runtime\Core\Public\Tasks\Task.h"
Niagara
UE5からはCascadeが非推奨になったのでNiagaraがエフェクトシステムとしてはデフォルトとなります。
SimTarget
エミッタのプロパティ [Sim Target]にてCPUSimではなくGPUCompute Simに変更すると計算がCPUからGPUへ変更されるためCPU負荷が減ります(GPU負荷は増えます)。
CPUSimだと負荷が増えやすいGameThreadでの計算となるため特別な事情がない限りはやっといた方がいいかと思われます。
パーティクルコリジョン
パーティクルのコリジョンが有効な場合、これもGPU計算にできます。GPUCompute Simにすると自動で設定されるようです。
CPUSim と GPUCompute Sim の負荷検証
テスト作成したパーティクルにて stat unitにて確認をすると以下の様に GameとDraw にて値の違いが確認できました。
パーティクル数700ほどを発生させていますが、CPU負荷が下がっています(GPU負荷は上がっています)。
GPUCompute SimではCalculate Bounds ModeでDynamicを選択すると警告がでてしまうことに注意が必要です。
Scalability
エミッタのステートにScalabilityの設定があるのでこれを適宜調整することで負荷軽減になります。
ScalabilityMode を system から self に切り替えると各種設定が可能になります。
▼Enable Distance Culling
距離による設定で、Min Distance Responseが設定距離以下になった場合の処理設定。 Max Distance Responseが設定距離以上になった場合の処理設定となります。
▼Scale Spawn Count
パーティクル量の調整項目で、Spawn Count Scaleがパーティクル量の倍率、距離によって変化を付ける場合はSpawn Count Scale By Distanceにチェックを入れます。
▼Enable Visibility Culling
カメラ表示によるカリング設定項目で、Visibility Culling Response設定で処理されます。
SLEEP AND LET PARTICLES FINISH か SLEEP AND CLEAR PARTICLES を設定することになると思います。
Effect Type
エフェクトタイプアセットを作成してエミッタシステムに割り当てることで、同一タイプによるバジェット管理ができるようです。
これにより、割り当てられた使用量を超える同一エフェクトタイプではカメラ距離や優先に基づいてカリング処理が行われるようです。
PoolingMethod
エフェクトをスポーンする際に Pooling Method を Auto Releaseにするとエフェクトをプールして再度同じエフェクトをスポーンする際に使いまわしをしてくれるようです。これによりスポーン時の処理負荷が軽減されます。使用頻度が高い単発エフェクト(攻撃ヒットエフェクトや移動時の砂煙エフェクトなど)に使うと効果があると思われます。
Manual Releaseはエフェクト終了時に明示的に UParticleSystemComponent::ReleaseToPool() を呼ぶ必要があるようです。単発ではないエフェクトはこちらを使う必要があるようです。
Chaos Destruction
UE5のデフォルト物理エンジンChaosの破砕シミュレーションです。
ジオメトリコレクションの表示
ジオメトリコレクションは破砕された状態での表示扱いとなり、LODなどはないため破砕しない場合や遠方などでの適用は何らかの対策すべきです。(ただDrawCall数は変らないぽい?)
考えられる対策としては破砕前と同等のスタティックメッシュを用意して差し替えなどがあると思います(スムーズに差し替えるにはそれなりに難しいとは思いますが)。
Choas Cache Manager
キャッシュを利用することで負荷が軽減できます。ただし予め記録したキャッシュデータのとおりに破壊されることになります(インタラクティブではない)。
キャッシュマネージャは[アクタ]->[Chaos]->[キャッシュマネージャを作成]から作成します。

キャッシュマネージャの設定は以下のようになります。
-
Cache CollectionにChaosキャッシュコレクションを設定。 -
Cache Nameに任意のキャッシュ名を設定。 -
Referenced Actorに破砕対象のジオメトリコレクション。 -
Component Nameにジオメトリコレクションコンポーネント名を設定。
Chaosキャッシュコレクションはコンテンツブラウザから右クリック->[フィジックス]->[Chaosキャッシュコレクション]でアセットを作成します。
キャッシュマネージャの Cache Mode を 記録 にし、PIEを起動することで記録を開始します。

記録に成功すると以下のようにChaosキャッシュコレクションに設定したキャッシュ名で保存されます。

Cache Mode を プレイにすることで再生されます。任意のタイミングが必要な場合は Start Mode を Timed から Triggered に変更し、ChaosChacheManager から Trigger All等を実行することで再生ができます。

負荷検証
キャッシュあり/なしでGame負荷が落ちているのが確認できます。
CharacterMovementComponent
エンジン付随のアクター移動コンポーネント。単純な移動ならこれを使わずに作成したほうが軽くなります。
MovementMode
NavMesh使用している条件下なら移動中のコリジョンチェックをなくすことができます。
注意
NavMeshが条件になるため、NavMeshがない場所に移動するとWalkingモード(コリジョンチェックあり)に戻ります。また段差等の高さの差がある個所の移動が怪しくなります。
CharacterMovementComponent の詳細設定の DefaultLandMovementMode を Navmesh Walkingに、Sweep While Nav Walking のチェックも外します。
対象のアクターがレベルに配置済の場合、上記デフォルト設定で DefaultLandMovementMode を変えてもNavMeshが見つけられず、すぐ Walkingモードに戻ってしまう場合があるので、
スポーン時にNavMesh Walking に設定してしまう方法がよいかもしれません。以下コード例。
▼C++でのMovementMode設定
#include "GameFramework/CharacterMovementComponent.h"
void ATestEnemyCharacter::BeginPlay()
{
Super::BeginPlay();
UCharacterMovementComponent* _CharaMC = GetCharacterMovement();
if( _CharaMC ){
// 移動モード設定
_CharaMC->SetMovementMode(EMovementMode::MOVE_NavWalking);
// NavWalk中はSweepしない
_CharaMV->bSweepWhileNavWalking = false;
}
}
コリジョンアナライザーでの検証
[ツール]->[デバッグ]->[コリジョンアナライザー]にて各MovementModeでの移動中のコリジョンを確認すると以下のようになりました。
移動時の床チェックComputeFloorDistがなくなっており、Sweep処理もなく移動中のOverlapチェックのみになっているのが確認できます。
このOverlapもコリジョンの Generate Overlap Eventsのチェックを切ると消えます。(ただしOverlapイベント受け取りができません)
通常操作の(PlayerControllerの)ポーンでも NavWalkingモードでの移動は可能ですが、NavMeshがない場所(途切れているような場所)へ移動すると通常モードに戻るためあまり意味がないかと思います。
なので使用する対象としては、動く範囲がNavMesh内に決まっているアクターにとどめておく方がいいと思われます。
MaxSimulationIterations
UCharacterMovementComponent::PhysWalking() にて行われている処理で、Walkingモード時の移動位置シミュレート最大回数の値です、コリジョンチェック等を複数回繰り返しているようです。複雑なコリジョンや高速移動するようなオブジェクト以外は減らすことで負荷軽減になるようです。

CharPhysWalkingの検証
UnrealInsights を使って検証してみましたが、差があまりでませんでした。フラットな地形ではなく複雑な地形でないと差がでにくいと思われます。

Update Only if Rendered
Update Only if Renderedにチェックを入れると非表示時の移動処理をスキップします。非表示時に移動の必要がないキャラクタの場合に有効だと思われます。

AI Perception
AIに対する感覚システムで、視覚、聴覚、触覚などがあります。
感覚システムのOn/Off制御
感覚システムを稼働する側(敵キャラの視界など)を適宜On/Offすることで処理負荷軽減が期待できます。
登録(知覚システムの開始)はUAIPerceptionSystem::UpdateListener で、解除(知覚システムの停止)は UAIPerceptionSystem::UnregisterSource で行います。BPには対応していないようです。
#include "Perception/AIPerceptionComponent.h"
#include "Perception/AIPerceptionSystem.h"
auto PerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld());
if (PerceptionSys) {
if(_IsRegist){
// 登録
PerceptionSys->UpdateListener(*AIPerceptionComponent);
}else{
// 解除
PerceptionSys->UnregisterListener(*AIPerceptionComponent);
}
}
視界対象ポーンの自動登録の停止
AIPerception_Sight は基本ポーンを対象とした仕組みでシステム開始時にレベル上のポーンが自動登録されます。検索する対象が多いと当然処理負荷が高くなるため、必要な対象ポーンだけ登録することが可能ならば負荷軽減につながると思います。
各AISenseクラスの bAutoRegisterAllPawnsAsSources を false にすると対象の自動登録をなくすことができ、DefaultGame.iniに以下のような設定をすることで実現ができます。
以下、AISense_Sightの場合のコード例。
[/Script/AIModule.AISense_Sight]
bAutoRegisterAllPawnsAsSources=false
代わりに手動で対象ポーンを登録する必要があります。UAIPerceptionSystem::RegisterSource で行います。
以下コード例。
#include "EngineUtils.h"
UWorld* World = GetWorld();
for (TActorIterator<APawn> PawnIt(World); PawnIt; ++PawnIt)
{
if( PawnIt->ActorHasTag(FName("MyChara"))){ // タグで判別する
// 対象登録
RegisterSource(SenseID, **PawnIt);
}
}
AIPerceptionSystemの差し替え
UAIPerceptionSystemクラスを差し替えて知覚システムの対象ポーンを制御することなどができます。
プロジェクト設定の[エンジン]->[AIシステム]->[認識システムクラス]で差し替えられます。

UAIPerceptionSystemクラスの差し替えコード例。
#include "CoreMinimal.h"
#include "Perception/AIPerceptionSystem.h"
#include "MyAIPerceptionSystem.generated.h"
UCLASS()
class MYPROJECT_API UMyAIPerceptionSystem : public UAIPerceptionSystem
{
friend class UAISystem;
GENERATED_BODY()
protected:
// 全ポーンを全センスへ登録
virtual void RegisterAllPawnsAsSourcesForSense(FAISenseID SenseID) override;
// ポーンが新規作成された際に各センスへの通知と登録を行う
virtual void OnNewPawn(APawn& Pawn) override;
};
#include "MyAIPerceptionSystem.h"
#include "Perception/AISense.h"
#include "EngineUtils.h"
#include "AIController.h"
void UMyAIPerceptionSystem::RegisterAllPawnsAsSourcesForSense(FAISenseID SenseID)
{
// bAutoRegisterAllPawnsAsSources が true の場合呼ばれる。
// 全ポーンを勝手に登録しないように判定する場合はここでコードを書く
// Super::RegisterAllPawnsAsSourcesForSense(SenseID);
}
void UMyAIPerceptionSystem::OnNewPawn(APawn& Pawn)
{
// 新たなポーンが生成されるときに呼ばれる。
// そのポーンを登録するかしないかを判定する必要がある場合はここにコードを書く
// Super::OnNewPawn(Pawn);
}
Animation
アニメーションはFastPathの利用、UpdateRateOptimization(URO)の利用等でCPU処理負荷軽減を図ることができます。またUE5からは PropertyAccessをつかってAnimBPでのマルチスレッド処理を書くことが可能です。
慣性ブレンド(Inertialization)
UE4.24から追加されたブレンド方法です。
通常のブレンドはAからBへ遷移する場合、AとB両方のアニメーションを計算して徐々にウエイトを移していきますが、慣性ブレンドの場合は即時にBに切り替え、遷移前のAは切り替え時の慣性を使用してブレンドを行います。それによりアニメーション評価関数の呼び出しが減るためCPU処理負荷が減るようです。
トランジションのブレンドロジックをInertializationに変更し、そのロジック後に慣性化ノードをつなぐことで使用できます。(Outputポーズノード手前でOK)


トランジション以外にも各ブレンドノードのブレンドロジックにて設定が可能なようです。
注意
慣性ブレンドは遷移元のアニメーション評価も止まるため、ブレンドタイミング後でアニメーションシーケンスに設定した通知もされないことに注意が必要です。
Animation停止
不要なタイミングでアニメーションを停止することで負荷軽減になります。SkeletalMeshComponentに各種アニメーションを停止するメソッド/フラグがあります。
SetRender Static
動的変更が可能で、アニメーション更新を停止します。停止後はデフォルトポーズになります。

Pause Anims
動的変更が可能で、アニメーション更新を停止します。停止後は停止時のアニメーションポーズのままになります。

No Skeleton Update
動的変更が可能で、スケルトンの更新を停止します。停止後は停止時のアニメーションポーズのままになります。

マルチスレッド処理
UEはTaskGraphやParallelForといった手段でマルチスレッド処理が実装できます。
bRunOnAnyThread
アクター/コンポーネントクラスの bRunOnAnyThread を true にすることで、アクター/コンポーネント処理がTaskGraphにより別スレッドにて処理を行ってくれます。ただしスレッドセーフにするには自前で行う必要があります。Tick処理の依存関係は AddTickPrerequisiteActor, AddTickPrerequisiteComponent で設定ができるので独自処理を切り出して既存処理の後に行うといったことが可能かと思います。
既存コンポーネントにはゲームスレッド前提で動く想定になっているものが多く、check(IsInGameThread()) というチェックコードが入っているようです。
Task System
UE5から実装された非同期処理システムです。
基本的な使い方は以下のように。
他にもプライオリティ付けや複数タスクを連続して処理するパイプ機能などがあるようです。
#include "Tasks/Task.h"
using namespace UE::Tasks;
FTask MyTask = Launch(UE_SOURCE_LOCATION, [] {
FPlatformProcess::Sleep(1.0f);
UE_LOG(LogTemp, Log, TEXT("Task End.."));
});
// ヘッダファイルメンバ定義
// TTask<int32> MyTaskInt;
void UMyTestComponent::BeginPlay()
{
// タスクを起動
MyTaskInt = Launch(UE_SOURCE_LOCATION, [] {
FPlatformProcess::Sleep(0.5f);
return(1); // 結果を返す
});
}
void UMyTestComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
// タスク終了の確認
if (MyTaskInt.IsValid()) {
if (MyTaskInt.IsCompleted()) {
// 結果受け取り
auto _Result = MyTaskInt.GetResult();
UE_LOG(LogTemp, Log, TEXT("%d"), _Result);
MyTaskInt = {}; // タスク始末
}
else {
// まだタスクが終わっていない
}
}
}
コンパイラ最適化
コンパイラの最適化でCPUコスト削減ができます。ですが最適化により特定の処理に不具合が起こる場合もあり得るので注意が必要です。例えばBPの変数やカスタムイベントなどで日本語が使われていたりする場合などは要注意です。
リンク時最適化(Link Time Optimaization : LTO)
異なるオブジェクトファイル間での最適化。
ビルドコンフィギュレーションの bAllowLTCG を有効にすることで可能になります。ただしビルド時間が大幅に延びることに注意が必要です。
public class MyProjectTarget : TargetRules
{
public MyProjectTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V2;
bAllowLTCG = true; // リンク時最適化
//...省略..
}
}
また bPreferThinLTO を有効にすることでLTOの軽量バージョンが利用できるようです。(リンク時間が大幅にかかるのを軽減する?)
プロファイル誘導最適化(Profile Guided Optimization : PGO)
プロファイルデータに基づいた最適化。プロファイルデータの測定のための専用ビルドとトレーニングランが必要です。
-
bPGOProfileを有効にして計測用パッケージを作成する。 - 作ったパッケージでプロファイルデータを取得するためのトレーニングランを行い、*.pgcファイルを作成する。
- /Platforms/{プラットフォーム名}/Build/PGO/ フォルダに計測用パッケージ作成時に生成された *.pgd と トレーニングランで作成された *.pgc ファイルを置く。
-
bPGOOptimizeを有効にしてパッケージを再作成する。成功していた場合はビルドログに [??? of ??? instructions (100.0%) were optimized using profile data] といった最適化を行った旨のログがあります。
public class MyProjectTarget : TargetRules
{
public MyProjectTarget(TargetInfo Target) : base(Target)
{
//...省略...
if( Target.Configuration == UnrealTargetConfiguration.Shipping
|| Target.Configuration == UnrealTargetConfiguration.Test )
{
bPGOProfile = true; // 計測パッケージ作成時
//bPGOOptimize= true; // PGO適用時
}
//...省略...
}
}
注意
・計測/最適化パッケージのビルドコマンドで指定する場合は -PGOProfile 、-PGOOptimize となります。
・bPGOProfile bPGOOptimize は BuildConfiguration.xml で指定も可能です。
・Test版かShipping版でないと最適化されたパッケージが作成できないようです。
まとめ
CPU処理負荷が高い場所は アクター(Animation, Tick処理、物理、LOD)、UI処理、AI処理辺りが多いと思います。最近のプロジェクトではオブジェクトが多いので調べるのも大変です。
プログラマーが関わらない背景アーティストのオブジェクトなどはカメラ外で普通にTick処理が走っていたりLOD設定されていなかったりするので注意が必要です。












