2009/08/23 Na-7
2009/08/24 Na-7
2009/09/02 Na-7
2010/08/10 Na-7
2010/11/14 Na-7
Softimage付属ランタイムをXNAに組み込みスキンアニメする
注意 | この資料は、筆者が自らの経験を記録したものであり、他人に勧めるものではありません。 この資料を参考として行った行為がいかなる結果になろうとも、筆者は責任を負いませんので予めご承知おきください。 |
◎目次
◎概要 | |
◎使用ツール | |
◎ワールド座標変換行列 | |
1.A方式 | |
2.B方式 | |
◎同一モデルの複数表示 | |
手法A:Loadメソッドのオーバーライド | |
手法B:モーション関連データの退避復元 | |
○手法Bのサンプルコード | |
○手法Bのサンプルコード解説 |
◎概要
Softimageで作成したアニメーションモデルデータをXNAに渡す方法は複数ある(詳細はこちら)が、XNA公式連携手順によりデータを渡す場合は、最終的にアニメーションランタイムライブラリ(XSIXNARuntime)を使用することになる。しかし筆者が知る限りでは、このランタイムの使用方法(特にスキンアニメ関連)を懇切丁寧に記述した公式ドキュメントは(国内外問わず)存在しない。
英語ヘルプ及びXNAViewerを参考にすると、ランタイムの組み込みや基本事項を修得できるので、非スキンモデルのみ使用する場合はこれで十分と言えるかもしれない。しかしスキンアニメーションモデルを動かそうとする場合、英語ヘルプやXNAViewerには記述されていないノウハウが必要となるケースが多い。本稿ではそのノウハウを解説する。
◎使用ツール
ツール名 | 入手元 |
Softimage(旧名称XSI:Softimage Mod Tool 7.5:三次元CG製作用ソフトウェア) | http://www.softimage.com/products/modtool/ |
XNAViewer(Softimageで作成したデータをXNAで表示するXNA用サンプルプロジェクト) | Softimage添付(セットアップ方法はこちら) |
XNA3.0(XNA GameStudio 3.0:ゲーム開発用フレームワーク) | http://msdn.microsoft.com/ja-jp/xna/default.aspx |
◎ワールド座標変換行列
Softimageで作成したモデルをXNAで表示する場合、そのモデルのマテリアル設定(=エフェクト、.fxファイル)に合わせてXNAの描画コードを記述する必要がある。ここでは、モデルのマテリアルにSoftimage付属エフェクトファイル(Lambert.fxやPhong.fxなど)を使用した場合の注意事項を記述する。
2つの方式を記述するが、A方式がシンプルなので通常はA方式が良い。(但しB方式が必要になるケースがあったので併記する)
A方式 | |
下記のように記述すると、ワールド座標変換行列を渡すことができる。 Model.Root.Transform = World; XNAViewerの場合、ModelAssetクラスのCrosswalkModelが該当する。 Models[0].CrosswalkModel.Root.Transform = world;
例えば、XNAViewerのDraw()内で、DrawModel()を呼び出す直前に以下のようなコードを記述する。 // ワールド座標変換行列 Models[CurrentModel].CrosswalkModel.Root.Transform = world; // モデル描画 DrawModel(Models[CurrentModel].CrosswalkModel);
|
|
B方式 |
|
0.B方式を採用するメリット B方式はA方式に比べて適用作業が面倒であるが、ワールド座標変換行列をどうしてもエフェクトパラメータで渡したい場合に有効である。例えば、こちらの ボーン数増加改修を適用する場合、A方式だと モデルオブジェクトに対するスケールが反映されなくなってしまうが、B方式であれば正常に反映される。
1.エフェクトパラメータ名 XNAのエフェクトパラメータ名は、world、view、projection(文字大小は不統一)が一般的だが、Softimage付属エフェクトファイルは、Model、View、Projectionというパラメータ名で統一してい る(Lambert.fx等から共通的に呼び出されているxsi_defaultvs.hlslを参照のこと)。よって、ワールド座標のエフェクトパラメータ名は、スキン/非スキン共に「Model」 である。(詳細はブログ参照)
2.FXファイルの書き換え xsi_defaultvs.hlslはスキンモデルと非スキンモデルに処理が分かれており、スキンモデル処理内のワールド座標変換処理は、Modelパラメータを無視してい る。このままではワールド座標が反映されない。以下のように修正すると、ワールド座標が反映される。(非スキンモデルは改修不要) // transform in screen space (詳細はブログ参照)
3.XNAViewerの書き換え XNAViewerをB方式で動かす場合の改修例を記載する。 // 改修 〜 foreach (ModelMesh mesh in m.Meshes) { 〜
// 改修 〜 foreach (Effect effect in mesh.Effects) { if (effect.GetType() == typeof(BasicEffect)) { 〜 } 〜
// bind bones 〜 } protected
override
void Draw(GameTime
gameTime) 〜
// ワールド座標変換行列を指定する // モデル描画 〜 } |
◎同一モデルの複数表示
XNAViewerを改修してロボットモデルを増やすと、以下の問題が発生する。
・ロボットモデルには3種類のモーションがあるが、5体のロボットに別々のモーションを割り当てようとしても、皆同じモーションになってしまう。
・モーションの速度は、5体に割り当てたモーション速度の合計値に影響し、モーションが異常に速くなってしまう。
この問題は、ContentManager.Loadの「同じアセットを読み込む呼び出しを繰り返すと、同じオブジェクト インスタンスが返されます。(XNAヘルプより)」という仕様に起因して発生する。この問題を解消しない限り、同一モデルを複数表示するゲームの製作は困難である。(詳細はブログ参照)
この問題を解消するために、筆者は2つの手法を確立した。
手法A:Loadメソッドのオーバーライド
ContentManagerのReadAssetを利用してLoad メソッドをオーバーライド し、アセットが既にロード済みであったとしても、常にそのアセットの新しいコピーを返すようにする(サンプルコードはこちら)。こうして新たに作成したContentManagerをModels.Addの引数に指定する。(詳細はブログ参照)
長所
・手軽に実装できる
・コンテンツのデータ構造が変わっても影響を受けない
・モデルデータ以外のコンテンツデータにも適用可能
・ACLなど他のライブラリにも適用可能(たぶん)
(訂正:ACLでは複数表示の問題が発生しないため、適用不要)
短所
・メッシュデータやエフェクトデータも複製されるため、メモリを無駄に消費する(インスタンスを大量に作成する場合は不向き)
手法B:モーション関連データの退避復元
インスタンス毎にモーション関連データ(ボーントランスフォーム及びCurrentTime)の退避領域を作成し、必要に応じて退避復元する。(詳細はブログ参照)
長所
・メモリは浪費しない
短所
・描画速度は落ちる(トランスフォームデータのコピーを複数回実施しているため)
○手法Bのサンプルコード
1.新しいXNAViewerプロジェクトを用意する(セットアップ方法はこちら)
2.新しいXNAViewerプロジェクトが正常に起動し、モデルがアニメーションすることを確認する
3.Game1.csを開き、こちらのコードを張り付ける
○手法Bのサンプルコード解説
1.ModelAssetに、ボーントランスフォームとCurrentTimeの退避領域を設ける
public
Matrix[] BoneTransforms;
public
TimeSpan CurrentTime;
2.ModelAssetのLoadContent()の最後の方で、ボーントランスフォーム退避領域を初期化する
BoneTransforms = new
Matrix[CrosswalkModel.Bones.Count];
CrosswalkModel.CopyBoneTransformsTo(BoneTransforms);
3.Update()の最初の方で、退避領域からデータを復元する
Models[i].CrosswalkModel.CopyBoneTransformsFrom(Models[i].BoneTransforms);
Models[i].Animations[Models[i].AnimationIndex].CurrentTime =
Models[i].CurrentTime;
4.Update()の最後の方で、退避領域にデータを退避する
Models[i].CrosswalkModel.CopyBoneTransformsTo(Models[i].BoneTransforms);
Models[i].CurrentTime = Models[i].Animations[Models[i].AnimationIndex].CurrentTime;
5.DrawModel()の最初の方で、退避領域からデータを復元する
m.CopyBoneTransformsFrom(BoneTransforms);
6.DrawModel()の引数を追加して、5の引数BoneTransformsを参照可能とする
7.CurrentTimeの値を任意のタイミングでセットすると、モーションのタイミングを個別制御できる
Models[CurrentModel].CurrentTime =
TimeSpan.Parse("00:00:00");
サンプルコード解析のヒント:「追加」をキーワード検索すると、追加コードがヒットする