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