#はじめに
DirectX9でFBX読み込みてーなって思ったのでその備忘録としてメモしておきます。なんで今どきDirectX9なのかというと学校の課題だからです。ゲームエンジンのありがたみがとってもわかりました。あといろいろわからないところがあるので知っている方が通りがかったら教えてくれると嬉しくて夜に寝られるようになります。
FBXSDKの導入の仕方はいくらでもあるのでそれは飛ばします。
#構造体の定義
今回出てくる構造体たちです。
//頂点の情報
struct VERTEX_3D {
D3DXVECTOR3 pos;
D3DXVECTOR3 nor;
D3DCOLOR col;
D3DXVECTOR2 tex;
};
//メッシュの情報
struct MeshInfo {
//ポリゴンの数
int polygonCount;
//頂点の数
int vertexCount;
//頂点インデックスの数
int indexCount;
//頂点
VERTEX_3D* vertex;
//頂点インデックスの順番
int* indexBuffer;
//UVSetの数
int uvSetCount;
//UVSetの名前
std::string* uvSetName;
//頂点バッファ
LPDIRECT3DVERTEXBUFFER9 pVB;
//インデックスバッファ
LPDIRECT3DINDEXBUFFER9 pIB;
//テクスチャ
LPDIRECT3DTEXTURE9* texture;
//テクスチャパス
std::vector<std::string> texturePath;
};
//FBXの情報
struct FbxInfo {
//メッシュ
std::vector<FbxMesh*> meshes;
//メッシュの数
int meshCount;
//マテリアル
std::vector<FbxSurfaceMaterial*> material;
//マテリアルの数
int materialCount;
//UVSetの数
int uvSetCount;
//UVSetの名前
std::string* uvSetName;
};
#はじめにやること
FBXのマネージャーを作ります。そのあとインポーターを作ります。そのあとシーンを作ります。
この処理は初期化とか読み込みとかの時にやるのがいいと思います。僕は2つに分けました。
//FBXのマネージャー作成
m_manager = FbxManager::Create();
if (NULL != m_manager) {
//インポーター作成
FbxImporter* importer = FbxImporter::Create(m_manager, "");
if (NULL != importer) {
//シーン作成
m_scene = FbxScene::Create(m_manager, "");
if (NULL != m_scene) {
//filePathに指定したファイルを読み込む
bool result = importer->Initialize(filePath);
if (result) {
//シーンにインポートしたファイルを渡す
importer->Import(m_scene);
}
}
}
//インポーターの役目は終わりなので解放する
importer->Destroy();
}
#各情報を取得する前のしたごしらえ
あとは頂点情報やらなんやらを取得するだけですがその前に準備をします。モデルに三角ポリゴン以外があると困ったことになるので、ポリゴンを三角にします。あと単位もよくわからなくなってると困るので自分で指定したものにします。
//シーンのポリゴンを三角にする
FbxGeometryConverter geometryConverter(m_manager);
geometryConverter.Triangulate(m_scene, true);
FbxSystemUnit SceneSystemUnit = m_scene->GetGlobalSettings().GetSystemUnit();
if (SceneSystemUnit.GetScaleFactor() != 1.0f)
{
// シーンの単位をcmに設定する
FbxSystemUnit::cm.ConvertScene(m_scene);
}
#メッシュの取得
ようやく情報の取得が始まります。まずはメッシュを取得します。ほとんどの場合、1つのモデルには複数のメッシュがあると思うのでいい感じに取得します。今回はvectorで管理しました。ネットの情報を見るとノードをたどっていく方法もありますが、とっても簡単に取得できる方法もあるみたいなのでそれでやりました。
std::vector<FbxMesh*> Fbx::GetMesh() {
//メッシュの数を取得
m_fbxInfo.meshCount = m_scene->GetSrcObjectCount<FbxMesh>();
std::vector<FbxMesh*> meshes;
for (int i = 0; i < m_fbxInfo.meshCount; ++i)
{
//i番目のメッシュを取得
FbxMesh* mesh = m_scene->GetSrcObject<FbxMesh>(i);
meshes.emplace_back(mesh);
}
return meshes;
}
#頂点座標の取得
次に頂点座標を取得します。多分あってます。
void Fbx::GetVertex(int meshIndex, VERTEX_3D* vertex) {
//メッシュに含まれる頂点座標を取得
FbxVector4* vtx = m_fbxInfo.meshes[meshIndex]->GetControlPoints();
for (int vIdx = 0; vIdx < m_meshInfo[meshIndex].vertexCount; vIdx++)
{
vertex[vIdx].pos.x = (float)vtx[vIdx][0];
vertex[vIdx].pos.y = (float)vtx[vIdx][1];
vertex[vIdx].pos.z = (float)vtx[vIdx][2];
}
}
#頂点インデックスの取得
1行で済むのでとっても簡単でした。
m_meshInfo[meshIndex].indexBuffer = m_fbxInfo.meshes[meshIndex]->GetPolygonVertices();
#法線の取得
こちらも結構簡単です。
void Fbx::GetNormal(int meshIndex, VERTEX_3D* vertex) {
FbxArray<FbxVector4> normals;
//法線を取得
m_fbxInfo.meshes[meshIndex]->GetPolygonVertexNormals(normals);
//法線の数を取得
int normalCount = normals.Size();
for (int i = 0; i < normalCount; i++)
{
//頂点インデックスに対応した頂点に値を代入
vertex[m_meshInfo[meshIndex].indexBuffer[i]].nor.x = normals[i][0];
vertex[m_meshInfo[meshIndex].indexBuffer[i]].nor.y = normals[i][1];
vertex[m_meshInfo[meshIndex].indexBuffer[i]].nor.z = normals[i][2];
}
}
#UVSetの名前取得
ここら辺からよくわからなくなってきたので大変でした。今もよくわかっていないので大変ですが。UVSetが何のためにあるかを簡単に説明します。FBXではメッシュがUVSet名を、マテリアルがUVSet名とテクスチャ名を持っています。UVSet名をテクスチャと関連付けて使うことでテクスチャの貼り貼りを管理しています。よくわかんない人は調べてください。
void Fbx::GetUVSetName(int meshIndex) {
FbxStringList uvsetName;
//メッシュに含まれるUVSet名をすべて取得
m_fbxInfo.meshes[meshIndex]->GetUVSetNames(uvsetName);
//UVSetの数を取得
m_meshInfo[meshIndex].uvSetCount = m_fbxInfo.meshes[meshIndex]->GetUVLayerCount();
/*テクスチャ関係のメモリ確保*/
TextureMemoryAllocate(meshIndex);
for (int i = 0; i < m_meshInfo[meshIndex].uvSetCount; i++)
{
//UVSet名を取得
m_meshInfo[meshIndex].uvSetName[i] = uvsetName[i];
}
}
void Fbx::TextureMemoryAllocate(int meshIndex) {
m_meshInfo[meshIndex].uvSetName = new std::string[m_meshInfo[meshIndex].uvSetCount];
m_meshInfo[meshIndex].texturePath.reserve(m_meshInfo[meshIndex].uvSetCount);
m_meshInfo[meshIndex].texture = new LPDIRECT3DTEXTURE9[m_meshInfo[meshIndex].uvSetCount];
}
#UV座標の取得
こちらですがちゃんと検証できていないので正しいかわかりません()。多分あっているとは思うのですが間違っていたら教えてくださいませ。
void Fbx::GetUV(int meshIndex) {
FbxArray<FbxVector2> uvsets;
FbxStringList uvsetName;
m_fbxInfo.meshes[meshIndex]->GetUVSetNames(uvsetName);
//UVを取得
m_fbxInfo.meshes[meshIndex]->GetPolygonVertexUVs(uvsetName.GetStringAt(0), uvsets);
//UVの数を取得
int uvsetCount = uvsets.Size();
for (int i = 0; i < uvsetCount; i++)
{
//頂点インデックスに対応した頂点に値を代入
m_meshInfo[meshIndex].vertex[m_meshInfo[meshIndex].indexBuffer[i]].tex.x = uvsets[i][0];
m_meshInfo[meshIndex].vertex[m_meshInfo[meshIndex].indexBuffer[i]].tex.y = uvsets[i][1];
}
}
#マテリアルの取得
シーンに含まれているマテリアルをすべて取得します。先に述べたようにテクスチャ関係で利用します。また頂点カラーや環境光等の情報も持っているので必要に応じて取得しましょう。今回は割愛します。
void Fbx::GetMaterial() {
//マテリアルの数を取得
m_fbxInfo.materialCount = m_scene->GetMaterialCount();
m_fbxInfo.material.reserve(m_fbxInfo.materialCount);
for (int i = 0; i < m_fbxInfo.materialCount; i++)
{
//マテリアルを取得
m_fbxInfo.material.emplace_back(m_scene->GetMaterial(i));
}
}
#テクスチャ情報の取得
こちらのほぼコピーです。ここが1番詰まりました。なにに詰まったかというとメッシュのUVSet名とマテルあるが持っているテクスチャのUVSet名が一致しないということです。blenderでFBXを作成するとメッシュのUVSet名はUVMapだけどテクスチャのUVSet名はdefaultになっているようです。解決方法を調べに調べまくった結果、MayaのUVエディターで名前を変えることにしました。先人たちはどうしていたのかとっても気になります。それ以外はいたって単純です。
void Fbx::GetTextureInfo(int meshIndex) {
int uvIndex = 0;
for (int matIndex = 0; matIndex < m_fbxInfo.materialCount; matIndex++)
{
//diffuseの情報を取得
FbxProperty prop = m_fbxInfo.material[matIndex]->FindProperty(FbxSurfaceMaterial::sDiffuse);
//レイヤテクスチャの数を取得する
int layeredTextureCount = prop.GetSrcObjectCount<FbxLayeredTexture>();
//レイヤテクスチャを利用している場合
if (0 < layeredTextureCount)
{
for (int j = 0; layeredTextureCount > j; j++) {
//レイヤテクスチャを取得する
FbxLayeredTexture* layeredTexture = prop.GetSrcObject<FbxLayeredTexture>(j);
//テクスチャの数を取得する
int textureCount = layeredTexture->GetSrcObjectCount<FbxFileTexture>();
for (int k = 0; textureCount > k; k++)
{
//テクスチャを取得する
FbxFileTexture* texture = prop.GetSrcObject<FbxFileTexture>(k);
if (texture)
{
//テクスチャ名を取得する
std::string textureName = texture->GetRelativeFileName();
//UVSet名を取得する
std::string UVSetName = texture->UVSet.Get().Buffer();
//UVSet名を比較し対応しているテクスチャなら保持する
for (int i = 0; i < m_meshInfo[meshIndex].uvSetCount; i++)
{
if (m_meshInfo[meshIndex].uvSetName[i] == UVSetName) {
//ちゃんと設定していないのでファイルまでのパスを追加しています
std::string a = "Models/test/";
m_meshInfo[meshIndex].texturePath.emplace_back(textureName + a);
//テクスチャのUVSet名を取得する
m_fbxInfo.uvSetName[uvIndex] = UVSetName;
uvIndex++;
}
}
}
}
}
}
//レイヤテクスチャを利用していない場合
else {
//テクスチャ数を取得する
int fileTextureCount = prop.GetSrcObjectCount<FbxFileTexture>();
if (0 < fileTextureCount)
{
for (int j = 0; fileTextureCount > j; j++)
{
//テクスチャを取得する
FbxFileTexture* texture = prop.GetSrcObject<FbxFileTexture>(j);
if (texture)
{
//テクスチャ名を取得する
std::string textureName = texture->GetRelativeFileName();
//UVSet名を取得する
std::string UVSetName = texture->UVSet.Get().Buffer();
//UVSet名を比較し対応しているテクスチャなら保持する
for (int i = 0; i < m_meshInfo[meshIndex].uvSetCount; i++)
{
if (m_meshInfo[meshIndex].uvSetName[i] == UVSetName)
{
std::string a = "Models/test/";
m_meshInfo[meshIndex].texturePath.emplace_back(textureName + a);
m_fbxInfo.uvSetName[uvIndex] = UVSetName;
uvIndex++;
}
}
}
}
}
}
}
//UVSetの数を取得する
m_fbxInfo.uvSetCount = uvIndex;
}
#モデルの表示(アニメーションなしver)
ようやく表示できます。アニメーションなしは特にいうことはないです。
void Fbx::DrawModel() {
D3DXMATRIX t, r, s, mat;
D3DXMatrixIdentity(&t);
D3DXMatrixIdentity(&r);
D3DXMatrixIdentity(&s);
D3DXMatrixIdentity(&mat);
D3DXMatrixTranslation(&t, m_position.x, m_position.y, m_position.z);
D3DXMatrixRotationYawPitchRoll(&r, m_rotation.y, m_rotation.x, m_rotation.z);
D3DXMatrixScaling(&s, m_scale.x, m_scale.y, m_scale.z);
mat = s * r * t;
m_pDevice->SetTransform(D3DTS_WORLD, &mat);
for (int i = 0; i < m_fbxInfo.meshCount; i++)
{
m_pDevice->SetStreamSource(0, m_meshInfo[i].pVB, 0, sizeof(VERTEX_3D));
m_pDevice->SetIndices(m_meshInfo[i].pIB);
SetTexture(i);
m_pDevice->SetFVF(FVF_VERTEX_3D);//D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1
m_pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_meshInfo[i].indexCount, 0, m_meshInfo[i].polygonCount);
}
}
#モデルの表示(アニメーションありver)
アニメーションありの場合です。正直何やってるいるかわからないので時間があるときにしっかり見てみたいと思います。今すぐ知りたい人は調べてください。あとなんかスケールと回転がなんかダメなので何かがダメなのですがわかりません泣。コピペなんかしないで全部自分で調べて書けってことなんでしょうね。
void Fbx::DrawAnimation()
{
//アニメーションの時間更新
m_timeCount += m_frameTime;
if (m_timeCount > m_stop)
{
m_timeCount = m_start;
}
FbxNode* pNode = m_scene->GetRootNode();
FbxMatrix globalPosition = pNode->EvaluateGlobalTransform(m_timeCount);
FbxVector4 t0 = pNode->GetGeometricTranslation(FbxNode::eSourcePivot);
FbxVector4 r0 = pNode->GetGeometricRotation(FbxNode::eSourcePivot);
FbxVector4 s0 = pNode->GetGeometricScaling(FbxNode::eSourcePivot);
FbxAMatrix geometryOffset = FbxAMatrix(t0, r0, s0);
for (int i = 0; i < m_fbxInfo.meshCount; i++)
{
FbxMatrix *clusterDeformation = new FbxMatrix[m_fbxInfo.meshes[i]->GetControlPointsCount()];
memset(clusterDeformation, 0, sizeof(FbxMatrix) * m_fbxInfo.meshes[i]->GetControlPointsCount());
FbxSkin *skinDeformer = (FbxSkin *)m_fbxInfo.meshes[i]->GetDeformer(0, FbxDeformer::eSkin);
int clusterCount = skinDeformer->GetClusterCount();
for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
FbxCluster *cluster = skinDeformer->GetCluster(clusterIndex);
FbxMatrix vertexTransformMatrix;
FbxAMatrix referenceGlobalInitPosition;
FbxAMatrix clusterGlobalInitPosition;
FbxMatrix clusterGlobalCurrentPosition;
FbxMatrix clusterRelativeInitPosition;
FbxMatrix clusterRelativeCurrentPositionInverse;
cluster->GetTransformMatrix(referenceGlobalInitPosition);
referenceGlobalInitPosition *= geometryOffset;
cluster->GetTransformLinkMatrix(clusterGlobalInitPosition);
clusterGlobalCurrentPosition = cluster->GetLink()->EvaluateGlobalTransform(m_timeCount);
clusterRelativeInitPosition = clusterGlobalInitPosition.Inverse() * referenceGlobalInitPosition;
clusterRelativeCurrentPositionInverse = globalPosition.Inverse() * clusterGlobalCurrentPosition;
vertexTransformMatrix = clusterRelativeCurrentPositionInverse * clusterRelativeInitPosition;
for (int cnt = 0; cnt < cluster->GetControlPointIndicesCount(); cnt++) {
int index = cluster->GetControlPointIndices()[cnt];
double weight = cluster->GetControlPointWeights()[cnt];
FbxMatrix influence = vertexTransformMatrix * weight;
clusterDeformation[index] += influence;
}
}
for (int cnt = 0; cnt < m_meshInfo[i].vertexCount; cnt++) {
FbxVector4 outVertex = clusterDeformation[cnt].MultNormalize(m_fbxInfo.meshes[i]->GetControlPointAt(cnt));
float x = (FLOAT)outVertex[0];
float y = (FLOAT)outVertex[1];
float z = (FLOAT)outVertex[2];
m_meshInfo[i].vertex[cnt].pos.x = x;
m_meshInfo[i].vertex[cnt].pos.y = y;
m_meshInfo[i].vertex[cnt].pos.z = z;
}
delete[] clusterDeformation;
SetVertexBuffer(i);
D3DXMATRIX t, r, s, mat;
D3DXMatrixIdentity(&t);
D3DXMatrixIdentity(&r);
D3DXMatrixIdentity(&s);
D3DXMatrixIdentity(&mat);
D3DXMatrixTranslation(&t, m_position.x, m_position.y, m_position.z);
D3DXMatrixRotationYawPitchRoll(&r, m_rotation.y, m_rotation.x, m_rotation.z);
D3DXMatrixScaling(&s, m_scale.x * 0.1724140408247713f, m_scale.y* 0.1724140408247713f, m_scale.z* 0.1724140408247713f);
mat = t * r * s;
m_pDevice->SetTransform(D3DTS_WORLD, &mat);
m_pDevice->SetStreamSource(0, m_meshInfo[i].pVB, 0, sizeof(VERTEX_3D));
m_pDevice->SetIndices(m_meshInfo[i].pIB);
SetTexture(i);
m_pDevice->SetFVF(FVF_VERTEX_3D);
m_pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, m_meshInfo[i].indexCount, 0, m_meshInfo[i].polygonCount);
}
}
void Fbx::SetVertexBuffer(int meshIndex) {
VERTEX_3D* pVtx;
m_meshInfo[meshIndex].pVB->Lock(0, 0, (void**)&pVtx, 0);
for (int i = 0; i < m_meshInfo[meshIndex].vertexCount; i++)
{
pVtx[i].pos = m_meshInfo[meshIndex].vertex[i].pos;
pVtx[i].nor = m_meshInfo[meshIndex].vertex[i].nor;
pVtx[i].col = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);
pVtx[i].tex = m_meshInfo[meshIndex].vertex[i].tex;
}
m_meshInfo[meshIndex].pVB->Unlock();
}
#おわりに
グダグダですが以上で大体のことは終わりです。ゲームエンジンを使ってゲームを作りたいです。