ホーム < ゲームつくろー! < DirectX技術編 < アニメーションの根っこ:フレームって何だ?

その24 アニメーションの根っこ:フレームって何だ?


 その23でアニメーションの流れについてサンプルプログラムSkinned Meshを参考にざーっと見てきましたが、そもそもアニメーションをするためにはどのようなデータを与えられているのでしょうか?それがわかって、初めてプログラムの計画が立てられるというものです。この章ではアニメーションに使われるデータについてxファイルを解析しながら細かく見ていくことにしましょう。


@ 基本単位「フレーム」

 三角ポリゴンが集まって形成されている最小単位のポリゴンオブジェクトを「メッシュ(Mesh)」といいます。メッシュは1つの場合もありますし、複数のメッシュによって1つの「物」を形成する場合もあります。その「物」の基本単位はフレーム(Frame)として管理されています。ではフレーム(Frame)として管理される情報は何か?まずはその辺りからを出発点としましょう。
 DirectXのマニュアルで調べると、xファイルの形式の説明が出てきます。

Frame
template Frame {
< 3D82AB46-62DA-11cf-AB39-0020AF71E433 >
  FrameTransformMatrix frameTransformMatrix;
   Mesh mesh;
}

xファイルは「テンプレート指向」という考え方で作られていまして、太文字の部分が変数名(テンプレート)に相当します。C++の構造体とほぼ同じで、宣言された変数を保持します。FrameテンプレートにはFrameTransformMatrixテンプレートとMeshテンプレートの2つが宣言されています(ただしFrameテンプレートは他のテンプレートも持つことが出来ます(オープンテンプレート))。この2つのテンプレートについて、さらに調べます。

FrameTransformMatrix
template FrameTransformMatrix {
   < F6F23F41-7686-11cf-8F52-0040333594A3 >
   Matrix4x4 frameMatrix;
}
Mesh
template Mesh {
   < 3D82AB44-62DA-11cf-AB39-0020AF71E433 >
   DWORD nVertices;
   array vertices[Vector];
   DWORD nFaces;
   array faces[Vector];

   // optional:
   meshFaceWraps template;
   meshTextureCoordinates template;
   meshNormals template;
   meshVertexColors template;
   meshMaterialList template;
}

FrameTransformMatrixテンプレートは、さらにMatrix4x4という4x4行列を定義するテンプレートを持っています。この行列はフレームに保持されているメッシュを別の座標(通常は親フレーム)に変換するローカル変換行列です。Meshテンプレートの方は、幾つかの変数、配列とオプションのテンプレートを持っています。Matrix4x4テンプレートを見てみましょう。

Matrix4x4
template Matrix4x4 {
< F6F23F45-7686-11cf-8F52-0040333594A3 >
 array float matrix[16];
}

16個のfloat型の変数の配列を保持できるようです(4x4ですから当然ですね)。

 ということで、フレームがどういう変数を保持しているのかが見えてきました。

 まとめると、フレームは三角ポリゴンの塊である「メッシュ」とそれを座標変換させる「ローカル変換行列」からなることがわかりました。



A アニメーションはどこに?

 xファイルのテンプレートであるFrameにはアニメーション部分が含まれていません。では、アニメーションは何処で定義されているのでしょうか。DirectXで検索すると、同じくxファイル形式で「Animation」が出てきました。

Animationテンプレート<日本語版>
template Animation {
   < 3D82AB4F-62DA-11cf-AB39-0020AF71E433 >
   DWORD AnimationKey;
   DWORD AnimationOptions;
}

マニュアルには、Animationテンプレートは前のフレームへの参照と、少なくとも1セットのAnimationKeyテンプレートが格納されている事とあります。ただ、前のフレームへの参照という意味がちょっと分かりにくいのと、上の表記では、誰が見てもAnimationKeyテンプレートが入っているようには見えません。ちょっと疑わしいので本場英語バージョン(December 2005)を見るとこうなっていました。

Animationテンプレート<英語版>
template Animation {
  < 3D82AB4F-62DA-11cf-AB39-0020AF71E433 >
  [...]
}

Where:
[ ... ] - Any .x file template can be used here. This makes the architecture extensible.

注目は[ ... ]の説明文で、これは「いくつかの.xファイルのテンプレートをここに入れることが出来ます。これによって、アーキテクチャの拡張を行えます」と言っています。つまり、Animationテンプレートには色々なテンプレートを含める事が出来るということのようです。よって、日本語版の変数の入れ方も出来るんでしょうが、日本語版は言っている事と表記がちょっとちぐはぐです。

 このままだとAnimationテンプレートについてピンとこないので、マニュアルにあるキューブのアニメーションの例を抜粋しましょう。

Frame CubeFrame {
   FrameTransformMatrix {...}
   {CubeMesh}
}

Animation Animation0 {
 {CubeFrame} // Use the frame containing the cube.
  AnimationKey {
     2; // Position keys
     9; // 9 keys
     10; 3; -100.000000, 0.000000, 0.000000;;,
     20; 3; -75.000000, 0.000000, 0.000000;;,
     30; 3; -50.000000, 0.000000, 0.000000;;,
     40; 3; -25.500000, 0.000000, 0.000000;;,
     50; 3; 0.000000, 0.000000, 0.000000;;,
     60; 3; 25.500000, 0.000000, 0.000000;;,
     70; 3; 50.000000, 0.000000, 0.000000;;,
     80; 3; 75.500000, 0.000000, 0.000000;;,
     90; 3; 100.000000, 0.000000, 0.000000;;;
  }
}

 最初にCubeFrameというフレームを設定しています。次にAnimation型であるAnimation0変数の内部に、太文字で示したCubeFrameという変数への参照(参照は中括弧で囲みます)とAnimationKeyテンプレートによるアニメーションキーを入れています。これは、日本語マニュアルで言っていた事と一致しますね。これより、Animation0はCubeFrameフレームに対するアニメーションを定義している事がわかります。なるほど、つまりアニメーションはフレームに含まれているのではなくて「フレームとは独立に定義する」ようです。

 AnimationKeyテンプレートというのは、アニメーション動作をキーフレームで設定するテンプレートで、次のように定義されています(英語版です)。

AnimationKeyテンプレート<英語版>
template AnimationKey {
   < 10DD46A8-775B-11CF-8F52-0040333594A3 >
   DWORD keyType;
   DWORD nKeys;
   array TimedFloatKeys keys[nKeys];
}

Where:
keyType - Specifies whether the keys are rotation, scale, position, or matrix keys (using the integers 0, 1, 2, or 3, respectively).
nKeys - Number of keys.
keys - An array of keys. See TimedFloatKeys.

keyTypeは定義したアニメーションが回転(0)なのか、スケール変換(1)なのか、ポジションのオフセット(2)なのか、それとも行列(3)なのかを番号で指定します(括弧がその番号です)。nKeyは設定してあるキーの数、そしてkeysはTimedFloatKeysテンプレートの配列です。ちなみに日本語マニュアルだと、ここもDWORDになっています(怒)。

 TimedFloatKeysテンプレートも見ておきましょう。

TimedFloatKeysテンプレート<英語版>
template TimedFloatKeys {
   < F406B180-7B3B-11cf-8F52-0040333594A3 >
   DWORD time;
   FloatKeys tfkeys;
}

timeにはキーフレームの時刻を、FloatKeys型であるtfkeysには1つのキーフレームを定義する数値(回転角度やポジションなど)を設定します。

 FloatKeys型は、次のようになっています(これが最後です)。

FloatKeysテンプレート<英語版>
template FloatKeys {
   < 10DD46A9-775B-11cf-8F52-0040333594A3 >
   DWORD nValues;
   array float values[nValues];
}

nValuesにはすぐ下のvalues配列の数を指定します。valuesに実際の数値を指定の数だけ列挙します。ここまでの情報を元にして先ほどのアニメーションキーの定義をもう一度見てみます。

  AnimationKey {
     2; // Position keys
     9; // 9 keys
     10; 3; -100.000000, 0.000000, 0.000000;;,
     20; 3; -75.000000, 0.000000, 0.000000;;,
     30; 3; -50.000000, 0.000000, 0.000000;;,
     40; 3; -25.500000, 0.000000, 0.000000;;,
     50; 3; 0.000000, 0.000000, 0.000000;;,
     60; 3; 25.500000, 0.000000, 0.000000;;,
     70; 3; 50.000000, 0.000000, 0.000000;;,
     80; 3; 75.500000, 0.000000, 0.000000;;,
     90; 3; 100.000000, 0.000000, 0.000000;;;
  }

 Position keysが2なので、これはオフセット(平行移動)のキーフレームアニメーションを表しています。keyが9なので、アニメーションキーの数は9、そして「10; 3; -100.0, 0.0, 0.0;;;」の数字はそれぞれ「時刻(フレーム); 変数の数; x; y; z;;」である事が良く分かります。

 ということで、大分掘り下げてきましたが、結局のところアニメーションはAnimationテンプレートの中でフレーム変数を指定し、キーフレームによって動作定義をすることで行える事がわかりました。もちろん、実際には自らxファイルを手打ちするなんて事はまずありませんが、「アニメーション」をするときに使われる値をしっかりと把握できました。



B 親子関係はどこに?

 ここまでで、xファイルから1つのフレームに対して独立した複数のアニメーションが定義できる事がわかりました。ところで、アニメーションの親子関係は何処に記述されているのでしょうか?ヘルプを見ても良く分かりません。そこで、tiny.xを調べてみました。

フレームの親子関係
Frame Scene_Root {
   FrameTransformMatrix {...}
   Frame body {
      FrameTransformMatrix {...}
      Frame {
         FrameTransformMatrix {...}
         Mesh {...}
      }
   }

   Frame Box01 {
      FrameTransformMatrix {...}
      Frame Bip01 {
         FrameTransformMatrix {...}
         Frame Bip01_Footsteps {
            FrameTransformMatrix {...}
         }
         Frame Bip01_Pelvis {
            FrameTransformMatrix {...}
            Frame Bip01_Spine {
               //-- 以下延々とフレームが続く・・・ --//

 これはtiny.x内でFrame部分だけを抜き出したものです。これを見ると親子関係の記述が良く分かりました。どうやら、親子関係はFrameを入れ子にすることによって実現できるようです。Scene_Rootフレームの子フレームにはbodyBox01フレームがあります。この2つのフレームは「兄弟フレーム」で、同じ座標空間を共有します。各フレームには必ずFrameTransformMatrixテンプレートがあり、そのフレームの位置・回転・スケールが保持されます。ちなみに、このフレームの定義がすべて終わった後に、Animationテンプレート部分がやってきます。

 ようやく、アニメーションの全容が見えてきました。



C スキンメッシュはどうなってるんだ?

 ここまでで、親子関係のあるポリゴンメッシュを行列を使って動かす仕組みが見えてきました。ただ、生物などのやわらかい動きを実現する「スキンメッシュ」については触れていませんでした。実は上のフレーム階層にあるMesh{...}の中にスキンメッシュの情報であるSkinWeightsテンプレートが埋め込まれています。プログラム上でもスキンメッシュの情報は取り扱わなければなりませんので、その元データを眺めてみる事にします。

スキンメッシュ 定義
SkinWeights {
   "Bip01_R_UpperArm";        // ボーン名
   156;                              // 頂点の数
   0,                                 // 頂点インデックス
   3449,

   //-- 以下延々と....

   1737,
   1738;
   0.605239,                      // ボーンによる重み
   0.605239,

   //-- 以下延々と....

   0.999994,
   0.979129;

   // メッシュの頂点をボーン空間に変換する行列
   -0.941743,-0.646748,0.574719,0.000000,
   -0.283133,-0.461979,-0.983825,0.000000,
   0.923060,-1.114919,0.257891,0.000000,
   -65.499557,30.497688,12.852692,1.000000;;
}
template SkinWeights {
   < 6F0D123B-BAD2-4167-A0D0-80224F25FABB >
   STRING transformNodeName;
   DWORD nWeights;
   array DWORD vertexIndices[nWeights];
   array float weights[nWeights];
   Matrix4x4 matrixOffset;
}

 スキンメッシュはいわゆる「ボーン」を操作することで達成されます。ボーンとはオブジェクトに埋め込まれた「骨」のことです。スキンメッシュの情報はSkinWeightsテンプレートに記述されています。定義を見ると、ボーン名前(transformNodeName)、影響される頂点の数(nWeights)、頂点インデックス配列(nvertexIndices)、重み配列(weights)そして頂点が定義されている座標(メッシュ座標)から頂点をボーン空間に変換する座標変換行列からなるようです。重みはボーンの回転に対する頂点の移動量の割合であり、値が小さいほど頂点はこのボーンの回転に対して不動になっていきます。ここでは、その情報だけが与えられているので、実際の頂点座標の計算はプログラマ自身が行わなければならないようです。変換行列はメッシュ座標→ボーン座標を行います。ボーン(=フレーム)自体もボーン座標→親ボーン座標という変換行列を必ず持っていますから、この2つの変換行列を掛け算する事で、メッシュ座標→ボーン座標→親ボーン座標という座標変換が出来ます。親をどんどんたどると、いずれメッシュ座標に戻ることになりますから、これはメッシュ座標→ボーン座標→親ボーン座標→親ボーン座標→・・・→メッシュ座標という長旅が出来ることを表します。なるほど、これらの変換行列は便利で面白いすね。



D まとめ

 アニメーションに必要な情報をまとめます。「物」の基本単位であるFrameは、入れ子にすることで親子・兄弟関係を表すことが出来ます。1つのFrameには複数のMeshが含まれ、それはFrame内に定義される変換行列によって親フレームの座標に変換されます。Meshの中には頂点インデックスや面の情報、法線など数々のメッシュ情報がありますが、そこにスキンメッシュの情報も格納されます。スキンメッシュはボーンに対して定義されていて、フレームとは独立しているようです。ここには、影響する頂点の数や頂点インデックス、重みなどが定義されています。メッシュの頂点を実際に移動させるために、メッシュの頂点をボーンの空間に一度変換し、ボーン空間で動かしてからFrame内の変換行列によって親フレームを辿り、メッシュ座標へ戻すことになります。メッシュ自体を変化させるアニメーションはボーンを移動・回転させることによって実現できますが、そこはプログラマが実装する部分のようです。
 一方、アニメーションはフレーム単位で定義され、フレーム内メッシュの回転やオフセット動作をアニメーションキーで定義します。これは「固定的なアニメーション」と言えるでしょう。



 xファイルから読み取れる事はこのくらいです。あとは、その23で見てきた実装をもう一度振り返ってみることになりそうです。