LoginSignup
4
5

UE5 CPU負荷軽減についてのメモ

Last updated at Posted at 2023-05-17

概要

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での計算となるため特別な事情がない限りはやっといた方がいいかと思われます。

niagara01.png

パーティクルコリジョン

パーティクルのコリジョンが有効な場合、これもGPU計算にできます。GPUCompute Simにすると自動で設定されるようです。

niagara02.png

CPUSim と GPUCompute Sim の負荷検証

テスト作成したパーティクルにて stat unitにて確認をすると以下の様に GameDraw にて値の違いが確認できました。

▼CPUSim
CPU.png

▼GPUCompute Sim
GPU.png

パーティクル数700ほどを発生させていますが、CPU負荷が下がっています(GPU負荷は上がっています)。

GPUCompute SimではCalculate Bounds ModeDynamicを選択すると警告がでてしまうことに注意が必要です。

Scalability

エミッタのステートにScalabilityの設定があるのでこれを適宜調整することで負荷軽減になります。
ScalabilityMode を system から self に切り替えると各種設定が可能になります。

scalability.png

詳細設定は以下のようになります。

▼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 FINISHSLEEP AND CLEAR PARTICLES を設定することになると思います。

Effect Type

エフェクトタイプアセットを作成してエミッタシステムに割り当てることで、同一タイプによるバジェット管理ができるようです。

これにより、割り当てられた使用量を超える同一エフェクトタイプではカメラ距離や優先に基づいてカリング処理が行われるようです。

PoolingMethod

エフェクトをスポーンする際に Pooling MethodAuto Releaseにするとエフェクトをプールして再度同じエフェクトをスポーンする際に使いまわしをしてくれるようです。これによりスポーン時の処理負荷が軽減されます。使用頻度が高い単発エフェクト(攻撃ヒットエフェクトや移動時の砂煙エフェクトなど)に使うと効果があると思われます。

Manual Releaseはエフェクト終了時に明示的に UParticleSystemComponent::ReleaseToPool() を呼ぶ必要があるようです。単発ではないエフェクトはこちらを使う必要があるようです。

Chaos Destruction

UE5のデフォルト物理エンジンChaosの破砕シミュレーションです。

ジオメトリコレクションの表示

ジオメトリコレクションは破砕された状態での表示扱いとなり、LODなどはないため破砕しない場合や遠方などでの適用は何らかの対策すべきです。(ただDrawCall数は変らないぽい?)
考えられる対策としては破砕前と同等のスタティックメッシュを用意して差し替えなどがあると思います(スムーズに差し替えるにはそれなりに難しいとは思いますが)。

Choas Cache Manager

キャッシュを利用することで負荷が軽減できます。ただし予め記録したキャッシュデータのとおりに破壊されることになります(インタラクティブではない)。

キャッシュマネージャは[アクタ]->[Chaos]->[キャッシュマネージャを作成]から作成します。

キャッシュマネージャの設定は以下のようになります。

  1. Cache Collection にChaosキャッシュコレクションを設定。
  2. Cache Name に任意のキャッシュ名を設定。
  3. Referenced Actor に破砕対象のジオメトリコレクション。
  4. Component Name にジオメトリコレクションコンポーネント名を設定。

Chaosキャッシュコレクションはコンテンツブラウザから右クリック->[フィジックス]->[Chaosキャッシュコレクション]でアセットを作成します。

ChaosCacheManager.png

キャッシュマネージャの Cache Mode記録 にし、PIEを起動することで記録を開始します。

記録時に
LogChaosCache: Warning: ChaosCacheManager_UAID_????? has invalid observed component reference.
というログが出た場合は Component Name 設定のスペルを確認してください。
コンポーネント固有名ではなくコンポーネント名になります。
example01.png
破砕対象が上記の場合、GeometryCollectionComponent0 ではなく GeometryCollectionComponent となります。

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

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

負荷検証

キャッシュあり/なしでGame負荷が落ちているのが確認できます。

▼キャッシュなし時(記録時)
example01.png

▼キャッシュ再生時
example02.png

CharacterMovementComponent

エンジン付随のアクター移動コンポーネント。単純な移動ならこれを使わずに作成したほうが軽くなります。

MovementMode

NavMesh使用している条件下なら移動中のコリジョンチェックをなくすことができます。

注意
NavMeshが条件になるため、NavMeshがない場所に移動するとWalkingモード(コリジョンチェックあり)に戻ります。また段差等の高さの差がある個所の移動が怪しくなります。

CharacterMovementComponent の詳細設定の DefaultLandMovementModeNavmesh Walkingに、Sweep While Nav Walking のチェックも外します。

対象のアクターがレベルに配置済の場合、上記デフォルト設定で DefaultLandMovementMode を変えてもNavMeshが見つけられず、すぐ Walkingモードに戻ってしまう場合があるので、
スポーン時にNavMesh Walking に設定してしまう方法がよいかもしれません。以下コード例。

▼BPでのMovementMode設定
SetMovementModeBP.png

▼C++でのMovementMode設定

ATestEnemyCharacter.cpp
#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チェックのみになっているのが確認できます。

▼通常時(Walking)の移動中
CollisionTest1.png

▼NavWalkingモードの移動中
CollisionTest2.png

このOverlapもコリジョンの Generate Overlap Eventsのチェックを切ると消えます。(ただしOverlapイベント受け取りができません)

通常操作の(PlayerControllerの)ポーンでも NavWalkingモードでの移動は可能ですが、NavMeshがない場所(途切れているような場所)へ移動すると通常モードに戻るためあまり意味がないかと思います。
なので使用する対象としては、動く範囲がNavMesh内に決まっているアクターにとどめておく方がいいと思われます。

MaxSimulationIterations

UCharacterMovementComponent::PhysWalking() にて行われている処理で、Walkingモード時の移動位置シミュレート最大回数の値です、コリジョンチェック等を複数回繰り返しているようです。複雑なコリジョンや高速移動するようなオブジェクト以外は減らすことで負荷軽減になるようです。
MaxSimulationIter.png

CharPhysWalkingの検証

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

Update Only if Rendered

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

AI Perception

AIに対する感覚システムで、視覚、聴覚、触覚などがあります。

感覚システムのOn/Off制御

感覚システムを稼働する側(敵キャラの視界など)を適宜On/Offすることで処理負荷軽減が期待できます。
登録(知覚システムの開始)はUAIPerceptionSystem::UpdateListener で、解除(知覚システムの停止)は UAIPerceptionSystem::UnregisterSource で行います。BPには対応していないようです。

.cpp
#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クラスの bAutoRegisterAllPawnsAsSourcesfalse にすると対象の自動登録をなくすことができ、DefaultGame.iniに以下のような設定をすることで実現ができます。
以下、AISense_Sightの場合のコード例。

DefaultGame.ini
[/Script/AIModule.AISense_Sight]
bAutoRegisterAllPawnsAsSources=false

代わりに手動で対象ポーンを登録する必要があります。UAIPerceptionSystem::RegisterSource で行います。
以下コード例。

.cpp
#include "EngineUtils.h"

UWorld* World = GetWorld();
for (TActorIterator<APawn> PawnIt(World); PawnIt; ++PawnIt)
{
	if( PawnIt->ActorHasTag(FName("MyChara"))){	// タグで判別する
		// 対象登録
		RegisterSource(SenseID, **PawnIt);
	}
}

AIPerceptionSystemの差し替え

UAIPerceptionSystemクラスを差し替えて知覚システムの対象ポーンを制御することなどができます。
プロジェクト設定の[エンジン]->[AIシステム]->[認識システムクラス]で差し替えられます。
MyAiPerceptionSystem.png

UAIPerceptionSystemクラスの差し替えコード例。

MyAIPerceptionSystem.h
#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;

};
MyAIPerceptionSystem.cpp
#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)
Trangition.png
Inerti.png

トランジション以外にも各ブレンドノードのブレンドロジックにて設定が可能なようです。

注意
慣性ブレンドは遷移元のアニメーション評価も止まるため、ブレンドタイミング後でアニメーションシーケンスに設定した通知もされないことに注意が必要です。

Animation停止

不要なタイミングでアニメーションを停止することで負荷軽減になります。SkeletalMeshComponentに各種アニメーションを停止するメソッド/フラグがあります。

SetRender Static

動的変更が可能で、アニメーション更新を停止します。停止後はデフォルトポーズになります。

Pause Anims

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

No Skeleton Update

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

マルチスレッド処理

UEはTaskGraphParallelForといった手段でマルチスレッド処理が実装できます。

bRunOnAnyThread

アクター/コンポーネントクラスの bRunOnAnyThreadtrue にすることで、アクター/コンポーネント処理が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 を有効にすることで可能になります。ただしビルド時間が大幅に延びることに注意が必要です。

MyProject.Target.cs
public class MyProjectTarget : TargetRules
{
	public MyProjectTarget(TargetInfo Target) : base(Target)
	{
		Type = TargetType.Game;
		DefaultBuildSettings = BuildSettingsVersion.V2;
		bAllowLTCG = true;		// リンク時最適化
		
		//...省略..
	}
}

また bPreferThinLTO を有効にすることでLTOの軽量バージョンが利用できるようです。(リンク時間が大幅にかかるのを軽減する?)

プロファイル誘導最適化(Profile Guided Optimization : PGO)

プロファイルデータに基づいた最適化。プロファイルデータの測定のための専用ビルドとトレーニングランが必要です。

  1. bPGOProfile を有効にして計測用パッケージを作成する。
  2. 作ったパッケージでプロファイルデータを取得するためのトレーニングランを行い、*.pgcファイルを作成する。
  3. /Platforms/{プラットフォーム名}/Build/PGO/ フォルダに計測用パッケージ作成時に生成された *.pgd と トレーニングランで作成された *.pgc ファイルを置く。
  4. bPGOOptimize を有効にしてパッケージを再作成する。成功していた場合はビルドログに [??? of ??? instructions (100.0%) were optimized using profile data] といった最適化を行った旨のログがあります。
MyProject.Target.cs
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 bPGOOptimizeBuildConfiguration.xml で指定も可能です。
・Test版かShipping版でないと最適化されたパッケージが作成できないようです。

まとめ

CPU処理負荷が高い場所は アクター(Animation, Tick処理、物理、LOD)、UI処理、AI処理辺りが多いと思います。最近のプロジェクトではオブジェクトが多いので調べるのも大変です。
プログラマーが関わらない背景アーティストのオブジェクトなどはカメラ外で普通にTick処理が走っていたりLOD設定されていなかったりするので注意が必要です。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5