ホーム < ゲームつくろー! < DirectX技術編 < アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)
その27 アニメーションの根っこ:スキンメッシュアニメーション(ボーン操作)
RPGで主人公が走り、飛び、勝利のポーズを決める。かっこいいですよねぇ〜。そういう軟質系の動きは「スキンメッシュアニメーション」と呼ばれています。これをDirect3Dで実現するために、その23あたりから地道な下積みをしてきています。この章では、いよいよアニメーションの根本をなす「ボーン操作」について説明します。この章、はっきり言って試行錯誤と調査と努力の結晶です(T_T)
@ ボーン登場!
○×にもちらちらと出てくる「ボーン(骨)」。スキンメッシュアニメーションの中核をなす重要な概念でして、ようやくここで堂々の登場となります。ボーンとはスキンメッシュに埋め込まれた骨のことで、この骨をぐいぐいと動かすことにより、その回りにまとわれているポリゴンをスムーズに動かします。生き物を取り扱うかのような、非常にわかりやすい概念です。
早速ボーンがどのようにでは、次の図をご覧下さい。
Direct3Dの中で一番えらい座標はワールド座標です。次に偉いのがポリゴンオブジェクトが定義されているローカル座標。このローカル座標には、ポリゴンオブジェクトを構成する頂点(赤い点)が沢山定義されています。ここまではいつものお話です。図にはさらに2つのボーンがあります。各ボーンはそれぞれ「ボーン空間」というこれまた独立した座標系を持っています。図からわかりますように「ボーンは必ずボーン空間のZ軸に沿っています」。これはボーンをイメージする一つの大切なポイントと言えます。ボーンには明確な親子関係があって、図だとボーンAはボーンBの親に当たります。一番最初のルートボーンであるボーンAもローカル座標を親とします。つまり、すべてのボーンは自分の座標空間と親を持っていることになります。
ボーン空間とボーン、そしてボーンの親、これらが後で絶妙に組み合わされて、ボーンによるスキンメッシュアニメーションが実現されることになります。
A ボーン自体を動かすボーン行列の妙
ボーンという物自体はポリゴンでも何でもありません。単なる「概念」です。ですが、概念だとイメージができませんので、図示しながら考えていくことにしましょう。ボーンには明確な親子関係があります。よって、親のボーンが動くと下位の子ボーンは全て動くことになります。私たちが指先を動かす時に、同時に手首や肘や肩が同時に動きます。もしかすると、体の向きがすでに違うかもしれません。ですから、「指先のローカル位置は指先だけでは決められない」のです。手首・肘、肩と親ボーン全ての動きを考慮する必要があります。問題はそれをどう考慮するのかですが、大丈夫です。まずは下図をご覧下さい。
ローカル座標にボーンが3つ定義されています。今、大親分ボーンであるボーンAを動かすとしましょう。
ボーンAを動かすと、その先にある子ボーンも一緒に動きます。この考えは自然ですね。冒頭で説明した事ですが、ボーンA空間の親はローカル座標(空間)です。実は、ボーンというは自分の空間ではなくて、その親の空間で位置や回転が決まります。種を明かしますと、「ボーン空間」というのは厳密にはボーンの姿勢を定義するもので、ボーンが「存在する」空間ではないんです。単にその座標系を「空間」と呼んでいるに過ぎません。これを間違うと大混乱してしまいますから注意してください。以後、ボーン空間というとその姿勢と座標系を表すとします。
親であるローカル座標内であれば、ボーンA空間はいかようにも配置することができます。上に示した「MA」というのは、その配置するための行列です。オフセットと回転を組み合わせた「合成行列」です。この行列の事を「ボーン行列」と言います。定義をちゃんとしておきましょう。ボーン行列とは、親空間内に子ボーン(空間)を配置する行列です。
上の図で、ボーンAが配置されると、それに付随するように子ボーンも配置されていることが分かります。それはまるで「ボーンA、BそしてCが凝り固まったオブジェクト」のようです。このことから、親であるボーンAの動きを表す行列MAは、ボーンBにもCにも適用されなければ、正しい親子関係でボーンを動かせないことが分かります。これ、とっても大切です。
さて、続いてボーンBを動かしてみましょう。まず、ボーンBの親はボーンAです。先ほどのローカル座標とボーンAの関係と同じように、ボーンBは親であるボーンAの座標系(空間)で動きます。これは約束ですから忘れていはいけません。その様子を図にするとこうなります。
ボーンAの空間なので、ボーンAはZ軸方向を向いています。ボーンBの動きを表すボーン行列MBは、ボーンA空間に対して定義されます。ところで、このボーンA空間自体は、先ほどMAという行列によってローカル座標内に置かれました。ということは、MBで動いたボーンBをさらにMAで動かしてやると、ボーンBはローカル座標に配置されることになります。すなわち、ボーンBをローカル座標に置く行列は、
という掛け算で求められる事になります。この掛ける順番に注意してください。先にボーンBを動かしてからボーンAを動かすという意味合いになっています。
さらにボーンCを動かしてみましょう。上との対称性から、ボーンB空間においてボーンCを動かすMCが定義されることになります。
ボーンCをローカル座標に持っていくには、MCで動かしたあとにボーン空間Bごとローカルに置けば良いわけです。実は、その行列はもう上でLMtBとして計算してあります。つまり、ボーンCをローカル座標に置く行列は、
と計算されることになります。掛ける順番に注意です。ボーンCを動かしてから、それをローカルに持っていくんです。
ここまで説明すればもう規則性は明らかですよね。一般に、ある子ボーンをローカル座標に持っていく行列は、
となります。全ての子ボーンに対して上の式を適用していくと、特定のポーズを形成するボーンを作成することができます。そういう骨組みが出来てしまえば、後はそれに付随するように頂点を動かすだけです。
B ボーン空間の視点で頂点を見せてくれるボーンオフセット行列
Aで説明したように、ボーンは自由に動かすことができます。しかしながら、Xファイルに含まれるボーンは、すでに何らかのポーズを取っている事がほとんどです。この初期ポーズの事を初期姿勢と呼びます。実は、この初期姿勢が頂点をボーンに合わせて動かすために絶対に必要な要素なのです。
ボーンを何も動かさない初期姿勢状態があると、ボーンとその回りにある頂点の位置関係が一意に決まります。こうなると、ローカル座標にある頂点をあるボーン空間に一気に変換することができるのです。別の言い方をするならば、ローカル座標にある1つの頂点は、ボーン座標空間の立場から視点を変えてみることができるというわけです。
ボーン座標空間で頂点を見ることが出来ると、ボーンの動きを表すボーン行列で頂点の動きも一緒に表すことができるようになります。これは非常に便利なのです。この「ローカル座標にある頂点をボーン空間の座標に変換する行列」の事を「ボーンオフセット行列」と言います。そして、とても嬉しい事に、全てのボーンに対するボーンオフセット行列は、Xファイルにすでに提供されているのです。ですから、私たちはそれを計算することなく使用することが出来ます。
ボーンオフセット行列を使ってローカル座標にある頂点をボーンの空間に変換する式を先に示しておきます。
Pbeforeがローカル座標にある点、BOfsMtがボーンオフセット行列です。左辺のPafterがボーン空間に変換された(ボーン空間の視点で見た)点の位置になります。この式自体は行列変換の基礎のようなものでして、明確で簡単ですね。
C ボーンオフセット行列とボーン行列の連係が頂点を動かす!
ボーンオフセット行列とボーン行列。この2つは絶妙に働いてくれます。その動きを見てみましょう。下の図をご覧下さい。
3つの初期姿勢を取っているボーンに対して頂点が1つくっついています。今、この頂点は「ボーンCに同調する」とします。ボーンCとの位置関係を保ち、ボーンCがどう動こうともぴたりと寄り添うわけです。では、この初期姿勢から実際にボーンCを動かしてみましょう。ボーンCを動かすには、ボーンCのボーン行列MCを用いるのでした。
ボーンCの動きなので、ボーンB空間に注目しています。初期姿勢に対してMCという変換(初期姿勢に対する相対変換)により、ボーンの位置とその空間も一緒に変換されました。そして、頂点も全く同じように追従して変換されています。ところで、初期姿勢のボーンC空間に頂点が何気なくありますが、これ、もともとはローカル座標にある頂点です。ボーンCの動きにあわせるには、これをボーンC空間に変換する必要があります。それがボーンオフセット行列の役目でした。このことから、上右図にある移動後の頂点の座標(ボーンC空間)は、
という頂点座標と行列の掛け算になります。ローカル座標にある点を、ボーンCに定義されているボーンオフセット行列でボーンC空間に持ってきて、変換行列で変換する。そんな動きが上式には良く現れています。
さて、変換後の点はまだボーンC空間にあります。よって、これをローカル座標に戻す必要があります。それができる行列は…Aで嫌というほど説明してきたボーン行列の合成行列です。それを使って変換後の頂点をさらにローカル座標に戻します。
これで、ボーンCを動かして頂点を動かすという、一連の式が出来上がりました!!この式こそ「スキンメッシュアニメーション」の基本式中の基本式なんです。
D ボーンは頂点を知る
上で1つの頂点をボーンによって動かす仕組みを説明してきました。あるボーンに影響される頂点を同様に変換すれば、頂点の移動はできます。ボーンは、オブジェクトの中に何千とある頂点のどれが自分の影響を受けるかを良く知っています。これはXファイルの中を見て頂くと明らかです。
SkinWeights {
"Frame2_Bone01"; // ボーンの名前
6; // 影響する頂点の数
0, // 頂点番号(0,1,2,3,4,5番が影響するのがわかる)
1,
2,
3,
4,
5;
0.999997, // 影響力
0.999997,
1.000000,
1.000000,
1.000000,
1.000000;
1.000000,0.000002,0.000000,0.000000, // ボーンオフセット行列
0.000000,-0.000008,1.000000,0.000000,
0.000002,-1.000000,-0.000008,0.000000,
-2.000004,1.999996,0.000015,1.000000;;
}
上はXファイル内のSkinWeightsテンプレートを見たものです。これは1つ1つのボーンに対して定義されます。Frame2_Bone01という名前が付いたボーンは、6つの頂点に影響を与えます。その頂点の番号は0、1、2、3、4、5です。そして、その影響力は全てほぼ1のようです。最後はそのボーンのオフセット行列になっています。このように、ボーンは頂点を知っています。
ボーンは、自分が動く時にこの頂点のみを動かそうとします。この時「影響力」という数値がまた絶妙な効果をもたらします。
E 頂点影響力が柔らかさの決定打
Cでの頂点は、ボーンCに追従する動きを見せました。しかし、これだと困った事が起きてしまいます。またまた、下の図をご覧下さい。
ボーンがポリゴンの中に埋め込まれている様子です。緑色の頂点はボーンBに100%影響され、黄色い頂点はボーンCに100%影響されるとしましょう。この状態で、ボーンCを曲げてみます。
これは形状が崩れてしまいました。内側の頂点がぐっちゃになっていますし、外側の線分も不自然に伸びてしまっています。このように頂点が100%ボーンに影響されると、頂点が固まって動いてしまうので、特に接合部分でおかしな状態になってしまうんです。そこで、各頂点をボーンから100%影響されるのではなくて、適度な割合で影響されるようにします。上の例で、ボーンの境目にある頂点(線分の端が緑と黄色となる部分の頂点)について、ボーンB及びボーンCの動きの50%だけ影響されるように変更して同様に動かしますと、次のようになります。
まず変になっていた内側の部分が少し解消されました。また外側の引きつりも相当に解消しています。まだ多少いびつ感が残るのは、影響力の配分比率が最適でないためです。上の図からすると、内側のボーンBに刺さっている頂点は明らかにボーンCの影響を受けすぎです。ですから、そうですねぇ、ボーンCの影響力を10%、ボーンBを90%として、ボーンCの影響を殆ど受けないようにしましょう。内側のボーンC側の頂点は逆にB:C=40%:60%くらいに。外側の頂点も同程度の配分比率に修正します。すると、こうなりました。
内側の部分が綺麗に修正されています。全ての頂点についてこの影響力を最適化すれば、違和感の無い柔らかい動きが提供されます。
1つの頂点に複数のボーンが影響する時、その影響力の合計は1にならなければいけません。今、影響力をWで表しますと次のような関係式です。
一般には、1つの頂点に影響するボーンの数はあまり多くはなく、せいぜい4本くらいです。ただ、ボーンが細かいにもかかわらず肉付きが良いオブジェクトの場合、数10本というレベルで影響することもあります。
具体的に、影響力を考慮した頂点をどうやって計算するのか?これは、意外と簡単です。Cの最後で導きました頂点変換の式は、100%の影響力を考えていますので、ここに影響力を表す定数項を掛け算するだけなんです。さらに、同じ頂点に対して別のボーンの移動分を加算していきますと、影響力を加味した変換位置が算出されます。式は次のようになります。
ごちゃごちゃしていますが、一番下の行を見て頂くのが早いでしょう。あるボーンiについて、ボーンオフセット行列とボーン行列の合成行列(BMiとしました)そして影響力(重みとも言います)を掛け算します。これを全てのボーンについて足し合わせると、1つの頂点をどう動かすかという最終的な行列が出来上がります。後は、それを頂点に適用(掛け算)するだけです。これまでの説明が理解できていれば、何の難しいこともありません(^-^)。このように、1つの頂点に対して複数のボーンを影響させて位置を計算する方法を「頂点ブレンディング」と言います。
ボーンと頂点の影響力は静的な物です。つまりオブジェクトをDirectXに読み込んだ時点で固定されています。にもかかわらず、1つの頂点に対して上式のようにすべてのボーンでの変換を計算をするのは明らかに無駄です。W=0というのが実は殆どなのですから相当に無駄です(笑)。そこで、1つの頂点に影響するボーンについてのみ上式を適用すると、頂点ブレンディングは非常に最適化されます。さらに追求すると、「複数の頂点」が「同じボーンの組み合わせ」に「同じだけの影響する」ということもかなり多いです。例えば、先ほどのボーンを動かした図で、ボーンCによって動かされた頂点の殆どは影響力100%で動いています。これらをまとめて計算してしまえば、計算の流れも最適化されることになります。下の図は、その計算の流れを示しています。
スキンメッシュアニメーションには、この計算機構を前提とした作りになっていますんで、しっかり理解しなければ、あっという間に混乱しちゃいます。
G まとめ
スキンメッシュアニメーションを行ううえで、今回の行列演算は必ず必要になります。これをまとめるにあたり、私も実際にオブジェクトを作り、Xファイルを眺めて、試行錯誤しながら理解をしていきました。スキンメッシュアニメーション自体はかなりメジャーな技術だと思うのですが、その全容を詳しく説明してくれている参考文献は見当たりません。悲しいところです。今回しつこいほど細かく説明したのも、ボーン操作という皆が絶対に悩む部分に関して最強の参考サイトにしようと思ったためでして、長文になりましたことをお詫びいたします。
しかし!これで、ボーン操作の基礎知識はばっちりのはずです。この操作(行列)の感覚が掴めますと、通常のフォワードキネマティクス(親ボーンから動かし始めるアニメション)だけでなく「インバースキネマティクス(IK)」にまで発展させる事だってできるんです。その融合はずっと先の話になってしまいますが、いつかは開設したいと思います。
次の章では、これまで蓄えた知識を用いまして、ボーン操作によるスキンメッシュアニメーションを実装してみたいと思います。
H 謝辞
この章を改定してまとめる動機を作って頂きましたもっさりさんに感謝致します。