次に、Mayaで指定したテクスチャが反映されるようにする。
最終的には、テクスチャを貼っていればそのテクスチャの色、貼ってなければそのマテリアルの色を表示することになる。
つまりテクスチャの情報と色情報がセットで「マテリアル情報」となる。
これを構造体にまとめておこう。
ただし、「テクスチャを貼っていない場合」のことは後回しにするので、まずはテクスチャ情報だけでよい。
class Fbx
{
//マテリアル
struct MATERIAL
{
Texture* pTexture;
};
:
:
:
int vertexCount_; //頂点数
int polygonCount_; //ポリゴン数
int materialCount_; //マテリアルの個数
ID3D11Buffer *pVertexBuffer_;
ID3D11Buffer *pIndexBuffer_;
ID3D11Buffer *pConstantBuffer_;
MATERIAL* pMaterialList_;
「マテリアルを何個使ってるか」はその時にならないとわからないので、とりあえずポインタにしておく。
まずは、マテリアルを何個使ってるのか調べよう。
HRESULT Fbx::Load(std::string fileName)
{
:
:
:
//各情報の個数を取得
vertexCount_ = mesh->GetControlPointsCount(); //頂点の数
polygonCount_ = mesh->GetPolygonCount(); //ポリゴンの数
materialCount_ = pNode->GetMaterialCount();
InitVertex(mesh); //頂点バッファ準備
ノードからマテリアルの情報を引き出す。
いつも通り関数を分けておこう。
HRESULT Fbx::Load(std::string fileName)
{
:
:
:
InitVertex(mesh); //頂点バッファ準備
InitIndex(mesh); //インデックスバッファ準備
IntConstantBuffer(); //コンスタントバッファ準備
InitMaterial(pNode);
//マネージャ解放
pFbxManager->Destroy();
return S_OK;
}
:
:
:
void Fbx::InitMaterial(fbxsdk::FbxNode* pNode)
{
}
マテリアルの数はもう分っているので、pMaterialListを配列にして、マテリアルの個数分ループ処理にする。
void Fbx::InitMaterial(fbxsdk::FbxNode * pNode)
{
pMaterialList_ = new ●●●●;
for (int i = 0; i < ●●●●●●; i++)
{
}
}
i番目のマテリアルの情報を取得し、さらにその中のテクスチャ情報を調べる。
その情報の数(テクスチャの数)でテクスチャを貼ったかどうかがわかる。
for (int i = 0; i < ●●●●●●; i++)
{
//i番目のマテリアル情報を取得
FbxSurfaceMaterial* pMaterial = pNode->GetMaterial(i);
//テクスチャ情報
FbxProperty lProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sDiffuse);
//テクスチャの数数
int fileTextureCount = lProperty.GetSrcObjectCount<FbxFileTexture>();
//テクスチャあり
if (●●●●●●●●●●●●)
{
}
//テクスチャ無し
else
{
}
}
テクスチャを貼っていた場合は、そのファイル名を調べてテクスチャを作成する。
ファイル名さえわかってしまえば、Quadでもやっているので簡単。
FbxFileTexture* textureInfo = lProperty.GetSrcObject<FbxFileTexture>(0);
const char* textureFilePath = textureInfo->GetRelativeFileName();
//ファイルからテクスチャ作成
pMaterialList_[i].pTexture = ●●●●●●●●;
pMaterialList_[i].pTexture->●●●●●●●●●●●●;
この場合は pMaterialList_[i].pTexture を nullptr にしておけばよい。
ここで一度実行してみよう。
正しくエラー処理を書いていれば、画像ファイルがロードできずに停止するはず。
ブレークポイントをつけて、先ほど取得したファイル名が何になっているか確認してみよう。
ファイル名がフルパス(絶対パス)になっていて、Mayaでテクスチャを貼った時のパスになっているはず。
その場所に偶然画像ファイルがあることはほとんどないため、フルパスから「ファイル名+拡張子」のみに分解する。
(例)「c:\aaa\bbb\xxx.png」⇒「xxx.png」
_splitpath_sという関数はすごく便利なので紹介しておく。
たとえば次のようなプログラムを書いたとしよう。
【例】
char drive[_MAX_DRIVE]; //ドライブ名
char dir[_MAX_DIR]; //ディレクトリ(フォルダ)名
char name[_MAX_FNAME]; //ファイル名
char ext[_MAX_EXT]; //拡張子
_splitpath_s("d:/ge2a99/maya/texture/abc.png", drive, _MAX_DRIVE, dir, _MAX_DIR, name, _MAX_FNAME, ext, _MAX_EXT);
「_MAX_DRIVE」や「_MAX_DIR」などは、Windowsで予めdefineされている定数で、
「ドライブ名やフォルダ名の最大文字数はこのくらいだろう」という値が入っている。
(詳しくは宣言を見てみると良い)
上のプログラムを実行すると、各変数には次の文字列が入る。
『drive』・・・ "d:"
『dir』 ・・・ "/ge2a99/maya/texture/"
『name』 ・・・ "abc"
『ext』 ・・・ ".png"
このように、パスを「ドライブ名」「フォルダ名」「ファイル名」「拡張子」に分割してくれる。
各引数は、見ればわかると思うが――
_splitpath_s(元となるパス,
ドライブ名を入れる変数, ドライブ名の最大文字数,
フォルダ名を入れる変数, フォルダ名の最大文字数,
ファイル名を入れる変数, ファイル名の最大文字数,
拡張子を入れる変数, 拡張子の最大文字数)
となる。
ちなみに、必要無いところはnullptrを指定してサイズを0にしてやればよい。
たとえばドライブ名は必要ないのであれば・・・
_splitpath_s("d:/ge2a99/maya/texture/abc.png", nullptr, 0, dir, _MAX_DIR, name, _MAX_FNAME, ext, _MAX_EXT);
といった感じ。
この関数を教えたか忘れたんで、一応書いておく。
【例】
char result[100];
char* text = "ABC";
int value = 20;
wsprintf(result, "%s%dこんにちは", text, value);
例えば↑のように書けば、resultの中身は「ABC20こんにちは」となる。
printfの使い方を知っていれば難しいことはない。
printfが画面に映すのと同じものを第一引数に入れてくれる。
文章や数値をつなげて1つの文字列を作るときに便利な関数。
ということで、上の二つの関数を組み合わせてファイルパスから必要な部分だけ抜き取る。
FbxFileTexture* textureInfo = lProperty.GetSrcObject<FbxFileTexture>(0);
const char* textureFilePath= textureInfo->GetRelativeFileName();
//ファイル名+拡張だけにする
char name[_MAX_FNAME]; //ファイル名
char ext[_MAX_EXT]; //拡張子
_splitpath_s(textureFilePath, nullptr, 0, nullptr, 0, name, _MAX_FNAME, ext, _MAX_EXT);
wsprintf(name, "%s%s", name, ext);
//ファイルからテクスチャ作成
pMaterialList_[i].pTexture = new Texture;
pMaterialList_[i].pTexture->Load(name);
ブレークポイントで止めて、nameに正しい値が入っていることを確認する。
しかし、実行するとまだエラーが出る。
ファイル名からフォルダ名を取ってしまったので、プロジェクトフォルダ直下に画像があればよいが
それ以外のフォルダに入っている場合は見つけられなくなる。
仮にAssetsフォルダにあることが分かっているなら、先ほど取得したファイル名の前に「Assets/」をつければ解決するが、Assetsフォルダ内でさらにフォルダ分けしている可能性もある。
今回FBXファイルと画像ファイルは同じフォルダに入れておくルールにした。
FBXファイルのファイル名は「Assets/test.fbx」のようにフォルダ名付きのファイル名で指定していたはずだ。
そこで、この文字列からフォルダ名を抜き取り、今後そのフォルダのみを見るようにしてしまえば良い。
ファイルを開くとき、基本的にはプロジェクトのフォルダ(〇〇.slnがあるフォルダ)の中を探しに行く。
この場所のことをカレントディレクトリという。
カレントディレクトリを変更するにはSetCurrentDirectory関数を使う。
(例)SetCurrentDirectory("ABC");
例えば上のように書くと、現在のカレントディレクトリの中のABCフォルダがカレントディレクトリとなり、これ以降ファイルを開こうとするとABCフォルダの中を探しに行くようになる。
なお、カレントディレクトリは絶対パスで指定することも可能。
(例)SetCurrentDirectory("D:\\ge2a99\\Model\\Player");
GetCurrentDirectory関数を使うと、その時のカレントディレクトリを調べることができる。
まず、現状のカレントディレクトリを調べておいてから変更。
必要な処理が終わったら戻す。というのがセオリー。
(例)
//現在のカレントディレクトリを取得
char defaultCurrentDir[MAX_PATH];
GetCurrentDirectory(MAX_PATH, defaultCurrentDir);
//カレントディレクトリを変更
SetCurrentDirectory(XXXXX);
:
:
:
//終わったら戻す
SetCurrentDirectory(defaultCurrentDir);
ということで、テクスチャに使う画像はFBXファイルと同じ場所にあるはずだから、以下の手順になるはず。
HRESULT Fbx::Load(std::string fileName)
{
:
:
:
//各情報の個数を取得
vertexCount_ = mesh->GetControlPointsCount();
polygonCount_ = mesh->GetPolygonCount();
materialCount_ = pNode->GetMaterialCount();
//現在のカレントディレクトリを覚えておく
●●●●●●●●●●●●●●●●●●●●●●●●●
●●●●●●●●●●●●●●●●●●●●●●●●●●●
//引数のfileNameからディレクトリ部分を取得
char dir[MAX_PATH];
●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●
//カレントディレクトリ変更
●●●●●●●●●●●●●●●●
InitVertex(mesh);
InitIndex(mesh);
IntConstantBuffer();
InitMaterial(pNode);
//カレントディレクトリを元に戻す
●●●●●●●●●●●●●●●●●●●●●●
//マネージャ解放
pFbxManager->Destroy();
return S_OK;
}
これで、無事にファイルがロードできるはず。
テクスチャを貼るにはUV座標が必要。
struct VERTEX
{
XMVECTOR position;
XMVECTOR uv;
};
//頂点バッファ準備
void Fbx::InitVertex(fbxsdk::FbxMesh * mesh)
{
VERTEX* vertices = new VERTEX[vertexCount_];
//全ポリゴン
for (DWORD poly = 0; poly < polygonCount_; poly++)
{
//3頂点分
for (int vertex = 0; vertex < 3; vertex++)
{
//調べる頂点の番号
int index = mesh->GetPolygonVertex(poly, vertex);
//頂点の位置
FbxVector4 pos = mesh->GetControlPointAt(index);
vertices[index].position = XMVectorSet((float)-pos[0], (float)pos[1], (float)pos[2], 0.0f);
//頂点のUV
FbxLayerElementUV * pUV = mesh->GetLayer(0)->GetUVs();
int uvIndex = mesh->GetTextureUVIndex(poly, vertex, FbxLayerElement::eTextureDiffuse);
FbxVector2 uv = pUV->GetDirectArray().GetAt(uvIndex);
vertices[index].uv = XMVectorSet((float)uv.mData[0], (float)(1.0f - uv.mData[1]), 0.0f, 0.0f);
}
}
V座標は上下を反転させなければいけない。
マテリアルを反映させるとき、ここが一番めんどくさい……。
例えば「青いマテリアルの部分」と「赤いマテリアル部分」があった場合、まずは青い部分を全部表示し、その次に赤い部分を全部表示することになる。
そのために、インデックスバッファをマテリアルごとに分ける必要がある。
インデックスバッファを分けるために配列にする。
すでにポインタなので、ポインタのポインタにする。
ID3D11Buffer *pVertexBuffer_;
ID3D11Buffer **pIndexBuffer_;
ID3D11Buffer *pConstantBuffer_;
MATERIAL* pMaterialList_;
次に、InitIndex内でこをマテリアル分の配列にし、「インデックス情報を取得してバッファに入れる」処理をループにする。
//インデックスバッファ準備
void Fbx::InitIndex(fbxsdk::FbxMesh * mesh)
{
pIndexBuffer_ = new ID3D11Buffer*[materialCount_];
int* index = new int[polygonCount_ * 3];
for (int i = 0; i < materialCount_; i++)
{
int count = 0;
:
:
:
Direct3D::pDevice->CreateBuffer(&bd, &InitData, &pIndexBuffer_[i]);
}
}
poly番目のポリゴンに割り当てられているマテリアルのが何番なのか調べる。
それがi番目であればi番目のインデックス情報として追加していく。
for (int i = 0; i < materialCount_; i++)
{
int count = 0;
//全ポリゴン
for (DWORD poly = 0; poly < polygonCount_; poly++)
{
FbxLayerElementMaterial * mtl = mesh->GetLayer(0)->GetMaterials();
int mtlId = mtl->GetIndexArray().GetAt(poly);
if (mtlId == i)
{
//3頂点分
for (DWORD vertex = 0; vertex < 3; vertex++)
{
index[count] = mesh->GetPolygonVertex(poly, vertex);
count++;
}
}
}
インデックスバッファが複数になったので、描画もループにしなければならない。
インデックスバッファをセットするところと、描画がループに含まれていればいい。
そして、テクスチャを貼ってる場合はテクスチャとサンプラーをシェーダーに渡さなきゃない。
テクスチャを貼ってるかどうかは pMaterialList_[i].pTexture が nullptr かどうかでわかる。
マテリアルごとにテクスチャは変わるので、これもループに入っていなければいけない。
渡し方はQuadクラスを見ればできるでしょう。
では、自分でやってみよう。