概要
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 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設定されていなかったりするので注意が必要です。