極短Xファイルスキンメッシュアニメーションスキンメッシュアニメーションは3Dプログラマの一つの壁かなと思います。データ構造を含めすべて1から作り上げるのは、理屈を完璧に理解していなければ絶対に出来ませんし、理解していても構造が複雑で組むのに難儀します。そんな中でXファイルによるスキンメッシュアニメーションは、DirectXが提供するヘルパー関数を用いることで手間を物凄く省かせてくれます。言え、面倒な部分もあるのは確か。コンパクトにまとまったソースコードもWeb上にはあまり見当たりません。そこで、Xファイルを用いたこてこてのコードを組むとどうなるのか、改めて作ってみる事にしました。コードの細かい部分の説明は、マルペケのスキンメッシュアニメーションの記事に記載している事とかぶりますので、多くは省かせて頂きますが、これでスキンメッシュアニメーションの壁が少しでも低くなれば幸いです(^-^)マルペケ恒例極短シリーズ、今回はスキンメッシュアニメーションです。1にも2にもID3DXAllocateHierarchyが面倒くさい...Xファイルを用いたスキンメッシュアニメーションの入り口はD3DXLoadMeshHierarchyFromX関数です。この関数はスキンメッシュが入ったXファイルから情報を余すことなく引き出してくれて(パーシング)、ボーンの構成であるフレームツリー(D3DXFRAME)、FKアニメーション(ID3DXAnimationController)という2つのオブジェクトを返してくれます。この関数にXファイルを投げると自動的にパースし膨大な結果を返してくれます。その結果を受けてフレームに格納する繋ぎ役になるのが「ID3DXAllocateHierarchy」インターフェイスです。このインターフェイスはメソッドだけが提供されていて実体がありません。この実装はプログラマ自身がやらなければならないのです。これがXファイルによるスキンメッシュアニメーションのいきなり大きな(そして面倒臭い)壁だったりします。D3D_OK;}};}#endifこのコード、かなり切り詰めました。切り詰めてもこんなです(^-^;。ID3DXAllocateHierarchyインターフェイスはD3DXFRAMEの生成と破棄、D3DXMESHCONTAINERの生成と破棄の4つのメソッドから構成されます。ポイントがいくつかあります。今回スキンメッシュをより楽にできるようにD3DXFRAMEの派生クラスであるOXD3DXFRAMEクラスを作っています。派生クラスにはidとoffsetMatrixの2つのメンバを追加しました。idはそのD3DXFRAMEが担当するボーンIDです。このIDは、ボーン構造の入ったXファイルを解析すると出来るID3DXSkinInfoというオブジェクトが知っています。D3DXFRAMEにはボーンの名前であるNameメンバが最初からいて、上のCreateFrameメソッドの引数にもそれが文字列で入ってきます。してわざわざIDを別途格納するのか?それは、D3DXFRAMEの名前(Name)はアニメーションを行うどの過程でも一切使われないからです!衝撃の事実...orz。一方のボーンIDはオフセット行列を指定する時やボーンの姿勢を更新する時、そして描画デバイスに各ボーンのワールド変換行列を渡す時などで使われる大切なキー(鍵)となってくれるんです。最初からID渡せ〜っと思ってしまいます(笑)。Xファイルのフレーム構造には対応するボーンが無いNULLフレームが混じっています。いう対応ボーンが無いフレームのIDは0xffffffffと統一する事にしました。OXD3DXFRAMEに追加されているもう一つのメンバoffsetMatrixは、名前の通りボーンオフセット行列を格納します。これはフレームの初期姿勢となる大切な行列で、これも最初から渡せーと思う行列の一つです。最強に面倒なのが次にあるCreateMeshContainerメソッドです。これはD3DXMESHCONTAINER構造体にデータを収めるのが主目的なメソッドなのですが、要素の数も種類も多く、また不定長データが多いため細かくnewしていくしかなくて吐きそうになります(^-^;。データ構造は決まっているので、静々とそれに従ってメモリを確保して格納しています。このメソッドの中ではID3DXSkinInfo::ConvertBlendedMeshメソッドを呼び出しています。ここが最大に大切なポイントでして、これによってメッシュはスキンメッシュとして動くようになるんです。このメソッドが返すボーン影響数(maxFaceInfl)、ボーンコンビネーション構造体数(numBoneCombinations)そしてボーンコンビネーション構造体テーブル(boneCombinationTable)はスキンメッシュを行う時にすべて使いますので、OXD3DXMESHCONTAINERクラスでこれらを追加しています。削除メソッド(DestroyFrame、DestroyMeshContainerメソッド)は確保した物を全部きれいさっぱり消しているだけです。メモリが細かく確保されているので、削除忘れがないか検討するのが一苦労する部分でもあります(^-^;こんな感じで、極短とは言えID3DXAllocateHierarchyインターフェイスを愚直に実装するのはとにかく面倒なんです。極短main.cpp公開します�@のOX::OXAllocateHierarchyクラスを使うとD3DXLoadMeshHierarchyFromX関数を使ってXファイルからすべての情報を引き出せます。フレームに格納するIDとかオフセット行列とか、いくつか足りない所もあります。それらは読み込みが終わった後に補ってあげる必要があります。0;}160行くらいに頑張って納めています(^-^;setFrameIdという関数があります。この関数を通すと引数に渡しているID3DXSkinInfoインターフェイスからボーンIDの情報が引き出され、フレームに対応するボーンIDが刻印されます。フレームはツリー構造になっているので、どうしても再帰関数が必要になります。今回はsetFrameId関数の中にローカルstruct(create)を作り、そのstaticメソッドfにそのトラバースをしてもらっています。本当はC++にクロージャ(関数の中に関数を作れる仕組み)があればもっと素直に書けるのですが、C++にはそれが無いので、擬似的な関数内関数をこう実現しています。この関数ではついでにボーンオフセット行列(offsetMatrix)も格納しています。OX::OXD3DXMESHCONTAINER構造体の中にはボーンコンビネーション配列(boneCombinationTable)を格納したのでした。この構造体にはサブメッシュ描画時に描画デバイスに渡す1メッシュに影響するボーン群のIDが格納されています。これが無いとスキンメッシュアニメーションは出来ないのです。サブメッシュ17番に影響するボーンIDは5,13,15,16という感じです。これをすべてのサブメッシュ描画時に設定する必要があります。そのため、この構造体を取りだすgetMeshContainer関数を設けています。これらの情報を得ておけば、後は描画ループに突入できます。描画ループではID3DXMesh::DrawSubset関数を呼び出してサブメッシュ毎に描画しています。1つのサブメッシュを描画するには、そのメッシュに影響する最大4本のボーンのワールド変換行列を描画デバイスに設定する必要があります。そのワールド変換行列は毎フレーム変化するので、計算し直さなければなりません。それを行っているのがupdateCombMatrix関数です。D3DXMATRIX>というマップを渡しています。この関数を通り抜けるとこのマップに各ボーンIDに対応したワールド変換行列が計算されてきます。ここを簡潔にするためにOXD3DXFRAME構造体にボーンオフセット行列を格納したんです(^-^)。巷の本を見るとD3DXFRAMEにワールド変換行列をメンバとして追加している実装があったりします。それも悪く無いのですが、それをすると「インスタンス化」ではまります。スキンメッシュな同じモデルを複数描画する時、それぞれのモデルは独立したポーズを取ります。D3DXFRAMEは共有されてしまう物なので、そこに各モデル固有のワールド変換行列を入れてしまうと、フレームのコピーを都度作っていく事になってしまいます。共有されてしまう物の中には共有するメンバしか入れてはいかんのです。そのため、今回はID3DXAnimationControllerで姿勢を変更した後にupdateCombMatrix関数を通してボーンの姿勢を回収してしまう方法を取りました。上記のコードを実行するには次のようにします。�@の全コードをコピーしてoxallocatehierarchy.hとして保存します。次に�Aの全コードをmain.cppとしてこれも保存します。main.cppでは、今回「tiny.x」をテクスチャ付きで動かすように決め打ちしています。SDKのフォルダ内にもありますが、面倒ならばこちらから全部まとめたアーカイブをDLしちゃって下さい(DXGSMP_No68_XFileSkinMeshAnimation.zip)。VisualStudioで空のWin32アプリケーションを作成し、上記ファイルをプロジェクトファイル直下に置き、プロジェクトにコードを追加します。後はコンパイルして実行すれば動きます。リンカエラーが出た時にはDirectXのライブラリがリンクされていませんので、適宜パスを正しく設定してリンクが通るようにして下さい。ビルドが通れば、下の絵のようなtinyが歩き出します!。