ホーム < ゲームつくろー! < DirectX技術編 < クォータニオンによるボーンの姿勢制御

その21 クォータニオンによるボーンの姿勢制御


 ボーンは回転します。ぐるぐる回ります。そして、ボーンの先端には子ボーンがしっかりくっついています。また、子ボーンはそれ自身の座標系を持っていて、親ボーンと独立した回転を成します。2Dの場合、親ボーンの座標軸に対する子ボーンの座標のひねりは1つの角度で表現できますが、3Dとなると大変です。3つの軸角度で制御すると子ボーンのジンバルロックも招いてしまいます。3Dの場合、ボーンの回転はクォータニオンで制御するのが楽に思えます。この章では、ボーンの姿勢をクォータニオンで制御する方法について考えて見ます。



@ ボーンの先にくっついている子ボーンの座標系のねじれ

 ローカルオブジェクトに埋め込まれているボーンは、デフォルトで軸上にあります。ZXY軸回りを仮定するDirect3Dの場合だと、それはZ軸上になるでしょう。ボーン自体は色々な方向を向いています。それはボーンが回転しているのではなくて、ボーンの座標がねじれているだけです。

 


 このねじれをクォータニオンで表すには、親ボーンの2つ座標軸の方向を子ボーンの座標軸のそれに合わせるような計算をします。ここではZ軸とY軸を使うことにしましょう。まず親のZ軸と子のZ軸の法線を求め、さらにその角度差を求めます。この2つの情報があるとZ軸を合わせるクォータニオンを作成できます。作成したクォータニオンをY軸にも適用すると回転後Y軸の方向が出てきます。次に回転後Y軸を子のY軸に合わせるクォータニオンを作成します。法線は子のZ軸になるので、Y軸同士の角度差を求めるだけでそれを作成できます。この2段階のクォータニオンを作成して合成すると、親の座標空間を子のそれに変換するクォータニオンの出来上がりです。この計算は「その16 オブジェクトを任意の平面に立たせる姿勢制御」で詳しく述べていますのでご参照ください。ここでは、その求めた合成クォータニオンをQと表記する事にします。

 親ボーンが自分の軸を中心として回転すると、子の座標空間はデフォルト位置からさらにねじれますが、それは単に親ボーンと一緒に回転するだけです。行列で言うなら回転行列を掛け算される事と同じになります。親ボーンの回転をクォータニオンQpで表します。すると子のねじれはデフォルトのねじれを表すクォータニオンQQpの合成となります。具体的にはQ×Qpという掛け算をします。

 デフォルトの座標で2つのクォータニオン、親の回転でさらに1つ、合計3つのクォータニオンがボーンの姿勢制御にはかかわってきます。実用する上では、Qは固定なので、親の回転クォータニオンQpを逐次求めていってQに掛け算するだけの作業になります。これは、それほど大きな負荷ではありません。



A 姿勢制御用のクォータニオン演算関数

 ボーンの姿勢制御は、実はこれでおしまいなんです。クォータニオンを3つ計算するだけ。結構、あっけないですね。ただ、クォータニオンを計算するときに、毎度毎度法線を求めて角度を求めて・・・とするのはちょっとうざったい感じがします。これを行ってくれる関数はDirect3DXにはありませんので、自作しましょう。

 作成する関数は、2つのベクトルを与えると、一方を他方に変換するクォータニオンを生成してくれるD3DXQuaternion2Vecsです。

2ベクトルによるクォータニオン生成
D3DXQUATERNION *D3DXQuaternion2Vecs(
   D3DXQUATERNION *pOut,
   D3DXVECTOR3 *pV1,
   D3DXVECTOR3 *pV2
)
{
   // 標準化
   D3DXVECTOR3 N1, N2;
   D3DXVec3Normalize(&N1, pV1);
   D3DXVec3Normalize(&N2, pV2);

   // 回転軸と回転角度の計算
   D3DXVECTOR3 Norm;
   FLOAT angle;
   D3DXVec3Cross(&Norm, &N1, &N2);
   angle = (FLOAT)acos(D3DXVec3Dot(&N1, &N2));

   // クォータニオン生成
   D3DXQuaternionRotationAxis(pOut, &Norm, angle);

   return pOut;
}

 この関数を用いれば、子の座標のひねり角度は、

   D3DXQuaternionMultiply(
      &Out,
     D3DXQuaternion2Vecs(&ParentRotate, &D3DXVECTOR3(0,0,1), &ParentVec),
      &Q);

という計算で常に求められる事になります。扱っているのが実質親ボーンの位置ベクトル(ParentVec)だけであるのに注目です。



B 作成したクォータニオンの使い道

 親ボーンの座標空間に対する子ボーン座標空間のねじれを表すクォータニオンは何に使うのか?これは言うまでもなく「座標変換」です。親ボーン座標内のある任意の点は(位置ベクトル)、求めたクォータニオンで逆回転させてオフセット移動することにより子ボーンの座標空間に即座に変換されます(詳しくはその19を参照)。「クォータニオンによる逆回転ってどうやるの?」と困惑するかもしれませんが、非常に簡単なんです。クォータニオンをQとし、その共役クォータニオンをR、任意の点をP(クォータニオン表記)とするなら、

 順回転: R×P×Q
 逆回転: Q×P×R

です。単に回転クォータニオンの掛ける順番を逆にしているだけですね。回転行列での逆回転よりは簡単です。子ボーンの座標空間を親に変換するのも全く問題なく出来ます。ただ単に作業を逆にするだけです。すなわち、子ボーン座標内のある点は、クォータニオンによる順回転を与えた後オフセットする事で親座標に変換されます。この変換プロセスはインバースキネマティクスをやる上での必須作業になりますので、覚えておいて損はありません。

 回転の作業をクォータニオンにしてしまうことで、わずらわしい子ボーンのジンバルロック問題は全く発生しなくなります。キーフレームによる回転補間も、クォータニオンの得意分野ですから、ボーンとの相性は非常に良いわけで、この姿勢制御を使わない理由は無いと思います。また、私見ですが回転行列を用いるよりも直接的なイメージをしやすい気がします。それは、回転行列が固定軸回転を基軸としているのに対して、クォータニオンが任意軸回転をベースとしているからなんでしょうね。

 これまで、座標変換の仕組み(その19)、ボーンの回転制限(その20)、そして親子間のひねり関係を説明してきました。これだけツールがそろうと、インバースキネマティクスも非常にやりやすくなります。