1. サイトトップ
  2. ブログ
  3. Unreal Engine
  4. UE4
  5. 【UE4】URO(UpdateRateOptimizations)でアニメーションの最適化

【UE4】URO(UpdateRateOptimizations)でアニメーションの最適化

こんにちは。
情熱開発部プログラム課の笹目です。

今回はUE4についてのお話をしようと思います。
実は個人的には、前回UE4関連の記事を書いてから3年半以上が経ちます…
当時はSlateによるエディタ拡張について書いていたのですが、いつの間にか、エディタ拡張はSlateではなくBPで書くようになりました。
(こうして人はおじさんになってゆくのですね)

さて今回お話するのは、UE4でアニメーションの最適化を行う際に活躍する、URO(Update Rate Optimizations)という機能についてです。
結構前のバージョンから使用できる機能なので、UE4.20以前のバージョンをご利用の方もぜひ参考にしていただければと思います。

※使用したバージョンはUE4.25.3になります。
※エディタの言語環境は英語になります。

UROとは

UpdateRateOptimizations(以下URO)は、カメラからの距離などに応じてアニメーションのフレーム処理をスキップする最適化機能です。カメラから遠く離れた位置のキャラクターなど、重要度の低いものに対して、アニメーションの更新頻度を抑えることで処理を軽くすることができます。

このフレームスキップ処理は、UROが有効になっているすべてのスケルタルメッシュコンポーネントをなるべくそれぞれが別のフレームで更新するよう最適化されます。例えば、2つのスケルタルメッシュコンポーネントが 処理を1フレームスキップする設定をした場合は、2つのスケルタルメッシュコンポーネントが交互に更新されることになります。

また、後述しますが、URO機能はエディタやコンソールコマンドからでは細かい指定ができないため、細かい指定はC++で行う必要があります。

UROの設定方法

UROの主な設定方法は以下の4つがあります。

  1. コンソールコマンドで指定
  2. UEエディタ上でフラグを有効にする
  3. 距離係数のしきい値で指定(C++)
  4. メッシュLODにあわせて指定 (C++)

※同じスケルタルメッシュコンポーネントに対して、3と4の方法は両方同時には使用できません。4の使用フラグが立っている場合は、4の方法が優先して処理されるようです。

1.コンソールコマンドで指定

URO機能には、以下のコンソールコマンドが用意されています。

a.URO.Enable [0 or 1]
強制的にUROをON/OFF切り替える

a.URO.ForceAnimRate [フレーム数]
何フレームスキップで処理するかを強制的に設定する
上記URO設定方法の3や4の設定を上書きして反映される

a.URO.ForceInterpolation [0 or 1]
フレームスキップ時の補間をするか強制的に設定する

a.URO.DisableInterpolation  [0 or 1]
フレームスキップ時の補間をしないようにするか強制的に設定する
こちらのほうがForceInterpolationより優先される

a.URO.Draw [0 or 1]
各スケルタルメッシュのURO情報を画面に表示するか設定する

これらは、ゲーム内のすべてのスケルタルメッシュコンポーネントに反映されるものなので、個別に設定したい場合は注意が必要です。スケルタルメッシュコンポーネント毎に設定したい場合は、後述する他の方法で設定することをおすすめします。

2.UEエディタ上でフラグを有効にする

UEエディタ上でUROを有効にするためには、有効にしたいスケルタルメッシュコンポーネントのEnable Update Rate Optimizationsにチェックを入れます。

このチェックが入っていると、後述する「3.距離係数のしきい値で指定」の方法を利用したUROが機能します。ひとまずスケルタルメッシュコンポーネント毎にUROを有効にしたいという場合は、この方法が簡単です。

デフォルトの設定では、アニメーションをスキップする最大フレーム数は2です。スケルタルメッシュがカメラから遠い位置にいる場合は、2フレームスキップで処理され、中距離にいる場合は1フレームスキップで処理されます。この設定を変更するには、前述したコンソールコマンドかC++から指定します。

3.距離係数のしきい値で指定

距離係数(MaxDistanceFactor)とは、前フレームにおける、メッシュのスクリーン空間でのサイズを表します。スケルタルメッシュがカメラから遠くに行くほど、0.0に近い値になります。

距離係数(MaxDistanceFactor)のしきい値で指定する場合は、以下のようにC++で指定します。

void AMyCharacter::BeginPlay()
{
	// 有効なスケルタルメッシュがあるか確認
	if (USkeletalMeshComponent* SkeletalMesh = GetMesh())
	{
		// 有効な最適化パラメータがあるか確認
		if (FAnimUpdateRateParameters* AnimUpdateRateParams = SkeletalMesh->AnimUpdateRateParams)
		{
			// スケルタルメッシュのUROを有効にする
			SkeletalMesh->bEnableUpdateRateOptimizations = true;

			// MaxDistanceFactorのしきい値テーブルを作成
			static const float ThresholdTable[] =
			{
				0.5f, 0.5f, 0.3f, 0.1f, 0.1f, 0.1f
			};
			static const int32 TableNum = UE_ARRAY_COUNT(ThresholdTable);

			// しきい値テーブルをスケルタルメッシュの最適化パラメータに設定
			TArray<float>& Thresholds = AnimUpdateRateParams->BaseVisibleDistanceFactorThesholds;
			Thresholds.Empty(TableNum);
			for (int32 Index = 0; Index < TableNum; ++Index)
			{
				Thresholds.Add(ThresholdTable[Index]);
			}
		}
	}
}

BaseVisibleDistanceFactorThesholdsはデフォルトでは { 0.24f, 0.12f } が設定されています。この場合、以下のようにしきい値ごとに1フレームずつスキップ数が増える動作になります。
MaxDistanceFactor > 0.24f の場合、0フレームスキップ(スキップしない)
MaxDistanceFactor > 0.12f の場合、1フレームスキップで処理する
MaxDistanceFactor > 0.0f の場合、2フレームスキップで処理する

例えば、同じしきい値を連続して設定した場合は、スキップするフレーム数を制御できます。
上記実装例のように { 0.5f, 0.5f, 0.3f, 0.1f, 0.1f, 0.1f } の場合…
MaxDistanceFactor > 0.5f の場合、0フレームスキップ(スキップしない)
MaxDistanceFactor > 0.3f の場合、2フレームスキップで処理する
MaxDistanceFactor > 0.1f の場合、3フレームスキップで処理する
MaxDistanceFactor > 0.0f の場合、6フレームスキップで処理する
という形になります。

4.メッシュLODにあわせて指定

メッシュLODにあわせて指定する場合は、以下のようにC++で指定します。

void AMyCharacter::BeginPlay()
{
	// 有効なスケルタルメッシュがあるか確認
	if (USkeletalMeshComponent* SkeletalMesh = GetMesh())
	{
		// 有効な最適化パラメータがあるか確認
		if (FAnimUpdateRateParameters* AnimUpdateRateParams = SkeletalMesh->AnimUpdateRateParams)
		{
			// スケルタルメッシュのUROを有効にする
			SkeletalMesh->bEnableUpdateRateOptimizations = true;

			// LOD毎のURO設定を有効にする
			AnimUpdateRateParams->bShouldUseLodMap = true;

			// LOD毎のスキップフレーム数テーブルを作成
			static const int32 FrameSkipTable[] =
			{
				0, 2, 4
			};
			static const int32 TableNum = UE_ARRAY_COUNT(FrameSkipTable);

			// テーブルをスケルタルメッシュの最適化パラメータに設定
			TMap<int32, int32>& LODToFrameSkipMap = AnimUpdateRateParams->LODToFrameSkipMap;
			LODToFrameSkipMap.Empty(TableNum);
			for (int32 Index = 0; Index < TableNum; ++Index)
			{
				// (LODインデックス, スキップフレーム数)で指定
				LODToFrameSkipMap.Add(Index, FrameSkipTable[Index]);
			}
		}
	}
}

メッシュのLODににあわせて指定する場合は、LODToFrameSkipMapにLODインデックス毎のスキップフレーム数を渡します。

上記実装例のように { 0, 2, 4 } の場合、以下のような動作になります。
LOD0が表示されている場合、0フレームスキップ(スキップしない)
LOD1が表示されている場合、2フレームスキップで処理する
LOD2が表示されている場合、4フレームスキップで処理する

以下のようにbShouldUseMinLodをtrueに設定すると、表示されるLODに関係なく、最小LODの場合のスキップフレーム数で処理を行います。

SkeletalMesh->AnimUpdateRateParams->bShouldUseMinLod = true;

補間機能(Interpolation)

アニメーションのフレームをスキップしてしまうと、スキップするフレーム数によっては、見た目がカクカクしてしまいます。UROではそのカクつきを軽減するためのアニメーションの補間機能があります。

スケルタルメッシュのMaxEvalRateForInterpolatoinというパラメータに「スキップフレーム数がいくつの時まで補間を有効にするか」という値を設定することで、その条件を満たす間はアニメーションを補間します。

MaxEvalRateForInterpolatoinは、デフォルトでは4が設定されています。特に指定をしなければ、スキップフレーム数が3までのスケルタルメッシュは、アニメーションを補間するようになっています。

パラメータを変更する実装例は以下になります 。

void AMyCharacter::BeginPlay()
{
	// 有効なスケルタルメッシュがあるか確認
	if (USkeletalMeshComponent* SkeletalMesh = GetMesh())
	{
		// 有効な最適化パラメータがあるか確認
		if (FAnimUpdateRateParameters* AnimUpdateRateParams = SkeletalMesh->AnimUpdateRateParams)
		{
			// スケルタルメッシュのUROを有効にする
			SkeletalMesh->bEnableUpdateRateOptimizations = true;

			// スキップフレーム数が9までは補間を有効にする
			// 1フレーム処理して、9フレームスキップする場合は、10を指定
			AnimUpdateRateParams->MaxEvalRateForInterpolation = 10;
		}
	}
}

画面外にいるスケルタルメッシュのURO

UROが有効になっているスケルタルメッシュが画面外にいる場合のスキップフレーム数は、内部でBaseNonRenderedUpdateRateというパラメータで値を所持しています。こちらはC++からでしか値を変更できませんが、 デフォルトの値は4です。1フレーム処理して3フレームスキップします。

パラメータを変更する実装例は以下になります 。

void AMyCharacter::BeginPlay()
{
	// 有効なスケルタルメッシュがあるか確認
	if (USkeletalMeshComponent* SkeletalMesh = GetMesh())
	{
		// 有効な最適化パラメータがあるか確認
		if (FAnimUpdateRateParameters* AnimUpdateRateParams = SkeletalMesh->AnimUpdateRateParams)
		{
			// 画面外のスキップフレーム数を指定
			// 1フレーム処理して、5フレームスキップする場合は、6を指定
			AnimUpdateRateParams->BaseNonRenderedUpdateRate = 6;
		}
	}
}

Visibility Based Anim Tick Option

また、UROとは別の機能ですが、スケルタルメッシュコンポーネントのVisibilityBasedAnimTickOptionOnlyTickPoseWhenRenderedに設定した場合は、画面外のスケルタルメッシュはアニメーション処理をしません。画面外でアニメーションをしなくても問題ない場合は、こちらの機能も最適化に活用できます。(UE4.20まではOptimizationではなく、SkeletalMesh項目にMeshComponentUpdateFlagという名前で表示されています)

設定したUROの挙動を確認

スケルタルメッシュコンポーネントのDisplayDebugUpdateRateOptimizationsにチェックを入れると、 UROが意図した通りに効いているかを簡単に確認できます。

チェックを入れてゲームを実行してみると、画面左上には更新レート等の情報が表示され、スケルタルメッシュは色のついたバウンディングボックスが表示されます。

UpdateRate(5)と表示される場合は、1フレーム処理して4フレームスキップしていることになります。

バウンディングボックスの色は以下のルールで設定されます。
赤:0フレームスキップ(スキップしない)
緑:1フレームスキップ
青:2フレームスキップ
黒:2フレームより多くスキップ

UROを検証をする際にはとても助かりますね。

おわりに

今回はUROの機能について、4つの設定方法をご紹介しました。C++で設定する場合も十数行程度のコードを書けば対応できるので便利ですね。

上記の実装例ではキャラクタークラスのBeginPlayで設定していましたが、UROの設定を制御する専用コンポーネントを作成するという方法もあると思います。しきい値テーブルも外部から編集しやすいようにするといいかもしれませんね。

UROを使用している場合、AnimNotifyは基本的には通知されるようですが、ゲームの内容などによっては、UROを使用する際に少し検証してみたほうがいいかもしれません。

アニメーションの最適化を行う際には是非参考にしてみてください。


【免責事項】

本サイトでの情報を利用することによる損害等に対し、
株式会社ロジカルビートは一切の責任を負いません。