頂点カラーを反映させたFBXを描画する

概要

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

頂点カラーを反映させたFBXファイルを描画する方法について書いた記事です。
主に次の項目に該当する方に向けて書いています。
  • FBXをDirectXで描画したい
  • 頂点カラーを反映させたい
  • eIndexToDirectやeDirectについて知りたい

サンプル

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

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


描画のための準備

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

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

GetSrcObjectによるデータの取得

Fbxでデータを取得するためにはRootノードからAttributeの種類を調べて
該当するノードを見つける、または集める必要がありましたが
FbxSceneの「GetSrcObject」を使えば、その工程を省略できます。
GetSrcObjectは型を指定すれば、その型だけを集めてくれます。
そして、番号の指定でデータ集めたデータの取得ができます。

model_render_0079
model_render_0080
// FbxMeshの数を取得
int mesh_num = fbx_scene->GetSrcObjectCount<FbxMesh>();

for (int i = 0; i < mesh_num; i++)
{
	// Mesh作成
	CreateMesh(fbx_scene->GetSrcObject<FbxMesh>(i));
}

型の指定はテンプレートで行います。

使用条件

GetSrcObjectは「libfbxsdk.lib」では動作しません。
「libfbxsdk-mt.lib」か「libfbxsdk-md.lib」を設定する必要があります。
この設定を行った場合にFBXのVersionによっては動作しないことがありますので、
こちらで設定やエラー回避方法を書いています。
動作しなかった場合は確認してください。

頂点カラーの取得

頂点カラーデータはFbxMeshが保持しているので「GetElementVertexColorCount」と
「GetElementVertexColor」を使用して取得することから始まります。

model_render_0081
model_render_0082
// 頂点カラーのデータ数を確認
int color_count = mesh->GetElementVertexColorCount();
if (color_count == 0)
{
	return;
}
	
// 頂点カラーデータの取得
FbxGeometryElementVertexColor* color_buffer = mesh->GetElementVertexColor(0);

if (color_buffer == nullptr)
{
	return;
}

無事にデータを取得することができたら、FbxGeometryElementVertexColorの
「GetMappingMode」と「GetReferenceMode」を使用して情報を確認します。

model_render_0084
model_render_0085
FbxLayerElement::EMappingMode mapping_mode = vertex_colors->GetMappingMode();
FbxLayerElement::EReferenceMode reference_mode = vertex_colors->GetReferenceMode();

  • MappingMode:頂点カラーが設定されている対象の情報
    eByControlPoint:コントロールポイント
    eByPolygonVertex:頂点
    eByPolygon:ポリゴン
    eByEdge:辺
    eAllSame:どれでもいい
  • ReferenceMode:カラー情報への参照方法
    eDirect:直接参照する
    eIndexToDirect:インデックスバッファを介して参照する
二つの情報でデータが設定される対象の情報や参照方法がわかります。
この情報は頂点カラーだけではなく、法線やマテリアルバも同じ情報持っています。
なぜなら、各データの基底クラスになっている「FbxLayerElement」の情報だからです。

model_render_0083
この情報によって取得方法が変わります。
今回はMappingModeが「eByPolygonVertex」で、
ReferenceModeが「eIndexToDirect」だった場合の方法を紹介します。

// モードチェック
if (mapping_mode == FbxLayerElement::eByPolygonVertex)
{
	// 参照方法チェック
	if (reference_mode == FbxLayerElement::eIndexToDirect)
	{
		// 頂点カラーバッファ取得
		FbxLayerElementArrayTemplate<FbxColor>& colors = color_buffer->GetDirectArray();
		// 頂点カラーインデックスバッファ
		FbxLayerElementArrayTemplate<int>& indeces = color_buffer->GetIndexArray();

		for (int i = 0; i < indeces.GetCount(); i++)
		{
			int id = indeces.GetAt(i);
			FbxColor color = colors.GetAt(id);
			mesh_data.m_Vertices[i].Color.Alpha = color.mAlpha;
			mesh_data.m_Vertices[i].Color.Red = color.mRed;
			mesh_data.m_Vertices[i].Color.Green = color.mGreen;
			mesh_data.m_Vertices[i].Color.Blue = color.mBlue;
		}
	}
}

eIndexToDirectなので、以下の流れで頂点カラーの取得を行います。
  1. 頂点カラーインデックスバッファで取得したい頂点の番号を指定する
  2. インデックスバッファで取得した番号を使い頂点カラーバッファにアクセスする
  3. アクセスして取得した頂点カラーを使う
各バッファは「GetDirectArray」で頂点カラーバッファ、
「GetIndexArray」で頂点カラーインデックスバッファを取得します。

この方法のメリットは重複したデータを保持しなくてよくなるので、
メモリや修正がしやすくなることです。
まずは頂点単位のMappingModeでeDirectだった場合のデータ構成です。

model_render_0086

上の図のようにeDirectはシンプルで分かりやすいですが、
重複しているデータがあるので、メモリ的には無駄があります。
次にeIndexToDirectのデータ構成です。

model_render_0087

eIndexToDirectの構成の場合、カラーバッファから重複データがなくなるので、
使用するメモリを減らすことができます。
図ではインデックス用のバッファがあることで、メモリを多く消費していると
考える方もいらっしゃると思いますが、実際のモデルデータでは
インデックスを使用した方が、圧倒的にメモリ量を減らすことが出来ます。

頂点カラーの反映に必要な事

頂点カラーを描画で反映するためにはCPU側でInputLayoutにColor情報の追加、
Shader側もColor情報を受け取るための追加が必要です。

// CPU側
// InputLayoutにColorを追加
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_R32G32B32_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 },
};

// GPU側
struct VS_IN
{
	float4 pos : POSITION0;
	float4 nor : NORMAL0;
	float4 color : COLOR0;
	float2 texture_pos : TEXTURE0;
};

シェーダー側の対応

シェーダー側の対応は頂点シェーダーからピクセルシェーダーに
カラー情報を渡すだけです。
法線を使用した陰影の反映は頂点シェーダかピクセルシェーダで行います。

// 頂点カラー
output.color = input.color;

float4 normal;
// 移動が計算に反映させない
input.nor.w = 0.0;
// 頂点の法線にワールド行列を掛け合わせて
// ワールド座標上での法線の向きに変換する
normal = mul(input.nor, World).xyzw;
normal = normalize(normal);
// saturate => 引数で指定した値を0~1間での範囲に収める
// dot => 内積計算
output.color *= saturate(dot(normal, LightVector));

これで頂点カラーが反映されたメッシュが描画されます。