Xファイルからモデルデータ読み込み
いよいよ、Xファイルの読み込みを説明します!「Xファイル」とは、ポリゴンの頂点データや面のデータ、マテリアルなどいろんなデータをまとめたファイルです。これを読み込むことによって、簡単に複雑な形状のモデルを表示させることができるようになります。
実行すると下のように表示されます。結構前に作ったモデルなので、モデルの形状とかテクスチャーとか結構適当です。ちなみに名前は「でるでる」です。(ゲーム「JPEGバトラー」より)

下のリンクから今回のプロジェクトをダウンロードできます。
今回のメインコードファイルを載せます。重要なコードを赤色で表示させています。
MainSample.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
namespace MDXSample
{
/// <summary>
///
/// </summary>
public partial class MainSample : IDisposable
{
/// <summary>
/// メッシュ
/// </summary>
private Mesh _mesh = null;
/// <summary>
/// マテリアル情報配列
/// </summary>
private ExtendedMaterial[] _materials = null;
/// <summary>
/// テクスチャー配列
/// </summary>
private Texture[] _textures = null;
/// <summary>
///
/// </summary>
/// <param name="topLevelForm"></param>
/// <returns></returns>
/// <remarks>
///
/// </remarks>
public bool InitializeApplication(MainForm topLevelForm)
{
this._form = topLevelForm;
this.CreateInputEvent(topLevelForm);
try
{
this.CreateDevice(topLevelForm);
this.CreateFont();
}
catch (DirectXException ex)
{
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// Xファイルを読み込んでメッシュを作成する
try
{
this._mesh = Mesh.FromFile("Deruderu.x",
MeshFlags.Managed, this._device, out this._materials);
}
catch (DirectXException ex)
{
// メッシュの作成に失敗した場合は例外が飛んでくる
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
// 法線情報がなければ計算して作成
if ((this._mesh.VertexFormat & VertexFormats.Normal) == 0)
{
// 法線情報を加えたメッシュを複製する
Mesh tempMesh = this._mesh.Clone(this._mesh.Options.Value,
this._mesh.VertexFormat | VertexFormats.Normal, this._device);
// 法線を計算
tempMesh.ComputeNormals();
// 古いメッシュを破棄し、置き換える
this._mesh.Dispose();
this._mesh = tempMesh;
}
// テクスチャーがあれば読み込み
if (this._materials.Length >= 1)
{
// テクスチャー用の配列を作成
this._textures = new Texture[this._materials.Length];
// 配列分テクスチャーの読み込みを試みる
for (int i = 0; i < this._materials.Length; i++)
{
// 必ず null で初期化する
this._textures[i] = null;
// テクスチャー名が登録されているか確認
if (this._materials[i].TextureFilename != null &&
this._materials[i].TextureFilename.Length >= 1)
{
try
{
// テクスチャーを読み込む
this._textures[i] = TextureLoader.FromFile(this._device,
this._materials[i].TextureFilename);
}
catch (DirectXException ex)
{
// テクスチャーの作成に失敗した場合は例外が飛んでくる
MessageBox.Show(ex.ToString(), "エラー",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
}
}
this._device.Lights[0].Type = LightType.Directional;
this._device.Lights[0].Direction = new Vector3(1.0f, -1.5f, 2.0f);
this._device.Lights[0].Diffuse = Color.White;
this._device.Lights[0].Ambient = Color.FromArgb(255, 128, 128, 128);
this._device.Lights[0].Enabled = true;
this._device.Lights[0].Update();
return true;
}
/// <summary>
///
/// </summary>
public void MainLoop()
{
this.SettingCamera();
this._device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.DarkBlue, 1.0f, 0);
this._device.BeginScene();
// メッシュの描画
// 属性の数だけループさせて描画
for (int i = 0; i < this._materials.Length; i++)
{
// テクスチャーのセット
this._device.SetTexture(0, this._textures[i]);
// マテリアルをセット
this._device.Material = this._materials[i].Material3D;
// 描画
this._mesh.DrawSubset(i);
}
this._font.DrawText(null, "θ:" + this._lensPosTheta, 0, 0, Color.White);
this._font.DrawText(null, "φ:" + this._lensPosPhi, 0, 12, Color.White);
this._font.DrawText(null, "マウス位置:" + this._oldMousePoint, 0, 24, Color.White);
this._device.EndScene();
this._device.Present();
}
/// <summary>
///
/// </summary>
public void Dispose()
{
// テクスチャーの解放
if (this._textures != null)
{
foreach (Texture i in this._textures)
{
if (i != null)
{
i.Dispose();
}
}
}
// メッシュの解放
if (this._mesh != null)
{
this._mesh.Dispose();
}
if (this._font != null)
{
this._font.Dispose();
}
if (this._device != null)
{
this._device.Dispose();
}
}
}
}
|
では、赤文字の部分を説明していきます。MainSamplePartial.cs ファイルのコードはこちらです。
/// <summary>
///
/// </summary>
private Mesh _mesh = null;
/// <summary>
///
/// </summary>
private ExtendedMaterial[] _materials = null;
/// <summary>
///
/// </summary>
private Texture[] _textures = null;
|
今回、モデルデータをコードで作るのではなく、Xファイルから読み込む方法をとっているため、今までと処理内容が結構変わります。
早速ですが、今まで見たことのない型が2つ出てきました。
まず最初の「Mesh」ですが、日本語では「メッシュ」と呼びます。これは、「頂点バッファ」「インデックスバッファ」「面情報」「属性テーブル」など複数のデータをまとめたクラスです。今までのボックスのようにバッファなどのフィールドを複数持ったり、配列としてもったりする必要はなく、このクラスひとつでまとめてしまうことができるのです。
Xファイルからデータを読み込んだ場合、ほとんどはこのクラスにデータを保存します。
2つ目の「ExtendMaterial」は名前のとおり、「拡張したマテリアル」です。といってもマテリアルに関しては何も違いはありません。ただ、追加要素として「テクスチャーのファイル名」が加わっています。これはXファイルに設定されているテクスチャーファイル名を読み込むだけのものであり、テクスチャーを読み込めばほとんど必要なくなります。
なので、メモリの無駄遣いを避けたい場合は、Xファイルを読み込むときだけこの型を使い、「Material」だけを持っていたほうがいいでしょう。
「ExtendMaterial」「Texture」の型に関して配列という形をとっていますが、Xファイルには複数のマテリアルやテクスチャーを持っていることがあります。それを描画時に使い分けるために配列化してあります。
try
{
this._mesh = Mesh.FromFile("Deruderu.x",
MeshFlags.Managed, this._device, out this._materials);
}
catch (DirectXException ex)
{
MessageBox.Show(ex.ToString(), "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
|
Xファイルからメッシュデータを読み込みます。基本的に1行で済んでしまいますが、念のためにエラーチェックを行っています。エラーが起きればメッセージボックスを表示して
false を返すようにします。
|
Mesh.FromFile メソッド
|
| Xファイルからメッシュ関連データを読み込みます。 |
| filename |
読み込むXファイル名を指定しています。ファイル名の指定方法は「絶対パス」か「カレントディレクトリからの相対パス」です。今回使用している「Deruderu.x」ファイルは実行ファイルと同じディレクトリにおいてあります。 |
| options |
メッシュの作成オプション。特になければ「MeshFlags.Managed」でいいでしょう。 |
| device |
Direct3D デバイス |
| materials |
マテリアル データを含む配列を受け取ります。これを使用してテクスチャーを読み込んだり、マテリアルを使用したりします。 |
前回ボックスを6つの面に分けて作成しましたが、そんな少ないポリゴン数でもコードが非常に長くなってしまいました。しかし、Xファイルには「位置情報」や「テクスチャーのUV座標」など必要なデータがすでに入っているので、ちょこっと読み込むだけで簡単にモデルを作成できます。
通常複雑なポリゴン構成を作成するときは、「Xファイル」のようなモデルデータファイルを読み込んで作成するのが一般的です。
if ((this._mesh.VertexFormat & VertexFormats.Normal) == 0)
{
Mesh tempMesh = this._mesh.Clone(this._mesh.Options.Value,
this._mesh.VertexFormat | VertexFormats.Normal, this._device);
tempMesh.ComputeNormals();
this._mesh.Dispose();
this._mesh = tempMesh;
}
|
さて、Xファイルには「位置」や「UV座標」などのデータがあっても、「法線」のデータがないこともあります。もしなければ、法線データを加えて計算するようにしています。
なぜなら、法線データがないとライトを当てたときに色がおかしくなるためです。もちろんいらなければこのコードをはずしてもかまいません。
まず、条件分岐でメッシュの頂点フォーマットに、法線のデータが含まれているか確認します。法線があれば特に何もしません。
法線がなかった場合は、読み込んだメッシュに法線データを付け加えたメッシュを「複製」します。なぜ複製するのかというと、基本的にメッシュは直接頂点フォーマットを変えることはできません。例外もありますが、法線データを増やすなど、頂点のサイズが変化してしまう場合は必ず複製しなければなりません。
|
Mesh.Clone メソッド
|
| メッシュ���ータを複製します。 |
| options |
メッシュの作成オプション。複製元のメッシュのオプションを渡せばいいでしょう。 |
| vertexFormat |
ここでは複製元のメッシュの頂点フォーマットに法線データを付け加えるように指定します |
| device |
Direct3D デバイス |
新しく複製したメッシュには法線データを設定できるスペースはできましたが、各頂点の実際の法線データはまだ設定されていません。そこで使用するのが「Mesh.ComputeNormals」メソッドです。これを使用すると、面の法線の関係などから頂点の法線を自動的に計算してくれます。
後は最初に読み込んだメッシュを破棄し、新しいメッシュに置き換えます。
if (this._materials.Length >= 1)
{
this._textures = new Texture[this._materials.Length];
for (int i = 0; i < this._materials.Length; i++)
{
this._textures[i] = null;
if (this._materials[i].TextureFilename != null &&
this._materials[i].TextureFilename.Length >= 1)
{
try
{
this._textures[i] = TextureLoader.FromFile(this._device,
this._materials[i].TextureFilename);
}
catch (DirectXException ex)
{
MessageBox.Show(ex.ToString(), "エラー",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
}
}
|
メッシュで使用するテクスチャーを読み込みます。まず、属性の数を確認して読み込むか決めます。マテリアルがなければテクスチャーもないので読み込みません。
属性の数と言っていますが、「マテリアルとテクスチャーの組み合わせの数」と考えたほうがわかりやすいかもしれません。厳密には「属性テーブル」のエントリ数なのですが、ここではあんまり深いところまでは追求しません。ここでは属性の数と呼ぶことにします。
最初にテクスチャーを属性の数だけ作成します。その後、各配列にテクスチャーファイル名があるか確認して読み込みを行います。読み込むテクスチャーがない場合は必ず null を設定してください。これは描画時にテクスチャーをセットする場合に使用します。
for (int i = 0; i < this._materials.Length; i++)
{
this._device.SetTexture(0, this._textures[i]);
this._device.Material = this._materials[i].Material3D;
this._mesh.DrawSubset(i);
}
|
Xファイルの読み込みが終わったら描画に移ります。
メッシュの属性の数だけループさせてポリゴンを表示させています。なぜ一度に描画しないのかというと、属性のインデックスによって、使用するマテリアルやテクスチャーが違うため、インデックスが変わるたびにセットし直す必要があるからです。
言葉どおりループ内で毎回テクスチャーとマテリアルをセットしています。
「SetTexture」メソッドで第2引数に null を指定した場合は、テクスチャーを使用しないことと同じなので、テクスチャー作成時に null をセットしたのはこのためです。第1引数に関しては今は 0 を指定しておいてください。基本的には 0 でいいです。
最後に「DrawSubset」で描画しています。頂点バッファとかインデックスバッファの設定などはすべてこの中に含まれているので、コードがすっきりします。
if (this._textures != null)
{
foreach (Texture i in this._textures)
{
if (i != null)
{
i.Dispose();
}
}
}
if (this._mesh != null)
{
this._mesh.Dispose();
}
|
テクスチャーは読み込まれているならすべて解放します。読み込まれていないインデックスもあるかもしれないので、必ず null であるかチェックします。
メッシュの解放も忘れないように。
|