マテリアルを読み込んでObjファイルを描画する

概要

最終更新日:2020/02/26

Objファイルをマテリアルの反映を行って描画するための方法を書いた記事です。
主に次の項目に該当する方に向けて書いています。
※頂点や法線の読み込みが完了していることを前提に書いているので
 まだの方は「頂点と法線読み込み」を確認してください。
  • マテリアル(mtl)ファイルの読み込み方を知りたい
  • マテリアルファイルを描画で利用したい

サンプル

サンプルはここからダウンロードできます。
環境については以下の内容となっています。

開発環境
VSのバージョン VisualStudio 2019
DirectXのバージョン DirectX11


mtlファイルの読み込み

mtlファイルの読み込みはobjファイル側とmtlファイル側で
以下の対応を行う必要があります。
objファイル側
  • mtllibで使われているmtlファイルを調べる
  • 所属しているマテリアル単位でポリゴンを用意する
mtlファイル側
  • mtlファイルを開いて解析をする
  • newmtl単位でマテリアルを解析する
  • 必要なマテリアル要素を保存する

objファイル側

ここからはObjファイルで必要な対応の説明を行います。

使われているmtlファイルを調べる

mtlib」は読み込み中のobjファイルで使用するmtlファイルを示すキーワードです。
mtlibの後ろに続く文字列がmtlファイルの名前になっています。

mtllib Cube.mtl

マテリアルファイルを読み込むために必要なので、
解析するワードに「mtllib」を追加してください。

else if (strstr(buffer, "mtllib") == buffer)
{
	Replace('\n', '\0', buffer);
	// 使用しているmtlファイルの追加
	out_material_list.push_back(&buffer[strlen("mtllib") + 1]);
}

mtllibキーワードを見つけたらファイル名を保存しておき、読み込みは後で行います。
すぐにmtlファイルの読み込みを行わないのは、このキーワードを見つけた時は
objファイルを読み込んでいる最中だからです。
複数のファイルを同時に読み込んで、処理を複雑化させる必要はないので
objファイルの読み込みが終わってから、改めてmtlの読み込みを行います。

所属しているマテリアル単位でポリゴンを用意する

「usemtl」は、キーワード以降の行の所属するマテリアルを示しています。
usemtlの後ろに続く文字列が所属するマテリアルの名前になっており、
この名前はmtllib内で宣言されています。

usemtl Material.001

3Dモデルは一つのモデルに複数のマテリアルが宣言されていることがあります。
そして、DirectXではマテリアル単位でメッシュの描画を行うことが多いため、
「usemtl」でどのマテリアルに所属しているのかを把握しながら
ポリゴンを保存していきます。

else if (strstr(buffer, "usemtl") == buffer)
{
	Replace('\n', '\0', buffer);
	// 所属しているマテリアルの名前を覚えておく
	current_mat_name = &buffer[strlen("usemtl") + 1];		
}

current_mat_nameはusemtlを見つけた行以降のデータが
所属するマテリアルの名前です。
C++のアクセス指定子のように次のusemtlが見つかるまで有効です。

usemtlで所属するマテリアルを識別できるようにしたら、
usemtlの数だけインデックスバッファを用意してポリゴンの頂点番号を保存します。

// 頂点バッファリストに追加
out_custom_vertices.push_back(vertex);
// マテリアル名にインデックスを保存する
m_Indices[current_material].push_back(out_custom_vertices.size() - 1);

mtlファイル側

ここからはmtlファイルで必要な対応の説明を行います。

mtlファイルを開いて解析をする

objファイル側で保存していたmtlファイルを開いて解析をします。
解析の方法はobjファイルと同じ手順です。
  1. 一行取得する
  2. 先頭の文字を調べる
  3. 必要なキーワードなら解析を進める

newmtl単位でマテリアルを解析する

newmtl」はマテリアルデータの開始を示すキーワードです。
newmtlの後ろに続く文字列がマテリアルの名前になっており、
objファイルのusemtlで使われています。
まず、mtl側の書式です。

newmtl Material.001

次がnewmtlを解析するコードです。

if (strstr(buffer, "newmtl") == buffer)
{
	Replace('\n', '\0', buffer);
	// マテリアルグループ名の切り替え
	current_material_name = &buffer[strlen("newmtl") + 1];
}

current_material_nameはnewmtlを見つけた行以降の
データが所属するマテリアルの名前です。
C++のアクセス指定子のように次のnewmtlが見つかるまで有効です。

必要なマテリアル要素を保存する

マテリアルもたくさんの要素があるので、
プロジェクトで必要なマテリアル要素を選別して保存します。
今回のサンプルでは「Ka」「Kd」の要素を保存しています。
「Ka」はアンビエント、「Kd」がディフューズカラーです。
まず、mtl側の書式です。

Ka 1.000000 1.000000 1.000000
Kd 0.021371 0.023662 0.840000

次に解析コードです。

// Ambientカラー
else if (strstr(buffer, "Ka") == buffer)
{
	Replace('\n', '\0', buffer);
	std::vector split;
	Split(' ', &buffer[strlen("Ka") + 1], split);

	for (int i = 0; i < split.size(); i++)
	{
		m_Materials[current_material_name].Ambient[i] = atof(split[i].c_str());
	}
}
// Diffuseカラー
else if (strstr(buffer, "Kd") == buffer)
{
	Replace('\n', '\0', buffer);
	std::vector split;
	Split(' ', &buffer[strlen("Kd") + 1], split);

	for (int i = 0; i < split.size(); i++)
	{
		m_Materials[current_material_name].Diffuse[i] = atof(split[i].c_str());
	}
}

マテリアルを使用したメッシュの描画

マテリアルを使用したメッシュの描画は以下のポイントに気を付けて描画します。
  • マテリアル単位で描画する
  • マテリアルを設定する

マテリアル単位で描画する

マテリアル毎にカラーや使用するテクスチャ情報が異なるので、
Objファイルの描画はマテリアル単位で行います。
読み込み時にマテリアル単位でインデックスバッファを用意しているので、
それらを使って描画を行います。

// インデックスバッファ配列の数 = マテリアルの数
for (auto index : m_Indices)
{
	// 途中省略

	// 描画
	graphics->GetContext()->DrawIndexed(
		index.second.size(),	// 頂点数
		0,			// オフセット
		0);			// 開始頂点のインデックス
}

マテリアルを設定する

マテリアルが切り替わるごとに「SetMaterial」で
マテリアルの設定を変更して描画します。

// インデックスバッファ配列の数 = マテリアルの数
for (auto index : m_Indices)
{
	// 途中省略

	// マテリアル設定
	graphics->SetMaterial(&m_Materials[index.first]);

	// 途中省略
}

これでマテリアルを反映したObjファイルを描画できます。

model_render_0008