XNAにおける3Dゲーム描画設計 (モデル描画処理)
3/12に引き続き、モデルの描画処理の説明です。XNAにおけるモデル描画は「凝らないで描画するのであれば至極シンプル」に描画が行えます。
描画には前回説明した、視点行列、射影行列の情報を用いて、描画を行います。
「サンプルソース(2008.02.29.zip)」では、XNAのフレームワークにて作成されるSampleGame.Draw関数から、今回設計したDrawManagerクラスのDraw関数が呼び出される流れとなっており、この関数が描画の入口となっています。
SampleGame.Draw関数が呼び出されるタイミングはXNAの判断に委ねられており、特に指定しなければ16.6ms毎(内部ではTargetElapsedTime:{00:00:00.0166667}という設定)になっています。これは一般的なゲームの描画タイミングが1秒間に60フレーム更新となっているためです。

それではこの内部処理の説明をしてみたいと思います。
◇ ◇ ◇
【登録されているモデルキャラクタークラスの選別】
DrawManager.Draw関数では登録されているタスクを描画するためのループを行っており、その際描画対象がモデルキャラクタータスク(ModelCharacterTask)クラスのみ描画を行うように判定しています。対象となったタスクは、ここから更にDrawManager.DrawModel関数にモデル情報や表示座標を引き渡され、この中でモデルの描画を行います。
【モデル情報の描画】
DrawManager.DrawModel関数では引数に渡されたモデル情報や座標情報、回転情報、縮尺情報を用いて以下の手順で描画を行います。
(※この関数には半透明を表わす引数もとりあえず実装しています)
という流れとなります。
モデル情報の描画と言いつつも色々な用語が出てきたので、私なりの解釈で説明すると、以下のような感じでしょうか…
【ボーン】
モデル全体に存在する骨組となります。これはアニメーション情報に用いる情報となり、例えば人体のモデルを作成したとした場合、体に1本の軸(ボーン)が入っており、それを基準に5方向に頭、両腕、両足の各ボーンが設定され、それらが別々にアニメーションをする場合などに用いる…というイメージが大まかな掴みという感じでしょうか。
今回Metasequoiaでモデルを作成しているので、プラグイン等を用いない場合はXファイル上、ボーン情報は生成されていないのですが、XNAのファイル読み込みにてデフォルトで2つを与えられている感じです。
【メッシュ】
1モデル情報となります。モデル情報にはポリゴンという用語に代表される、頂点情報(Vertex)、頂点を3角形に構成した情報(Index)や、エフェクト情報が格納されています。
モデルには複数メッシュが格納されるイメージですが(例えば体、頭、腕、足、洋服等)、これは仕様なのか私には不明なのですが、Metasequoia(フリーウェア版)ではモデルをXファイルに出力すると、メッシュは1つのみとなる模様です。
【エフェクト】
ポリゴンの描画や、そのレンダリングの際に必要となる座標変換情報や効果(シェーディング)情報となります。このサンプルでは、メッシュ生成時にXNAが自動的に作成してくれるBasicEffectクラスがエフェクトとして生成されているので、これを用いて必要な情報を設定します。この情報を任意に変化させることで、ライティングやシェーディングの処理を自在に変化させることができます。描画の真髄はこのクラスにあるという感じです。
また、このエフェクトクラスは自前で作成することができ、そのクラスにHLSL(High Level Shader Language)という言語で記述した「エフェクトファイル」というものをXNAのプロジェクトに設定して、これをアセットとして読み込むことができ、PCに搭載されているグラフィックボードのシェーディング機能を使用する…といった事が可能となります。
【ワールド座標変換について】
40行, 50行にてeffect.Worldというエフェクトクラス内のメンバに座標変換を行っていますが、これはローカル座標系をワールド座標系に座標変換を行っています。
計算の細かい説明は詳しい説明のあるサイトにて調べてもらうとして、一般的にはこのような、「親ボーン×縮尺×回転」の行列をMatrix.CreateTranslationで平行移動させる…というのがお作法という感じです。
回転行列の掛け方が、Z→X→Yの順序とアルファベット的とは違うのですが、これはかけ算の順序で回転マトリックスが変化するので、表現をしたい様に掛け合わせるようになります。
(将来回転して飛んでいく弾等を検討したいので、とりあえずサンプルではこのように回すことにします)
◇ ◇ ◇

これで、ついにモデルの描画が行えました。
サンプルソースを見ると分かるとおり、「頂点同士の座標に線を引く、3頂点をポリゴンとして内部を塗る、光の加減で塗った面に明暗を付ける」などを今回のソースコード上では一切行っていません。
このように「かなり難かしいところはとりあえず隠蔽」が行え、「もしも深く操作したい場合は言語的な派生などを行い、拡張ができる」という設計が実現されている事は、XNAフレームワークの素晴らしさであると感じます。(とは行っても、○○○ツクール程、容易ではありませんが…)
描画には前回説明した、視点行列、射影行列の情報を用いて、描画を行います。
「サンプルソース(2008.02.29.zip)」では、XNAのフレームワークにて作成されるSampleGame.Draw関数から、今回設計したDrawManagerクラスのDraw関数が呼び出される流れとなっており、この関数が描画の入口となっています。
SampleGame.Draw関数が呼び出されるタイミングはXNAの判断に委ねられており、特に指定しなければ16.6ms毎(内部ではTargetElapsedTime:{00:00:00.0166667}という設定)になっています。これは一般的なゲームの描画タイミングが1秒間に60フレーム更新となっているためです。

それではこの内部処理の説明をしてみたいと思います。
◇ ◇ ◇
【登録されているモデルキャラクタークラスの選別】
DrawManager.Draw関数では登録されているタスクを描画するためのループを行っており、その際描画対象がモデルキャラクタータスク(ModelCharacterTask)クラスのみ描画を行うように判定しています。対象となったタスクは、ここから更にDrawManager.DrawModel関数にモデル情報や表示座標を引き渡され、この中でモデルの描画を行います。
【モデル情報の描画】
DrawManager.DrawModel関数では引数に渡されたモデル情報や座標情報、回転情報、縮尺情報を用いて以下の手順で描画を行います。
(※この関数には半透明を表わす引数もとりあえず実装しています)
1: /// <summary>モデルを描画します</summary> 2: /// <param name="model">モデル</param> 3: /// <param name="position">モデル中心座標</param> 4: /// <param name="rotate">回転情報</param> 5: /// <param name="scaleOn">スケールの可否(true:行う, false:行わない)</param> 6: /// <param name="scale">スケール情報</param> 7: /// <param name="alphaOn">透過をの可否(true:行う, false:行わない)</param> 8: /// <param name="alpha">透過情報</param> 9: /// <remarks> 10: /// 透過処理をかける場合は Z バッファ情報がおかしくなるので 11: /// 描画を行うオブジェクトの配列に任意でキューイングを行うようにしてください 12: /// </remarks> 13: public void DrawModel( 14: Model model, Vector3 position, Vector3 rotate, 15: bool scaleOn, Vector3 scale, 16: bool alphaOn, float alpha) 17: { 18: // ボーンの個数分変換行列を取得します 19: Matrix[] transforms = new Matrix[model.Bones.Count]; 20: model.CopyAbsoluteBoneTransformsTo(transforms); 21: 22: // モデルを描画する 23: foreach (ModelMesh mesh in model.Meshes) 24: { 25: //メッシュの各エフェクトを設定 26: foreach (BasicEffect effect in mesh.Effects) 27: { 28: // とりあえず現在はデフォルトのライト 29: effect.EnableDefaultLighting(); 30: 31: // ビュー 32: effect.View = m_camera; 33: // 射影 34: effect.Projection = m_projection; 35: // 伸縮が無い場合 36: // (マトリクスを掛けるオーバーヘッドがどれくらいか分らないのでとりあえず処理を分けます) 37: if (scaleOn == false) 38: { 39: // ワールド 40: effect.World = transforms[mesh.ParentBone.Index] * 41: Matrix.CreateRotationZ(rotate.Z) * 42: Matrix.CreateRotationX(rotate.X) * 43: Matrix.CreateRotationY(rotate.Y) * 44: Matrix.CreateTranslation(position); 45: } 46: // 伸縮がある場合 47: else 48: { 49: // ワールド 50: effect.World = transforms[mesh.ParentBone.Index] * 51: Matrix.CreateScale(scale) * 52: Matrix.CreateRotationZ(rotate.Z) * 53: Matrix.CreateRotationX(rotate.X) * 54: Matrix.CreateRotationY(rotate.Y) * 55: Matrix.CreateTranslation(position); 56: } 57: // 透過 58: if (alphaOn == true) 59: { 60: effect.Alpha = alpha; 61: } 62: else 63: { 64: effect.Alpha = 1; 65: } 66: } 67: mesh.Draw(); 68: } 69: } |
1. | ボーンの個数分変換行列を用意。(19~20行) |
2. | モデル内のメッシュの個数分のループを行い、各メッシュを描画する。(23行) |
2-1. | メッシュ内のエフェクトの個数分のループを行い、メッシュ内の各エフェクトに対して設定を行う。(26行) |
2-2. | ターゲットとなるエフェクト情報に座標変換情報[視点行列]、[射影行列](この二つは前回説明したカメラ等の情報)を設定し、更に現在のモデルの座標情 報や回転情報、縮尺情報を掛け合せて[ワールド行列]を作成して、これをエフェクト情報に設定します。(透過情報もこの際、エフェクト情報に設定します) (29~65行) |
2-3. | メッシュを描画。(67行) |
3. | メッシュ情報を全て描画するまで「2.」を繰り返す。 |
という流れとなります。
モデル情報の描画と言いつつも色々な用語が出てきたので、私なりの解釈で説明すると、以下のような感じでしょうか…
【ボーン】
モデル全体に存在する骨組となります。これはアニメーション情報に用いる情報となり、例えば人体のモデルを作成したとした場合、体に1本の軸(ボーン)が入っており、それを基準に5方向に頭、両腕、両足の各ボーンが設定され、それらが別々にアニメーションをする場合などに用いる…というイメージが大まかな掴みという感じでしょうか。
今回Metasequoiaでモデルを作成しているので、プラグイン等を用いない場合はXファイル上、ボーン情報は生成されていないのですが、XNAのファイル読み込みにてデフォルトで2つを与えられている感じです。
【メッシュ】
1モデル情報となります。モデル情報にはポリゴンという用語に代表される、頂点情報(Vertex)、頂点を3角形に構成した情報(Index)や、エフェクト情報が格納されています。
モデルには複数メッシュが格納されるイメージですが(例えば体、頭、腕、足、洋服等)、これは仕様なのか私には不明なのですが、Metasequoia(フリーウェア版)ではモデルをXファイルに出力すると、メッシュは1つのみとなる模様です。
【エフェクト】
ポリゴンの描画や、そのレンダリングの際に必要となる座標変換情報や効果(シェーディング)情報となります。このサンプルでは、メッシュ生成時にXNAが自動的に作成してくれるBasicEffectクラスがエフェクトとして生成されているので、これを用いて必要な情報を設定します。この情報を任意に変化させることで、ライティングやシェーディングの処理を自在に変化させることができます。描画の真髄はこのクラスにあるという感じです。
また、このエフェクトクラスは自前で作成することができ、そのクラスにHLSL(High Level Shader Language)という言語で記述した「エフェクトファイル」というものをXNAのプロジェクトに設定して、これをアセットとして読み込むことができ、PCに搭載されているグラフィックボードのシェーディング機能を使用する…といった事が可能となります。
【ワールド座標変換について】
40行, 50行にてeffect.Worldというエフェクトクラス内のメンバに座標変換を行っていますが、これはローカル座標系をワールド座標系に座標変換を行っています。
計算の細かい説明は詳しい説明のあるサイトにて調べてもらうとして、一般的にはこのような、「親ボーン×縮尺×回転」の行列をMatrix.CreateTranslationで平行移動させる…というのがお作法という感じです。
回転行列の掛け方が、Z→X→Yの順序とアルファベット的とは違うのですが、これはかけ算の順序で回転マトリックスが変化するので、表現をしたい様に掛け合わせるようになります。
(将来回転して飛んでいく弾等を検討したいので、とりあえずサンプルではこのように回すことにします)
◇ ◇ ◇

これで、ついにモデルの描画が行えました。
サンプルソースを見ると分かるとおり、「頂点同士の座標に線を引く、3頂点をポリゴンとして内部を塗る、光の加減で塗った面に明暗を付ける」などを今回のソースコード上では一切行っていません。
このように「かなり難かしいところはとりあえず隠蔽」が行え、「もしも深く操作したい場合は言語的な派生などを行い、拡張ができる」という設計が実現されている事は、XNAフレームワークの素晴らしさであると感じます。(とは行っても、○○○ツクール程、容易ではありませんが…)
コメント
コメントの投稿
« 昔のゲームの想い出 [0030] 「チェスターフィールド」 [ビック東海] [1987] [ファミリーコンピュータ] l Home l 昔のゲームの想い出 [0029] 「サラダの国のトマト姫」 [ハドソン] [1984] [MB-S1] »