FBX SDKでアニメーションを書き出してみる
はじめに
こんにちは。Kaibaです。今回は表題の通り、AutoDeskが提供しているC++版FBX SDKを使ってFBXファイルを読み込み、アニメーションを書き出す方法について纏めます。FBXファイルを読み込んだり書き出したりする部分は結構情報があるんですが、アニメーション周りに関しては全くと言っていいほど情報が出なかったのでここに書き留めておこうと思います。
環境構築
環境構築に関しては以下のブログなどを参考にやってみてください。
流れ
今回は以下の手順の処理を順に書いていきます。
- FBXファイルを読み込む
- アニメーションを書き込む
- 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/LocalStop
とDescription
で設定できます。
//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
を作ります。一つのボーン、アニメーションさせたいFbxNode
のFbxProperty
ごとに一つのFbxAnimCurveNode
が必要です。また、FbxAnimCurveNode
にはタイプ(型)があります。タイプはPropertyに合わせてください。FbxDouble
やFbxDouble3
などになります。
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の接続
さて、FbxAnimCurveNode
はFbxCluster
などに結びつきアニメーションのチャンネルを提供しますが、アニメーションの値は保持してくれません。アニメーションの値はFbxAnimCurve
が保持します。まずは各値毎にFbxAnimCurve
を作成します。
また、FbxAnimCurve
はキーフレームとしてFbxTime
とFbxDouble
を保持します。そのため、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