2019/02/28
ダブルトランスフォームをマトリクスで解消する
こんにちは、BACKBONEの中川です。
複雑なリグを組んでいると、予期せぬところで、
ダブル(二重)トランスフォーム現象が発生してしまうことがあります。
なぜこの現象が起こるのかを感覚的な理解だけでなく、
一度基本に立ち返り、数式を用いて確認していきます。
はじめに、おそらく最も簡単なダブルトランスフォーム現象を再現します。
1.ダブルトランスフォーム現象の再現
まず、ジョイントとジオメトリを作成します。
それらを親子化し、さらにバインドしてみます。
この状態でジョイントを動かすと、
ジオメトリは、ジョイントの2倍移動してしまいます。
この現象は、
1.親子化により、ジオメトリのトランスフォームノードがジョイントの影響を受ける
2.バインドにより、ジオメトリのシェイプノードがジョイントの影響を受ける
といったように、ジオメトリがジョイントから複数の影響を受けることにより発生しています。
従ってダブルトランスフォーム現象の解消方法としては、通常、
1.トランスフォームノードへの影響を抑制する
2.シェイプノードへの影響を抑制する
のどちらかを選択するのがよいでしょう。
どちらの場合も、マトリクスの計算が非常に重宝します。
あくまで一例ですが、ダブルトランスフォームの解消方法とその考え方をご紹介します。
2.トランスフォームノードへの影響を抑制する場合
今回の場合は単純に親子化を解除すればよいのですが、別の方法として、
トランスフォームノードの入力を操作することで解消してみます。
ジョイントのinverseMatrixから、新たに作成したdecomposeMatrixに接続し、
分解されたtranslate、rotate、scaleをそれぞれジオメトリのトランスフォームノードに接続します。
すると、親子化によるトランスフォームを相殺することができます。
非常に簡単な接続ではありますが、この接続によるマトリクスの関係を確認していきます。
まず、
ジオメトリのワールドマトリクス : gW
ジオメトリのローカルマトリクス : gL
ジョイントのワールドマトリクス : jW
ジョイントのローカルマトリクス : jL
と記号を置きます。
ジオメトリはジョイントの子になっていますので、
ジオメトリのワールドマトリクスは、ジオメトリのローカルマトリクスに
ジョイントのワールドマトリクスを掛け合わせたものになります。
gW = gL * jW
今回の場合、ジョイントはワールド直下に配置されているので、
jW = jL
となります。
また、ジョイントのinverseMatrixをジオメトリのトランスフォームアトリビュート
(translate、rotate、scale)に接続したことで、
gL = jL-1 ※-1はinverseの意
と表すことができます。
以上からジオメトリのワールドマトリクスは、
gW = jL-1 * jL = I (単位マトリクス)
となり、ジョイントの影響が打ち消されていることが数式で確認できました。
3.シェイプノードへの影響を抑制する場合
続いて、シェイプノードへの影響を抑制することで、
ダブルトランスフォームを解消する方法をご紹介します。
これはジョイントとジオメトリをバインドした直後の接続状態です。
ここに手を加え、ジョイントのworldInverseMatrix[0]から
スキンクラスタのbindPreMatrix[0]に接続します。
ここで、bindPreMatrixとはどういったアトリビュートかを簡単にご紹介します。
skinClusterノードには、matrixというアトリビュートと
bindPreMatrixというアトリビュートが存在しますが、
これらを掛け合わせたマトリクスが、
実際にジオメトリのシェイプノードに影響を与えるマトリクスとなります。
ジョイントとジオメトリをバインドした直後は、
このbindPreMatrixアトリビュートには何も接続されていませんが、
実はバインドと同時に、bindPreMatrixの値としてバインドした時点の
ジョイントのworldInverseMatrixが格納(セット)されます。
このbindPreMatrixに、ジョイントのworldInverseMatrixを”接続”(格納ではなく)をすることで、
worldMatrixの値を常に相殺し、シェイプノードに影響を与えない状態となります。
階層構造や接続関係が複雑になっても、
このようにマトリクスの影響をトランスフォームノードと
シェイプノードそれぞれに関して切り分けて考えれば、
的確に対応することができます。
応用
次のようなケースを考えてみます。
今度は、バインドされたジョイントとジオメトリが共通の親ノードを有しています。
この時、親ノードであるロケータを移動させると、ダブルトランスフォームが発生します。
先ほどと同様に、ロケータのワールドマトリクスlWを用いて、
各マトリクスを確認しダブルトランスフォームの原因をつきとめます。
ジオメトリのトランスフォームノードのワールドマトリクスgWは、
gW = gL * lW
また、スキンクラスタを介してジオメトリのシェイプノードに
影響を与えるジョイントのワールドマトリクスjWは、
jW = jL * lW
となっており、ロケータのワールドマトリクスlWがどちらにも影響していることがわかります。
ということは、どちらかからlWの影響を取り除かなければなりません。
今回はシェイプノードへの影響からlWを取り除くことを考えます。
バインド直後のスキンクラスタのアトリビュートは、
bindPreMatrixアトリビュート : jW-1が格納されている
matrixアトリビュート : jWが接続されている
※jW-1はバインド時のジョイントのinverseWorldMatrixの意。
という状態です。
ここで、jW = jL * lWを用いると、上記は
bindPreMatrixアトリビュート : (jL * lW)-1が格納されている
matrixアトリビュート : jL * lWが接続されている
と書き換えられます。
ここからlWの影響を取り除くと、
bindPreMatrixアトリビュート : (jL * lW)-1 → jL-1を格納する
matrixアトリビュート : jL * lW → jLを接続する
このようになります。
つまり、
bindPreMatrixにはジョイントのローカルインバースマトリクスをセットし、
matrixにはジョイントのローカルマトリクスを接続すれば、
ダブルトランスフォームが解消される、ということですね。
この設定により、ロケータの移動によるダブルトランスフォームを解消することができました。
※skinClusterのmatrixにジョイントのローカルマトリクスを
コネクションし直すのは手間がかかるため、
実際は、skinClusterのマトリクスはワールドマトリクスのままで、
bindPreMatrixへの接続を工夫することが多いです。
まとめ
このようにダブルトランスフォームが発生し、すぐに原因がわからないときは、
トランスフォームノードへの影響とシェイプノードへの影響に切り分け、
それぞれのマトリクスを書き出してみると、原因を特定しやすくなります。
参考にしていただければ幸いです。
ではまた。
※免責事項※
本記事内で公開している全ての情報について、その完全性、正確性、適用性、有用性等いかなる保証も行っておりません。
これらの情報のご利用により、何らかの不都合や損害が発生したとしても、当社は何らの責任を負うものではありません。
自己責任でご使用ください。