FBX SDK を利用したラッパークラス
アニメーションしないものは、適当にサンプルコードをコピペしていればできますが、
アニメーションするものは結構詰まってしまったので、ここにメモさせていただきます。
どのようにクラス化したのかの簡単な経緯と、詰まった部分のメモのみです。
中身を解析したい方はこちらのリンクへ。
GitHub
FBXModel.h
FBXModel.cpp
願わくば、FBX SDKとDirectXを使って描画しようとしている変わり者のあなたへ。
クラス化と中身
ひとつのオブジェクトとして、クラス化して利用したいですよね。
リソース管理をするにも何をするにも、クラス化をしておくと非常に便利です。
#pragma once
namespace FBX_LOADER
{
// <一つの頂点情報を格納する構造体>
struct VERTEX
{
DirectX::XMFLOAT3 Pos;
};
// <GPU(シェーダ側)へ送る数値をまとめた構造体>
struct CONSTANT_BUFFER {
DirectX::XMMATRIX mWVP;
};
class FbxModel
{
// 省略
public:
void Load();
void Draw();
}
}
とりあえず、データのロードと描画は必須ですので、書いておきます。
アニメーションするもの、しないもので分けるのであれば、クラスを二つにするのも手ですが、それをすると何らかの基底クラスを継承させてみたり、なんやかんや管理が面倒になってしまいます。
ですので、今回はデータをロードするときにアニメーションするかどうかを決めるようにしました。
//省略
class FBX_Model
{
//
private:
bool is_animation = false;
public:
void Load(......, bool isAnimation = false);
}
デフォルト引数を付けるかどうかは好みですね。
ただ、この方法で分ける場合、メンバ変数でアニメーションするかどうかを覚えておく必要があります。
様々なサイトを巡ったのですが、これだ!というものが無く、迷走しながら作ったので見るに堪えないかもしれませんが、ご了承ください。
メッシュデータの取り扱い
モデルを描画するには、メッシュデータに格納されているポリゴンの数だとか頂点のデータだとか、まあ色々なデータが必要になるわけです。
大抵のサイトに書いてあったことは、簡単に言うと...。
まず、親ノードがあります。
[RootNode]
これにぶら下がって、メッシュやらボーンやらがくっついています。
[RootNode]
┣[Mesh]
┗[Bone]
実際はもっと複雑かもしれないですが、大方こんなものです。
さて、これをFbxNodeクラスに用意されているGetChild関数で辿っていこう!
メッシュかどうかは、Attribute?とかいうそれっぽいやつがあるので、それでチェックしましょう!
と、書いてあるサイトが多かったです。
正直、これをやると処理が重くなりましたし、再帰処理に慣れていない方はこんがらがってしまうと思います。
もちろん、この方法でもできなくはないですが、ちょっとツライです。
再帰処理を使ってノードを辿っていかなくても、なんとかなる方法がありました。
void Load(......)
{
//......
int meshCount = m_fbxScene->GetSrcObjectCount<FbxMesh>();
for (int i = 0; i < meshCount; ++i)
{
// <たったこれだけで全てのメッシュデータを取得できる>
FbxMesh* mesh = m_fbxScene->GetSrcObject<FbxMesh>(i);
std::string name = mesh->GetName();
//m_fbxMeshNames.push_back(name);
//m_fbxMeshes.insert({ mesh, name });
m_fbxMeshes.push_back(mesh);
}
//......
}
GetSrcObject関数で、FbxMeshを指定すると、Scene内のメッシュデータを取り出すことができます。
Meshの名前を取得したりしているのは、ちゃんと取れているのかチェックしていたからなので、気にしないでください。
これで、メッシュだけをvectorなどでまとめておけば、毎回サーチしなくていいので大幅に処理が軽くなりました。(体感)(あいまいな記憶)
ここで取得したメッシュデータを、描画のときに使います。
面倒な描画
void Draw(......)
{
if (is_animation)
{
timeCount += FrameTime;
if (timeCount > stop)
timeCount = start;
this->DrawAnimation(m_fbxScene->GetRootNode(), world, view, proj);
}
else
{
this->DrawModel(m_fbxScene->GetRootNode(), world, view, proj);
}
}
今回はとりあえず、privateで描画の関数を分けました。
他にも良い方法があるかもしれません。
アニメーションしないやつは、おそらくできると思うのでアニメーションするやつだけ。
アニメーションするやつは、まずアニメーションしないモデル、と言ってわかるでしょうか。
人型のモデルだと、両腕を真横にまっすぐ伸ばして立っている、ああいうやつです。
あの状態を基本とし、アニメーションデータ(ボーンの動き)を取り出し、それを各ポリゴン(頂点)と計算する必要があります。
面倒ですね。
とりあえず、先ほど回収しておいたメッシュデータを使います。
{
int Count = m_fbxMeshes.size(); // メッシュの数だけ行う必要がある
for (int i = 0; i < Count; i++)
{
FbxMesh* mesh = m_fbxMeshes[i];
VERTEX* vertices = new VERTEX[mesh->GetControlPointsCount()];
}
}
この下はバッファを生成したり設定したりが並ぶので、最初のほうにあるリンクでも見て適当に書いてください。
さて、ボーンのデータからの計算です。
{
// <各頂点に掛けるための最終的な行列の配列>
FbxMatrix *clusterDeformation = new FbxMatrix[mesh->GetControlPointsCount()];
memset(clusterDeformation, 0, sizeof(FbxMatrix) * mesh->GetControlPointsCount());
FbxSkin *skinDeformer = (FbxSkin *)mesh->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(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;
}
}
// <最終的な頂点座標を計算しVERTEXに変換>
int count = mesh->GetControlPointsCount();
for (int cnt = 0; cnt < count; cnt++) {
FbxVector4 outVertex = clusterDeformation[cnt].MultNormalize(mesh->GetControlPointAt(cnt));
float x = (FLOAT)outVertex[0];
float y = (FLOAT)outVertex[1];
float z = (FLOAT)outVertex[2];
vertices[cnt].Pos.x = x;
vertices[cnt].Pos.y = y;
vertices[cnt].Pos.z = z;
}
delete[] clusterDeformation;
}
この部分を作ったのは結構前なので、全くどうなっているのか記憶にないので説明できないですが、頑張って読んでください。
読まなくても、コピペ等で使えます。
そしてまた、バッファをいじったりなんやかんやします。
最終的に、
{
// <描画実行>
int polygonVertexCount = mesh->GetPolygonVertexCount();
m_context->DrawIndexed(polygonVertexCount, 0, 0);
}
これで描画して終わり!
なお描画も、メッシュの数だけ行う必要があります。
ですので、あまりにポリゴンが多かったり、メッシュが多いモデルは大変です。
メッシュが多いモデルはあまりないでしょうが。
ここまでで、どこが詰まったのかと言いますと。
・メッシュデータを毎回ノードを辿って探していた
・毎回探していたので、複雑化しすぎてわからなくなっていた
・描画のためのバッファの用意し忘れ
・そもそもモデルにアニメーションがついてなかった
などですね。
最後の二つは完全にミスですが、皆さんも気を付けましょう!
終わりに
役に立つかどうかは分かりませんが、我が後輩達よ、こんなクソコード書くんじゃないぞ。
リファクタリングしようかとも思いましたが、動くところまで漕ぎつけるのに3回作り直してますからね、気力が持ちませんでした。
誰かリファクタリングしたい人は、してどうぞ。
クソゴミコードなので、コピペして使ったりするのは構いませんが、せめて自分のコードに書き直すくらいはしてね!バレちゃうぞ!