- BrightObjectのスキニング(FK編)-
- BrightObjectのスキニング(FK編)-

3Dゲームの世界では結構前から主流となっている(と思う)手法かと思います。
スキニングとは、

「描画したキャラクターにボーンという骨組みを入れ、
そのボーンを動かすことでキャラクターが動く」

というものです。
ここではBrightObjectでのスキニングの概要を書いておきます。

クオータニオンでのボーンの動かし方については以下を参照してください。
クオータニオンを使う
クオータニオンを用いた座標変換の考え方
クオータニオンを用いた座標変換を利用してボーンを描画する
クオータニオンを使ってボーンを回転させる

●ポイント1「ボーンと同じように頂点を回転させる」
ボーンの回転はクオータニオンによって回転をさせますが、頂点も同じです。



ボーンv1を、z軸周りに45度回転するクオータニオンで回転させます。
同じように頂点aにもそのクオータニオンを適用します。



こんな感じになると思います。
なお、ベクトルも頂点も座標値としては同じものなので考えやすい方でOKです。

実際の回転では頂点データに「どのボーンに属する頂点なのか」という値を持たせています。
「あるボーンを動かしたとき、どの頂点がそのボーンの動きについていくか」を決めてやらないと、
関係のない頂点まで動いてしまうからです。
例えば下図のような状態だとします。



頂点a1はボーンv1に属する頂点で、頂点a2はボーンv2に属する頂点です。
この状態ボーンv1を動かしたら、頂点a1が動きます。
しかし頂点a2はボーンv2に属する頂点のため、ボーンv2が動かないと動きません。

頂点a1、a2はそれぞれ配列で「影響を受けるボーン番号」「影響を受けるボーンから受ける重み」を持ちます。
(BrightObjectで「重み付け」をすると分かりやすいと思います)

BrightObjectでは、ボーンを回転させた後に頂点の回転移動を行います。
各ボーンを回転させるクオータニオンは、ボーンの回転をしたときに既に作成されています。
あとは各頂点の移動処理で「この頂点はどのボーンから、どのくらいの影響(重み)をうけるか」がわかれば、
その頂点に対して、「影響を受けるボーンの回転クオータニオン」と「重み」を使って頂点を回転すればOKです。

複数のボーンに属する頂点の場合でも同じことです。
上の図で、頂点a2がボーンv1, v2に属する頂点とした場合、
ボーンv1を動かしても、ボーンv2を動かしても頂点a2は動きます。
その場合、頂点a2は「影響を受けるボーン番号」「影響を受けるボーンから受ける重み」を、
2ボーン分持っているということです。


さてここで「重み」というものが出てきました。
「重み」とは上記の通り、「この頂点はどのボーンから、どのくらいの影響(重み)をうけるか」を示す値です。



この図ではa1はv1から100%の影響を受けます。
つまりそれは、v1を動かしたらa1がv1についていくように移動します。
同じようにa2はv2から100%の影響を受けるため、v2を動かしたらa2がv2についていくように移動します。

しかし実際にはv2を動かしたらv1も移動するのが自然です。
そのため、v2がa1にどの程度の影響を受けるのかを指定する必要があります。



こんな感じに指定したとします。
v2を動かした時、a1は50%の影響を受けます。
これは、v2の回転移動量に対し、その半分の回転移動量がa1に加えられます。
さらにv1からも、半分の移動量がa1に加えられます。
(子の回転クオータニオンが親の回転クオータニオンに合成されるため、v1が初期姿勢だったとしても、
「v2の回転クオータニオン × v1の回転クオータニオン(単位クオータニオン)=v2の回転クオータニオン」
という計算がされ、そのクオータニオンを行列に変換し、その行列に0.5を掛ける)

この結果、2つのジョイントから合計100%の影響を受けるようになります。
合計100%未満になるとボーンに追いついていかない動きとなり、
合計100%を超えるとボーンより先走って頂点が移動するような動きとなります。

実際には次のような計算で、頂点Pの位置を決めています。

P = 初期頂点座標位置 * v2回転行列 * v2から原点周りへ移動する平行移動行列 * 重み
+ 初期頂点座標位置 * (v2回転行列 * v1回転行列) * (v2から原点周りへ移動する平行移動行列 * v1から原点周りへ移動する平行移動行列) * 重み
+…




ある頂点の移動を計算するときに、その頂点に対して、
「影響を受けるボーン」と「重み」を使って上のように計算ができます。
すべてのボーンからの影響を上のように計算するのもいいですが、重みが0となっている場合は計算する意味が無いので、
その分は省いておくと計算量は減り、効率がよくなります。

BrightObjectでは平行移動は行列を使わず、単純にボーン(ジョイント座標)位置を、計算を始める前に頂点座標から減算しています。
回転行列は、各ボーンの回転クオータニオンを行列へ変換してから上記のように乗算をしています。


詳しい計算等は、その27 アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)が結構分かりやすいです。
トップページ:ゲームつくろー!


●ポイント2「クオータニオンでの回転は常に原点周りで」
よく「ローカル座標系」という言葉を書籍などで見かけます。
クオータニオンをローカル座標系については、
クオータニオンを用いた座標変換の考え方

を参考に。

ボーンを回転させるとき、回転の中心はボーンの末端だと思います。
例えば先ほどの図ですが、



ボーンv1を回転させる場合、回転の中心はボーンv2の先端になるのが自然かと思います。
しかし「頂点を、ある座標値を中心に○○度回転させる」というのは計算上、多分めんどくさいです。
なのでまず、「頂点a1をワールド座標系の原点周りに移動させた時の座標値」を計算します。
具体的には、親ボーンの先端(間接)の座標値をa1から次々に減算させていきます。
BrightObjectでいう「基準ジョイント」から「親ボーンの先端」に達するまで行います。


             

             


そうすると原点周り(ワールド座標系上)での頂点a1が求めることができます。
ここで、頂点a1に対して「ボーンv1を回転させる回転クオータニオン」を使って回転させ、
同じ手順で今度は各ボーンの先端(間接)の座標値を加算していきます。
基準ジョイントの座標値まで足すと、結果としてボーンv2の先端を中心に回転することとなります。

BrightObjectの頂点は常にワールド座標系上にあるものとして計算しますので、
原点周りに頂点を移動(減算)→回転クオータニオンでの回転→減算した値を加算
の繰り返しで頂点の回転を行います。




inserted by FC2 system