SkeletalMeshComponent初期化時にBeginPlayのタイミングでPrimaryComponentTick.bRunOnAnyThreadをtrueにするとLODが固定化される

お世話になっております。

キャラクター実装中にLODが変更されない問題を確認し、調査したところ表題の通り、BeginPlayで​PrimaryComponentTick.bRunOnAnyThreadに変更を加えたところ、SkeletalMeshのLODが変わらなくなってしまう問題を確認いたしました。

該当の問題については添付プロジェクトの方でも確認できるため、こちらUEのバグが仕様化と思われるのですが、PrimaryComponentTick.bRunOnAnyThreadをBeginPlayで変更した場合、なぜLODの更新が止まってしまうのか、理由を調査いただくことは可能でしょうか。

また、キャラクタの実装上PrimaryComponentTick.bRunOnAnyThreadをコンストラクタ等でtrueにすることは困難であるため、可能であればBeginPlayのタイミングで有効化したいのですが、そのようなことが可能かご回答いただけると助かります。

以上となります、ご確認よろしくお願いいたします。​

[Attachment Removed]

再現手順
プロジェクトを立ち上げPIEを起動する

Show LODColorationをコンソールコマンドで入力する

画面奥のSkeletalMeshのLODが変化しないことを確認する

[Attachment Removed]

お世話になっております。

お問い合わせありがとうございます。

SkeltalMeshComponentはワーカースレッドでは機能制限がかかるコンポーネントとなっております。

変更タイミングが BeginPlay であるかどうかにかかわらず、 bRunOnAnyThread = true としますと、 TickComponent から先で実行される機能のうち少なくない機能が if (!PrimaryComponentTick.bRunOnAnyThread) {} でガードされ、実行されません。LOD更新が行われないのも、これが理由となっております。

従いまして、本件は(無改造のエンジンとしては)「仕様」ということになります。

一度、ご確認いただけますと幸いです。​

​以上、よろしくお願いいたします。

[Attachment Removed]

ご回答ありがとうございます。​

エンジン仕様ということで承知いたしました。

RunOnAnyThreadをfalseにすることのパフォーマンスへの影響を考慮し、LODの方を統一する方向性で検討を行いたいと思います。

追加で質問なのですが、仮にエンジンのカスタマイズを行いこちらの問題を対処する場合、エンジンソースの変更が必要な個所を共有お願いできますでしょうか。

また、こちらの仕様についてはスレッドセーフでなくなるためこのような仕様となっているかと思われますが、こちらをエンジンの変更を加えることによって、どの部分がスレッドアンセーフ​になりうるか等、変更に対する懸念事項を共有いただけると助かります。

以上、お手数をおかけいたしますが、ご確認よろしくお願いいたします。​

[Attachment Removed]

ご回答ありがとうございます。

回答の通り、こちらの要件は「​Skeltal Mesh Component によるアニメーションは不要、ただし、RunOnAnyThreadの状況下でLOD切り替えだけは実施したい」という内容となります。

パフォーマンスに関してRunOnAnyThreadのフラグの差である程度の負荷の軽減を確認​しているため有効化したいと考えているのですが、有効化したところLODが切り替わらないという問題により、描画バグが発生しているという状態となります。

​対象のメッシュに対してはアニメーションは不要でありますが、SkeletalMeshが持つ骨情報はエフェクトのアタッチ先などで使用したいため、スケルタルメッシュとしては利用したい考えています。

改造に関しては、こちらとしても最終手段という認識であり、上記問題が解決できるのであれば避けたいと考えています。

あくまでLOD周りの実装の確認のため、もし改造するのであればという意図での質問となります。変更が大規模になるか、無改造でも上記問題の解決に至る手段があるのであれば、必ずしもお答えいただく必要はありません。

以上となります、​ご回答よろしくお願いいたします。

[Attachment Removed]

ご回答ありがとうございます。

いただいた回答の方を確認させていただきましたが、確かにUpdateLODStatusでLODの状態を確認し、必要に応じてアニメーションの更新処理を回す方式で問題なさそうに考えますが、PrimaryComponentTick.bRunOnAnyThreadがfalseの場合、そもそもLODは正しく動作しており、こちらが実装を行わなくてもLODは正しく動作していることは確認しています。

一方で、​bRunOnAnyThreadがfalseの場合、ポーズ計算は毎フレーム実行してしまうことを確認しているため、提示いただいたLODの切り替え時のみポーズ計算を実行することで処理負荷を下げることは、提示いただいたコードだけでは実現できないと考えております。

また、アニメーション処理を抑制するフラグとして、​bEnableAnimationを共有いただきましたが、こちらがtrueであればRefreshBoneTransformsの処理はスキップされてしまうことを確認しているため、こちらを運用するのであれば、bEnableAnimationの切り替えが必要と考えています。

こちらについて、理解が及んでいなければ申し訳ありませんが、​追加で必要なコードなどがあれば共有いただけると助かります。

以上、よろしくお願いいたします。​

[Attachment Removed]

ご回答ありがとうございます。

回答いただいたのちに、こちらでも検証を進めてみたところ、LOD切り替えタイミングでEnableAnimationを一時的に有効化して手動でLOD更新命令を呼ぶことで、一旦RunOnAnyThreadを有効化した状態でもLODの切り替えが機能する状態にすることはできました。

今回いただいた方法の方が安全面や処理効率がよさそうではあるため、こちらも検証させていただきます。

この検証において、何かありましたらこちらのスレッドの方で連絡させていただきますが、表題の件は解決いたしましたので、こちらの件はクローズとさせていただきます。

詳細に回答いただき非常に助かりました、ご対応ありがとうございました。

[Attachment Removed]

お世話になっております。

Skeletal Mesh ComponentのEnable Animationプロパティ(bEnableAnimation)をオフにしますと、初期化時に自動的にPrimaryComponentTick.bRunOnAnyThreadがtrueに切り替わる仕組みになっていますので、RunOnAnyThreadをご活用される際はこちらのプロパティ経由でコントロールすることもご検討ください。​

/**
 * Whether the built-in animation of this component should run when the component ticks.
 * It is assumed that if this is false then some external system will be animating this mesh.
 * Note that disabling animation will also cause cloth simulation not to run and the component's tick to run on any thread. 
(日本語訳)このコンポーネントの組み込みアニメーションを、コンポーネントのTick時に実行するかどうか。
 falseに設定した場合、外部システムがこのメッシュのアニメーション制御を行う前提となる。
アニメーションを無効にすると、クロスシミュレーションも実行されなくなり、コンポーネントのティックはあらゆるスレッドで実行される(RunOnAnyThread)点に注意。
 */
UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadOnly, Category=Animation)
uint8 bEnableAnimation : 1;

裏を返しますと、Skeletal Mesh Componentはアニメーション​を切らなければワーカースレッドで動作しないということになります。ご承知と思いますが、これはアニメーションブループリントなども含め、アニメーション周りがゲームスレッドで動作することを前提としている(ゲームスレッドで走るコードが、スキャッターとギャザー役を担っており、可能な範囲内でワーカーに仕事を振っていく)ためです。

そこで一点確認をさせていただきたいのですが、今回のご要件としては、「Skeltal Mesh Component によるアニメーションは不要、ただし、LOD切り替えだけは実施したい」という内容になりますでしょうか? もしくは、RunOnAnyThreadの状況下でも、フルスペックのアニメーション実行が必要でしょうか?

> 追加で質問なのですが、仮にエンジンのカスタマイズを行いこちらの問題を対処する場合、エンジンソースの変更が必要な個所を共有お願いできますでしょうか。

エンジンを改造しますとサポートの対象外となってしまうため、エンジン改造のガイドを積極的に行うことは立場上難しいというのが当方の正直な状況となります。ただし、ご要件を正確に把握できれば、入り口にあたる箇所や押さえるべきポイントなど、ある程度ご案内できると思います。

> また、こちらの仕様についてはスレッドセーフでなくなるためこのような仕様となっているかと思われますが、こちらをエンジンの変更を加えることによって、どの部分がスレッドアンセーフ​になりうるか等、変更に対する懸念事項を共有いただけると助かります。

現時点では大枠でしか回答ができませんが、上述いたしましたように、アニメーション周りはある程度戦略的にゲームスレッドで動かしている部分があり、仮にこれを丸ごとワーカーで動かしたいのだとすれば、アニメーションブループリントシステムやCharacter Movement Componentを巻き込んだ、相当大規模かつハイリスクな改造となると予想されます。当社では現在、ゲームスレッドでの動作を最小限に抑えた「Unreal Animation Framework」という新システムを開発しておりますが、これを達成するためにルートモーション周りの実装を一新する(Character Movement Componentへの依存を切る)必要がありました。このように、ご要件次第では複数のコンポーネントを巻き込んだ非常に規模の大きな改造となる点が懸念事項となります。

当社で新システムを開発しているように、現時点のアニメーション周りの実装はマルチスレッド化に追及の余地があるのは事実です。ただし、​まったくマルチスレッドで動作していないわけでもなく、標準実装でもさまざまな高速化のノウハウが蓄積してはおり、当社のタイトルも含め、各社様のタイトルではこの範囲で高速化を追求いただくことがほとんどです。今回のプロジェクトでは、この方向性で乗り切るのは難しそうな状況(=すでに標準機能ではパフォーマンスに問題があると判明している状況)でしょうか?

以上、一旦ご返信申し上げます。

よろしくお願いいたします。​

[Attachment Removed]

ご要件の明確化をありがとうございました。

アニメーション機能は不要であるとの旨、理解いたしました。

ここまでの状況を整理しますと、RunOnAnyThreadフラグを有効にした際に負荷が下がったのは、Tickレベルのマルチスレッド実行の恩恵もさることながら、アニメーション処理が丸ごとスキップされているのが大きいと考えられます。一方、無改造エンジンでは、ご要望のLODの切り替えを実現するにも、アニメーション処理(ポーズ計算)が必須となります。

そこで、落としどころとして、

・LODの切り替え時のみポーズ計算を実行することで処理負荷を大幅に下げる

・そのかわりゲームスレッドでコンポーネントが動くのは容認する(エンジンを改造しない)

というアプローチはいかがでしょうか。

ご提出いただいたプロジェクトのUTestSkeletalMeshクラスを以下の実装に変更し、使い勝手やパフォーマンスなどをご確認いただければと思います。

void UTestSkeletalMesh::BeginPlay()
{
	Super::BeginPlay();
	// RunOnAnyThreadはfalseのままとする
	// PrimaryComponentTick.bRunOnAnyThread=true;
}
 
// ヘッダに宣言も追加のこと
void UTestSkeletalMesh::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
#if WITH_EDITOR
	if (GetSkeletalMeshAsset() && GetSkeletalMeshAsset()->IsCompiling())
	{
		return;
	}
#endif
 
	// LOD切り替え時のみ、ポーズ刷新を行う
	if (UpdateLODStatus())
	{
		RefreshBoneTransforms();
	}
}

なお、「あくまで静的なメッシュとして運用するが、アタッチや座標取得のために骨構造が必要」というご要件であれば、スタティックメッシュにソケットをつけるというアプローチも考えられます。骨の位置データをソケットにコンバートするツールを内製するなど、制作上の工夫は必要になると思われますが、アニメーション処理を削ったカスタムスケルタルメッシュ以上のパフォーマンス恩恵が得られるかと存じます。

公式ドキュメント『スタティックメッシュでのソケットの使用』

https://dev.epicgames.com/documentation/ja\-jp/unreal\-engine/using\-sockets\-with\-static\-meshes\-in\-unreal\-engine

一度ご確認いただけますと幸いです。

以上、よろしくお願いいたします。

[Attachment Removed]

お世話になっております。

ご確認ありがとうございます。​

> 一方で、​bRunOnAnyThreadがfalseの場合、ポーズ計算は毎フレーム実行してしまうことを確認しているため、提示いただいたLODの切り替え時のみポーズ計算を実行することで処理負荷を下げることは、提示いただいたコードだけでは実現できないと考えております。​

提示させていただいたコードは、TickComponentを完全に乗っ取り、「ポーズ計算を毎フレーム実行する部分」を封殺するアプローチをとっておりますので、そのぶん確実に処理負荷を下げることができるという認識です。

以下に、サンプルプロジェクトにあったアクタを200体配置してStat ANIMコマンドで処理負荷を計測・比較した画像を添付します。

■​bRunOnAnyThread = false (毎回ポーズを再計算し、結果としてLODも正しく動作する)

[Image Removed]​

​■​bRunOnAnyThread = false + 提示コード(LOD切り替え時のみポーズを再計算し、結果としてLODも正しく動作する)

[Image Removed]処理負荷が大きく下がっているのをご確認いただけるかと思います。

参考までに、キャラクターを動かし、LOD切り替えを実際に発生させた状況を録画し添付しましたのでご参照ください。

特に大きなスパイクも発生せず、安定した処理負荷軽減効果が得られているかなと思います。

> また、アニメーション処理を抑制するフラグとして、​bEnableAnimationを共有いただきましたが、こちらがtrueであればRefreshBoneTransformsの処理はスキップされてしまうことを確認しているため、こちらを運用するのであれば、bEnableAnimationの切り替えが必要と考えています。

至らない説明で申し訳ございません。

今回の提示案では、bEnableAnimationはtrueのまま運用する形となります。

なお、大変恐縮ながら、Epic Gamesは12/20~​来年1/4までグローバルで冬期休暇をいただいております。

次の回答は年明けになる可能性もございますので、予めご承知おきください。​

以上、よろしくお願いいたします。​

[Attachment Removed]

ご確認ありがとうございます!

それでは本件は回答済みとしてクローズさせていただきます。

また本件に関しまして新たな疑問点や質問事項など生じました際は、改めて、そしてお気軽にEPSにご投稿ください。

冬期休暇のため返信が遅れましたことお詫び申し上げます。

本年も何卒よろしくお願いいたします。

[Attachment Removed]