😽

FBX SDKでアニメーションを書き出してみる

2022/12/11に公開

はじめに

こんにちは。Kaibaです。今回は表題の通り、AutoDeskが提供しているC++版FBX SDKを使ってFBXファイルを読み込み、アニメーションを書き出す方法について纏めます。FBXファイルを読み込んだり書き出したりする部分は結構情報があるんですが、アニメーション周りに関しては全くと言っていいほど情報が出なかったのでここに書き留めておこうと思います。

環境構築

環境構築に関しては以下のブログなどを参考にやってみてください。

https://bluebirdofoz.hatenablog.com/entry/2018/04/07/093846

流れ

今回は以下の手順の処理を順に書いていきます。

  1. FBXファイルを読み込む
  2. アニメーションを書き込む
  3. FBXファイルを出力する

FBXファイルを読み込む

まずはFBXファイルを読み込みます。
Import(FbxScene*)は返り値として成功/失敗のboolを返します。読み込み失敗時の処理を書きたいときはこれを使いましょう。


	//FbxManagerとFbxSceneオブジェクトを作成
	FbxManager* manager = FbxManager::Create();
	FbxIOSettings* ios = FbxIOSettings::Create(manager, IOSROOT);
	manager->SetIOSettings(ios);
	FbxScene* scene = FbxScene::Create(manager, "");

	//データを読み込み、FbxSceneに格納
	const char* filename = "Path/to/FBX file";
	FbxImporter* importer = FbxImporter::Create(manager, "Importer");
	importer->Initialize(filename, -1, manager->GetIOSettings());
	importer->Import(scene);
	importer->Destroy();

アニメーションを書き込む準備をする

さて、本題のアニメーションを書き込む方法ですが、FBXのアニメーション情報は結構厄介や構造をしています。

AnimStack

FbxSceneはAnimStackの配列を持っています。AnimStackシーン全体のアニメーションのデータを保存しています。 まずは読み込んだFBXファイルのAnimStackを読み込んでみます。

FbxArray<FbxString*> AnimStackNameArray;
scene->FillAnimStackNameArray(AnimStackNameArray);

特定のAnimStackを取得したいときはFindMemberを使って名前で検索できます。

AnimationStack = scene->FindMember<FbxAnimStack>("AnimStack名");

また、AnimStackを新たに作成する場合は以下のように作成できます。CreateAnimStackの返り値は正常に作成できたかどうかのboolです。引数のFbxStatusは失敗時の原因などを示す列挙型です。

FbxStatus* status=NULL;
if (scene->CreateAnimStack("AnimStack名", status))
{
    //作成成功時の処理
}

AnimLayer

AnimStackを新たに作成した場合は、AnimLayerを作成する必要があります。また、Animationの長さ、説明はそれぞれLocalStart/LocalStopDescriptionで設定できます。


//AnimStack initialize

FbxTime Duration;
Duration.SetSecondDouble(1); //1secのAnimation
AnimationStack->LocalStart = 0;
AnimationStack->LocalStop = Duration;
FbxAnimLayer* AnimLayer = FbxAnimLayer::Create(scene, "Base Layer");
AnimationStack->AddMember(AnimLayer);
AnimationStack->Description = "Animation made by Kaiba.";
scene->SetCurrentAnimationStack(AnimationStack);

メッシュ、スキン及びボーンの取得

さて、ここまででアニメーションを書き込む準備は完了しました。次に実際にオブジェクト(メッシュやボーン)にアニメーションを書き込んでいきましょう。
まずはシーンに存在するメッシュを取得してみましょう。

以下のコードでシーンに存在する全てのメッシュFbxMeshを取得できます。

int meshCount = scene->GetSrcObjectCount<FbxMesh>();

for (int i = 0; i < meshCount; ++i)
{
    FbxMesh* mesh = scene->GetSrcObject<FbxMesh>(i);
}

次に、メッシュのスキンに対して設定されているボーンを取得してみます。FbxではスキンはFbxSkin、ボーンはFbxClusterとして保存されています。
メッシュはスキンと、スキンはボーンと結びついています。


FbxMesh* mesh;
FbxArray<FbxCluster*> clusters;

int skinCount = mesh->GetDeformerCount(FbxDeformer::eSkin);

for (int i = 0; i < skinCount; i++) {

    FbxSkin* skin = (FbxSkin*)mesh->GetDeformer(i, FbxDeformer::eSkin);
    int clusterNum = skin->GetClusterCount();

    for (int j = 0; j < clusterNum; j++) {
        clusters.Add(skin->GetCluster(j));
    }
}

ボーンに対してアニメーションを書き込む

さて、ついにここまで来ました。ここが一番厄介です。自分も公式のドキュメントを読み込んでなんとか理解できましたが、結構ややこしい構造をしています。

FbxAnimCurveNodeの作成

まずはアニメーションさせたいプロパティ(FbxProperty)用のFbxAnimCurveNodeを作ります。一つのボーン、アニメーションさせたいFbxNodeFbxPropertyごとに一つのFbxAnimCurveNodeが必要です。また、FbxAnimCurveNodeにはタイプ(型)があります。タイプはPropertyに合わせてください。FbxDoubleFbxDouble3などになります。

FbxAnimLayer* AnimLayer;
FbxCluster* cluster;

//AnimLayerに接続したCurveNodeを作成
//LocalTranslation用AnimCurveNodeを作成
//FbxDouble3でTypedCurveを作成するとCurveNodeにはXYZに対応した3つのチャンネルが作成される。
FbxAnimCurveNode* trans_node =FbxAnimCurveNode::CreateTypedCurveNode(cluster->GetLink()->LclTranslation,AnimLayer->GetScene());

//AnimLayerに追加
AnimLayer->AddMember(trans_node);

//clusterのLocalTranslationに接続してアニメーションの適用対象を指定
cluster->GetLink()->LclTranslation.ConnectSrcObject(trans_node);

位置、回転、スケールの三つのPropertyに対してアニメーションを書き込む場合は三つのFbxDouble3型のFbxAnimCurveNodeが必要になります。

FbxCurveNodeの接続

さて、FbxAnimCurveNodeFbxClusterなどに結びつきアニメーションのチャンネルを提供しますが、アニメーションの値は保持してくれません。アニメーションの値はFbxAnimCurveが保持します。まずは各値毎にFbxAnimCurveを作成します。
また、FbxAnimCurveはキーフレームとしてFbxTimeFbxDoubleを保持します。そのため、LocalTranslationの場合はFbxDouble3の三つのFbxAnimCurveが必要です。

FbxAnimCurve* x_curve = FbxAnimCurve::Create(AnimLayer->GetScene(),"transX_Curve");
FbxAnimCurve* y_curve = FbxAnimCurve::Create(AnimLayer->GetScene(), "transY_Curve");
FbxAnimCurve* z_curve = FbxAnimCurve::Create(AnimLayer->GetScene(), "transZ_Curve");
//AnimCurveNodeのチャンネルと接続
trans_node->ConnectToChannel(x_curve, 0);
trans_node->ConnectToChannel(y_curve, 1);
trans_node->ConnectToChannel(z_curve, 2);

アニメーションの書き込み

さて、ついにアニメーションの書き込みです。作成した各FbxAnimCurveに対してFbxAnimCurveKeyを追加していきます。

今回は、TranslationのXのCurveに対して書き込みます。
1秒間でX座標を0から1へと線形に変化させるよう、キーフレームを打ち込んでみます。


FbxAnimCurveKey key;
FbxTime Time;

x_curve->KeyModifyBegin();

//0秒時点
Time.SetSecondDouble(0);
//1秒時点でX座標を0に
key.Set(Time, 0);
int KeyIndex = x_curve->KeyAdd(Time, key);
x_curve->KeySetInterpolation(KeyIndex, FbxAnimCurveDef::eInterpolationLinear);


//1秒時点
Time.SetSecondDouble(1);
//1秒時点でX座標を1に
key.Set(Time, 1);
int KeyIndex = x_curve->KeyAdd(Time, key);
x_curve->KeySetInterpolation(KeyIndex, FbxAnimCurveDef::eInterpolationLinear);

x_curve->KeyModifyEnd();

FBXファイルを出力

アニメーションを書き込んだら最後にFBXファイルを出力しましょう。

FbxExporter* Exporter = FbxExporter::Create(manager, "Exporter");
Exporter->Initialize(argv[3]);
Exporter->Export(scene);
Exporter->Destroy();

Export(FbxScene*)はImportと同じく返り値として成功/失敗のboolを返します。また、最後ににはFbxManager->Destroyも呼んで破棄しておきましょう。

おわりに

今回はC++版FBX SDKを使ってアニメーションを書き出す方法について纏めました。公式ドキュメントやサンプルは結構あるんですが、何分結構ややこしい構造をしてるせいで中々わかりずらいですね。また、FBX SDKにはPython版も存在しています。機能に少し違いがあるらしいのですが、実際に使う場合はPythonの方が使い勝手は良いかもしれません。

まぁ最近はUnityにもFBXExporterがありますし中々SDKを触って実装する必要性がある場面は少なそうですが、自前でアニメーション書き出し用アプリを作ったりしてたので記事にしておきました。この記事が誰かの役に立てば幸いです。それでは!

Discussion