7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【DirectX】FBX SDKのラッパークラス【FBX SDK】

Posted at

FBX SDK を利用したラッパークラス

アニメーションしないものは、適当にサンプルコードをコピペしていればできますが、
アニメーションするものは結構詰まってしまったので、ここにメモさせていただきます。

どのようにクラス化したのかの簡単な経緯と、詰まった部分のメモのみです。
中身を解析したい方はこちらのリンクへ。

GitHub
FBXModel.h
FBXModel.cpp

願わくば、FBX SDKとDirectXを使って描画しようとしている変わり者のあなたへ。

クラス化と中身

ひとつのオブジェクトとして、クラス化して利用したいですよね。
リソース管理をするにも何をするにも、クラス化をしておくと非常に便利です。

FBX_Model.h
#pragma once

namespace FBX_LOADER
{
    // <一つの頂点情報を格納する構造体>
    struct VERTEX 
    {
        DirectX::XMFLOAT3 Pos;
    };

    // <GPU(シェーダ側)へ送る数値をまとめた構造体>
    struct CONSTANT_BUFFER {
        DirectX::XMMATRIX mWVP;
    };

    class FbxModel
    {
         // 省略
    public:
        void Load();
        void Draw();
    }
}

とりあえず、データのロードと描画は必須ですので、書いておきます。
アニメーションするもの、しないもので分けるのであれば、クラスを二つにするのも手ですが、それをすると何らかの基底クラスを継承させてみたり、なんやかんや管理が面倒になってしまいます。
ですので、今回はデータをロードするときにアニメーションするかどうかを決めるようにしました。

FBX_Model.h
//省略
class FBX_Model
{
     //
private:
     bool is_animation = false;
public:
     void Load(......, bool isAnimation = false);
}

デフォルト引数を付けるかどうかは好みですね。
ただ、この方法で分ける場合、メンバ変数でアニメーションするかどうかを覚えておく必要があります。

様々なサイトを巡ったのですが、これだ!というものが無く、迷走しながら作ったので見るに堪えないかもしれませんが、ご了承ください。

メッシュデータの取り扱い

モデルを描画するには、メッシュデータに格納されているポリゴンの数だとか頂点のデータだとか、まあ色々なデータが必要になるわけです。
大抵のサイトに書いてあったことは、簡単に言うと...。

まず、親ノードがあります。
[RootNode]

これにぶら下がって、メッシュやらボーンやらがくっついています。
[RootNode]
   ┣[Mesh]
   ┗[Bone]

実際はもっと複雑かもしれないですが、大方こんなものです。

さて、これをFbxNodeクラスに用意されているGetChild関数で辿っていこう!
メッシュかどうかは、Attribute?とかいうそれっぽいやつがあるので、それでチェックしましょう!

と、書いてあるサイトが多かったです。
正直、これをやると処理が重くなりましたし、再帰処理に慣れていない方はこんがらがってしまうと思います。

もちろん、この方法でもできなくはないですが、ちょっとツライです。

再帰処理を使ってノードを辿っていかなくても、なんとかなる方法がありました。

Sample.cpp
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などでまとめておけば、毎回サーチしなくていいので大幅に処理が軽くなりました。(体感)(あいまいな記憶)

ここで取得したメッシュデータを、描画のときに使います。

面倒な描画

Sample.cpp
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で描画の関数を分けました。
他にも良い方法があるかもしれません。

アニメーションしないやつは、おそらくできると思うのでアニメーションするやつだけ。
アニメーションするやつは、まずアニメーションしないモデル、と言ってわかるでしょうか。
人型のモデルだと、両腕を真横にまっすぐ伸ばして立っている、ああいうやつです。

あの状態を基本とし、アニメーションデータ(ボーンの動き)を取り出し、それを各ポリゴン(頂点)と計算する必要があります。
面倒ですね。

とりあえず、先ほど回収しておいたメッシュデータを使います。

Sample.cpp
{
     int Count = m_fbxMeshes.size(); // メッシュの数だけ行う必要がある
     for (int i = 0; i < Count; i++)
     {
          FbxMesh* mesh = m_fbxMeshes[i];
          VERTEX* vertices = new VERTEX[mesh->GetControlPointsCount()];
     }
}

この下はバッファを生成したり設定したりが並ぶので、最初のほうにあるリンクでも見て適当に書いてください。

さて、ボーンのデータからの計算です。

Sample.cpp
{
// <各頂点に掛けるための最終的な行列の配列>
		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;
}

この部分を作ったのは結構前なので、全くどうなっているのか記憶にないので説明できないですが、頑張って読んでください。
読まなくても、コピペ等で使えます。

そしてまた、バッファをいじったりなんやかんやします。

最終的に、

Sample.cpp
{
// <描画実行>
		int polygonVertexCount = mesh->GetPolygonVertexCount();
		m_context->DrawIndexed(polygonVertexCount, 0, 0);
}

これで描画して終わり!

なお描画も、メッシュの数だけ行う必要があります。

ですので、あまりにポリゴンが多かったり、メッシュが多いモデルは大変です。
メッシュが多いモデルはあまりないでしょうが。

ここまでで、どこが詰まったのかと言いますと。

・メッシュデータを毎回ノードを辿って探していた
・毎回探していたので、複雑化しすぎてわからなくなっていた
・描画のためのバッファの用意し忘れ
・そもそもモデルにアニメーションがついてなかった

などですね。
最後の二つは完全にミスですが、皆さんも気を付けましょう!

終わりに

役に立つかどうかは分かりませんが、我が後輩達よ、こんなクソコード書くんじゃないぞ。
リファクタリングしようかとも思いましたが、動くところまで漕ぎつけるのに3回作り直してますからね、気力が持ちませんでした。
誰かリファクタリングしたい人は、してどうぞ。

クソゴミコードなので、コピペして使ったりするのは構いませんが、せめて自分のコードに書き直すくらいはしてね!バレちゃうぞ!

7
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?