テクスチャを反映させたFBXを描画する

概要

最終更新日:2020/03/24
FBXSDKバージョン:FBX SDK 2020.0.1 VS2017

テクスチャを反映させたFBXファイルを描画する方法について書いた記事です。
主に次の項目に該当する方に向けて書いています。
  • FBXをDirectXで描画したい
  • FBXでテクスチャを反映して描画したい
  • マテリアルとテクスチャの関係を知りたい
  • テクスチャの取得方法が知りたい
  • UV座標の取得方法が知りたい

サンプル

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

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


描画のための準備

以下の内容については別記事で説明を完了しているのでこの記事では省略します。
  • FBX SDKのインストールと環境設定
  • FbxManagerの作成からFbxファイルのインポート
  • ルートノードから子ノードを辿る方法
  • Attributeについて
  • 頂点インデックスバッファ作成
  • 頂点と法線の取得による頂点バッファ作成
  • マテリアルの取得と反映
記事はこちらで書いてあります。

FBX SDKのインストールと環境設定の方法 => こちら
FbxManagerの作成からFbxファイルのインポートまで => こちら
ルートノードから子ノードを辿る方法とAttributeについて => こちら
頂点と法線、頂点インデックスバッファを使った描画 => こちら
頂点カラーを使った描画 => こちら
マテリアルを使った描画 => こちら

テクスチャの取り扱いはマテリアルが深く関連していますので、
マテリアルの取得方法などがよくわからない方はマテリアルの記事は確認してください。

入力レイアウトと頂点構造への追加

テクスチャ情報を描画に反映させるためには入力レイアウトと頂点構造に
UV情報を追加する必要があります。

// 入力レイアウト
D3D11_INPUT_ELEMENT_DESC vertex_desc[]{
	{ "POSITION",	0, DXGI_FORMAT_R32G32B32_FLOAT,		0,	0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	{ "NORMAL",		0, DXGI_FORMAT_R32G32B32_FLOAT,		0,	D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	{ "COLOR",		0, DXGI_FORMAT_R32G32B32A32_FLOAT,		0,	D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
	// 追加
	{ "TEXTURE",	0, DXGI_FORMAT_R32G32_FLOAT,		0,	D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};

// 頂点構造
struct CustomVertex
{
	Vector3 Posision;		// 座標(x, y, z)
	Vector3 Normal;			// 法線
	Color Color;			// 頂点カラー
	// 追加
	Vector2 TexturePos;		// テクスチャ座標(u, v)
};

テクスチャの保存場所

FBXにおけるテクスチャはメッシュやマテリアルと同様に
ルートノード直下で管理されています。

model_render_0108

単純にテクスチャの取得をしたいだけならRootNodeからまとめて取得できます。

テクスチャとマテリアル

テクスチャはマテリアルのDiffuseプロパティと繋がりを持っています。

model_render_0109

この繋がりを利用してプログラムでマテリアルからテクスチャを取得します。

マテリアルからテクスチャを取得する

マテリアルと繋がりのあるテクスチャの取得は「テクスチャとマテリアル」に
書いてあるように、Diffuseプロパティが繋がりを持っているので、
「FindProperty」を使い、Deffuseを取得します。

// Diffuseプロパティを取得
prop = material->FindProperty(FbxSurfaceMaterial::sDiffuse);

次は「GetSrcObject」と「GetSrcObjectCount」でテクスチャの取得を行いますが、
取得で使う型が二種類あります。
  • FbxFileTexture
  • FbxLayeredTexture

FbxFileTexture

FbxFileTextureを指定して取得できた場合は、マルチテクスチャではないことを
示しているため、そのままテクスチャ読み込みを行います。
マルチテクスチャとは一つのポリゴンに対して複数のテクスチャを重ねる手法です。

// FbxFileTextureを取得する
int texture_num = prop.GetSrcObjectCount<FbxFileTexture>();
if (texture_num > 0)
{
	// propからFbxFileTextureを取得	
	texture = prop.GetSrcObject<FbxFileTexture>(0);
}

FbxLayeredTexture

FbxLayeredTextureはマルチテクスチャのテクスチャ管理に使用されているクラスです。
FbxFileTextureの取得が失敗した場合、マルチテクスチャの可能性があるので、
FbxLayeredTextureを取得をして確認を行います。

// FbxFileTextureを取得する
int texture_num = prop.GetSrcObjectCount<FbxFileTexture>();
if (texture_num > 0)
{
	// propからFbxFileTextureを取得	
	texture = prop.GetSrcObject<FbxFileTexture>(0);
}
else
{
	// 失敗したらマルチテクスチャの可能性を考えてFbxLayeredTextureを指定する
	// FbxLayeredTextureからFbxFileTextureを取得	
	int layer_num = prop.GetSrcObjectCount<FbxLayeredTexture>();
	if (layer_num > 0)
	{
		texture = prop.GetSrcObject<FbxFileTexture>(0);
	}
}

FbxLayeredTextureが存在したらその後は通常のFbxFileTextureと同じ処理を行います。
※マルチテクスチャを実装する場合、テクスチャの保存方法などを
 変える必要がありますが、この記事では取り扱いません。

テクスチャの読み込み

FbxFileTextureが取得出来たら、インスタンスから「GetFileName」
または「GetRelativeFileName」を使用してファイル名の取得を行います。

model_render_0110
model_render_0111

どちらもファイル名を取得できますが、GetFileNameは絶対パス、
GetRelativeFileNameは相対パス込みで取得できます。

// ファイル名を取得
std::string file_path = texture->GetRelativeFileName();

相対パスを使った方がいいと思いますが、ファイルの出力方法次第では
どちらも同じパス情報が保存されているので、パスの中からファイル名だけを
取得するようにして、正しいファイルパスを付与してください。
ただし、パス分解はFBX SDKでは用意されていないので、自作しなければいけません。

// ファイル分解
char buffer[256];
ZeroMemory(buffer, sizeof(char) * 256);
memcpy(buffer, file_path.c_str(), sizeof(char) * 256);

// 記号統一
Replace('\\', '/', buffer);
std::vector split_list;
std::string replace_file_name = buffer;
// 「/」で分解
Split('/', buffer, split_list);

UTF-8からAnsiに変換

ファイル名に全角文字が含まれている場合、取得の際に文字化けすることがあります。

model_render_0112

上の文字化けしている部分の本来の文字列は「キャラクター」です。
この問題を解決するために、「FbxUTF8ToAnsi」を使用して
UTF-8の文字コードをANSIに変換します。

model_render_0114
char* file_name;
size_t size = 0;
// 文字コード変換
FbxUTF8ToAnsi(file_path.c_str(), file_name, &size);

これで、文字化けが解消されて本来の文字列として使用できます。

model_render_0113

FbxUTF8ToAnsiで変換した文字列はメモリを動的に確保されているので
最後に「FbxFree」を実行してメモリを解放させます。

model_render_0115
// 確保された文字列のメモリを解放
FbxFree(file_name);

文字列に変換した後は各自で用意しているテクスチャ読み込み関数を使用して
テクスチャを読み込んでください。
読み込みが無事に完了したら、どのような方法でも構いませんので、
テクスチャを保持していたマテリアルの情報とテクスチャ情報を結びける処理を
追加してください。
この処理によって描画時のマテリアル毎のテクスチャの指定が出来るようになります。

// 読み込んだテクスチャとマテリアルの関係を覚えておく
m_MaterialLinks[material->GetName()] = m_Textures[keyword];

これで、マテリアル側のテクスチャ描画の準備は終了です。

メッシュからUV情報を取得する

テクスチャを描画するにはメッシュの各頂点にUV座標を指定する必要があります。
このUVの取得は「GetPolygonVertexUVs」を使って行いますが、
引数にUVSetの名前を指定しなければいけません。

UVSetとはUVマッピングを行うためのUV情報のことです。
UVSetにはUV座標と、その座標を使用するためのインデックスバッファを持っています。

model_render_0117

このUVSetには名前がついており、この名前をGetPolygonVertexUVsで
指定することで、該当する名前のUVSetを取得できます。
UVSetの名前は「GetUVSetNames」で取得できます。

model_render_0116
model_render_0118
// uvsetの名前保存用
FbxStringList uvset_names;
// UVSetの名前リストを取得
mesh->GetUVSetNames(uvset_names);

FbxArray<FbxVector2> uv_buffer;

// UVSetの名前からUVSetを取得する
// 今回はシングルなので最初の名前を使う
mesh->GetPolygonVertexUVs(uvset_names.GetStringAt(0), uv_buffer);

取得したUVバッファはIndexToDirectの対応がされているので、
変換することなく、そのまま使用することが出来ます。

ただし、DirectXの場合はV値の反転は行ってください。
なぜ、行わなければいけないのかはこちらに書いています。

CPU側の描画の対応

描画の際に設定の必要があるのはメッシュで使用するテクスチャ情報だけです。
UVは頂点情報に含まれているので、特に何かをする必要はありません。
設定の方法はメッシュで使用されているマテリアルから指定するテクスチャを決めます。
サンプルではマテリアルの名前を使用して、この名前と繋がりのあるテクスチャを
設定するようにしています。

// テクスチャ設定
if (m_MaterialLinks.count(mesh.m_MaterialName) > 0)
{
	graphics->SetTexture(m_MaterialLinks[mesh.m_MaterialName]);
}
else
{
	graphics->SetTexture(nullptr);
}

GPU側の描画の対応

GPU(シェーダー)側の対応ですが、頂点シェーダーでは入力と出力に
頂点座標情報の追加と、入力値をそのまま出力します。

// 頂点シェーダー入力情報
struct VS_IN
{
	float4 pos : POSITION0;
	float4 nor : NORMAL0;
	float4 color : COLOR0;
	// 追加
	float2 texture_pos : TEXTURE0;
};

// 頂点シェーダー出力情報
struct VS_OUT
{
	float4 pos : SV_POSITION;
	float4 color : COLOR;
	// 追加
	float2 texture_pos : TEXTURE0;
};

// コード内
// Texture座標指定
output.texture_pos = input.texture_pos;

ピクセルシェーダーでも、入力にテクスチャ座標を追加します。
その情報をもとにテクスチャカラーを取得して出力カラーを算出します。

// ピクセルシェーダー入力情報
struct PS_IN
{
        float4 pos : SV_POSITION;
	float4 color : COLOR;
	// 追加
	float2 texture_pos : TEXTURE0;
};

// コード内
// テクスチャカラーの取得
float4 tex_color = Texture.Sample(Sampler, input.texture_pos);

テクスチャ解放

読み込んだテクスチャは使用しなくなったらきちんと解放してください。

// テクスチャ解放
for (auto texture : m_Textures)
{
	if (texture.second != nullptr)
	{
		texture.second->Release();
	}
}
m_Textures.clear();
m_MaterialLinks.clear();

これで、テクスチャが反映されたFBXが描画できます。