お世話になっております。
モーションのマトリクスを更新するUpdateRefToLocalMatricesInner()についてなのですが、この中の処理で無駄にマトリクスを計算している部分がありそうなので連絡です。
この処理の中でRequiredBoneIndicesとして必要なボーンインデックスが渡されており、またその処理の中でHideなどのステータスのチェックが行われますが、最終的に
> const int32 NumReferenceToLocal = ReferenceToLocal.Num();
> for (int32 ThisBoneIndex = 0; ThisBoneIndex < NumReferenceToLocal; ++ThisBoneIndex)
> {
> VectorMatrixMultiply(&ReferenceToLocal[ThisBoneIndex], &(*RefBasesInvMatrix)[ThisBoneIndex], &ReferenceToLocal[ThisBoneIndex]);
> }
この部分がすべてのスケルトンに含まれる骨で処理されているため、無駄なマトリクスの掛け算がおこなわれていそうです。
ReferenceToLocalはUpdateRefToLocalMatrices()の中でRefBasesInvMatrixの数でIdentityに初期化されており(UE4の頃は初期化されておらずnanが含まれていました)、最後の掛け算は描画メッシュに含まれる骨のみ更新すればよいのではと思うのですがいかがでしょうか?
Hideの場合もマトリクスを更新する必要性は薄いと思いますが、親のマトリクスのスケール0が割り当てられているので更新する形で残しています。
またもう1点、ExtraRequiredBoneIndicesの扱いについてなのですが、この中にLOD.ActiveBoneIndicesと同じ骨のインデックスが含まれることがありますが、その場合重複して計算が行われてしまいます。
こちらで変更したものを添付いたしいますので確認をお願いします。
※ MasterPoseの場合はここでスケルトンすべての骨の更新を行う必要がありそうなので全てコピーする処理のままになっています
(下記のリンク先は、本スレッドを英語に翻訳した英文スレッドですが、Epic Games のサポートチームが内部的に使用するものですので、ユーザーの方に利用していただく必要はございません。サポートは、この日本語スレッドに日本語で表示されることになります。)
[Unnecessary calculations of motion [Content removed]
(以下は、サポート担当の Carmichael Euan によるコメントを翻訳したものです。)
お世話になっております。
はい、ご指摘のとおり、無駄を省ける部分が確かにあります。特に、VectorMatrixMultiply の呼び出しについて検討してみると、Hide されたボーンの計算はスキップすることができます。これらのボーンは、親のトランスフォームをスケール 0 で取るだけなので、余計な乗算の必要はありません。この問題につきましては、開発チームが修正できるように、Epic 内部で運用されている JIRA のチケットを作成したいと思います。
また、ReferenceToLocal がメッシュのすべてのボーンを含んでいる件 (現在のメッシュ LOD のためのボーンだけではなく) につきましては、意図されたものだと私は理解しております。このデータはモーション ベクターの計算に使用されるため、複数のフレームにわたって有効である必要があります。もし現在の LOD のボーンだけを保持しているのであれば、LOD が変わったときやボーンの数が変化したときに問題が生じることになります。メッシュのすべてのボーンを保存しておくことによって、そのような問題が回避することが可能です。そうは申しましても、もっと効率的なやり方があるかもしれませんので、この件につきましても JIRA のチケットに記載したいと思います。
しかし、ExtraRequiredBoneIndices につきましては、冗長な VectorMatrixMultiply の呼び出しは行っていないはずです。と申しますのも、配列 RequiredBoneSets (これには LOD.ActiveBoneIndices と ExtraRequiredBoneIndices が含まれています) をループで回している間に、ReferenceToLocal で同じボーン インデックスに書き込まれ、その後にすべてのボーンについて VectorMatrixMultiply が呼び出されているからです。つまり、LOD.ActiveBoneIndices と ExtraRequiredBoneIndices で重複するボーンがあっても、重複して VectorMatrixMultiply が呼び出されることはないはずです。
私に何か見落としがあれば、教えていただけますと幸いです。
お世話になっております。
インラインで返答させていただきます。
> はい、ご指摘のとおり、無駄を省ける部分が確かにあります。特に、VectorMatrixMultiply の呼び出しについて検討してみると、Hide されたボーンの計算はスキップすることができます。これらのボーンは、親のトランスフォームをスケール 0 で取るだけなので、余計な乗算の必要はありません。この問題につきましては、開発チームが修正できるように、Epic 内部で運用されている JIRA のチケットを作成したいと思います。
Hideされたボーンに関してですが、完全に計算されないというのであればそちらの方が軽くなりますので良いと思います。その場合親のスケール0をかける必要もなくZeroMatrixで良いと思います。ただ、過去から現在のコードを見ますとHideされてスケール0にした状態でも位置は更新されていましたので、アプリ側でHideしたボーンから座標を取得するために残されていた可能性があるかなと思いました。そのためこちらに関しては現状と同じようにHideしたボーンの更新もやってしまってよいのではと自分は思っております。
> また、ReferenceToLocal がメッシュのすべてのボーンを含んでいる件 (現在のメッシュ LOD のためのボーンだけではなく) につきましては、意図されたものだと私は理解しております。このデータはモーション ベクターの計算に使用されるため、複数のフレームにわたって有効である必要があります。もし現在の LOD のボーンだけを保持しているのであれば、LOD が変わったときやボーンの数が変化したときに問題が生じることになります。メッシュのすべてのボーンを保存しておくことによって、そのような問題が回避することが可能です。そうは申しましても、もっと効率的なやり方があるかもしれませんので、この件につきましても JIRA のチケットに記載したいと思います。
ReferenceToLocalの数が全てのボーン確保されていること自体は問題ないと思っております。 更新すべきRequiredBoneIndicesに含まれないボーンもVectorMatrixMultiplyで更新がされている部分に関して高速化が可能ではないでしょうか?
> しかし、ExtraRequiredBoneIndices につきましては、冗長な VectorMatrixMultiply の呼び出しは行っていないはずです。と申しますのも、配列 RequiredBoneSets (これには LOD.ActiveBoneIndices と ExtraRequiredBoneIndices が含まれています) をループで回している間に、ReferenceToLocal で同じボーン インデックスに書き込まれ、その後にすべてのボーンについて VectorMatrixMultiply が呼び出されているからです。つまり、LOD.ActiveBoneIndices と ExtraRequiredBoneIndices で重複するボーンがあっても、重複して VectorMatrixMultiply が呼び出されることはないはずです。
LOD.ActiveBoneIndices と ExtraRequiredBoneIndices に重複したボーンがあった場合、ReferenceToLocal[ThisBoneIndex]の同じ計算が2度行われるということを伝えたかったのです。そのためmodコードではReferenceToLocalUpdatedのフラグ配列を使用して2度再度計算が行われないようにスキップしています。
またNeedsUpdateBoneIndciesとReferenceToLocalUpdatedは同じような情報を持ちますが、ComponentTransform.IsValidIndex(ThisBoneIndex)など、他の要素でReferenceToLocalが更新されない場合があるのでNeedsUpdateBoneIndciesを別に用意しています。
最終的に自分の変更点をまとめると
ReferenceToLocalUpdated … 同じ骨のReferenceToLocalを2度計算しないようにする
NeedsUpdateBoneIndcies … 最終的にReferenceToLocalが更新された骨のみVectorMatrixMultiplyする
Hideされた骨 … 位置のみ最終姿勢に更新する仕様は変更なし
という形にしてあります。
よろしくお願いいたします。
ご返答ありがとうございます。
だいたい伝えたかった内容は伝わったかなと思いますので、あとは実際に実装を担っている方にお任せしたいと思います。
LeaderPoseに関しては今回の変更ではほぼ何も触っておりませんので、申し訳ございませんがこちらもお任せいたします。
最後のコメント読みまして、過去に実装したMasterにアタッチされているコンポーネントの描画メッシュ(LODの段階がある場合は現在のLODの描画メッシュ)に必要な骨を収集してRequiredBoneIndicesに入れる最適化が依然必要ということなのかなと思いました。
ただこの点はエンジン側で行われなくてもアプリ側でHideBoneを使用することでも代替可能ですので、エンジンの処理はこのままでも良いかもしれません。
よろしくお願いいたします。
Kitamura 様、
本件問題、記録上これにてクローズとさせていただきますが、さらに追加の質問が生じた場合や、何らかの問題が発生した場合は、こちらのスレッドにそのまま書き込んでいただければ、自動的に再オープンとなり、サポートを受けられるようになります。どうぞよろしくお願いいたします。ご質問いただきまして、ありがとうございました。
(以下は、サポート担当の Carmichael Euan によるコメントを翻訳したものです。)
> Hideされたボーンに関してですが、完全に計算されないというのであればそちらの方が軽くなりますので良いと思います。その場合親のスケール0をかける必要もなくZeroMatrixで良いと思います。
Hide されたボーンが、親のトランスフォームを (スケール ゼロで) 取るように設定する必要があります。そうしなければ、キャラクターのスキニングが最初の Hide されたボーンでまずいことになってしまうからです。そのため、以下のコードは残念ながら依然として必要となります。
ReferenceToLocal[ThisBoneIndex] = ReferenceToLocal[ParentIndex].ApplyScale(0.f);
> アプリ側でHideしたボーンから座標を取得するために残されていた可能性があるかなと思いました。そのためこちらに関しては現状と同じようにHideしたボーンの更新もやってしまってよいのではと自分は思っております。
Hide されたボーンのトランスフォームは更新されないため、ユーザーがメッシュの Hide されたボーンのトランスフォームを取得しようとすると、最後にアニメートされた位置でのものとなります。そのようなユースケースがあるとは思えませんので、Hide されたボーンにおける VectorMatrixMultiply の呼び出しは削除しても問題ないと考えております。
> LOD.ActiveBoneIndices と ExtraRequiredBoneIndices に重複したボーンがあった場合、ReferenceToLocal[ThisBoneIndex]の同じ計算が2度行われるということを伝えたかったのです。
おっしゃるとおりです。その場合、ReferenceToLocal が同じボーンに対して 2 回計算される可能性があります。これは、VectorMatrixMultiply の呼び出しに比べると比較的小さな問題だとは思いますが、開発チームの情報に含めておきます。この関数全体は、再実装してより効率的なものにすることができる可能性がありますが、この種の変更には常にリスクがともないます。
> NeedsUpdateBoneIndcies … 最終的にReferenceToLocalが更新された骨のみVectorMatrixMultiplyする
この NeedsUpdateBoneIndices を生成するための修正には、親またはリーダーコンポーネントによって制御されているボーンが含まれていません。もしも Leader Pose Component を使用している場合は、そのことによってまずいことになってしまいます。その場合は、なお VectorMatrixMultiply をそれらのボーンで呼び出す必要があります。たとえ、それらのボーンが親コンポーネントによって制御されているとしてもです。