ことれいのもり

【解説編】AssimpでFBXアニメーションを動かすための理論的な手順まとめ

はじめに

FBX形式の3Dモデルにアニメーションをつけて再生するには、「スキニング」などの基礎的な考え方がかかせません。

Assimpは多くの3Dファイル形式を読み込めるライブラリですが、FBX形式のアニメーションを動かすために必要な手順があります。


この記事(解説編)では、Assimpを使ってFBXモデルのアニメーションを動かすための エンジンに依存しない理論的な流れ を解説します。


特定のライブラリ(今回はOpenSiv3D)での実装例は、後編の「実装編」で紹介します。

初心者でもわかりやすいように、かみ砕いて説明するので、安心して読み進めて!

前提知識

とくにないです!

今回の記事で紹介することは、Assimpの公式ドキュメントに記載されています。

正確で詳しい情報が欲しい方はこちらを見ることを推奨します。


Assimpドキュメント

FBX形式のモデルをアニメーションするための全体の流れ

まずは簡単に、全体の流れを紹介します。


① Assimpを使ってFBXファイルを読み込む

② メッシュとボーンの情報を抽出する

③ アニメーションを更新する

④ スキニング処理(ボーンの変形処理)をする

⑤ メッシュを更新する

① Assimpを使ってFBXファイルを読み込む

Assimpは、様々な3Dファイル形式を共通のデータ構造に変換してくれるライブラリです。

具体的には、「aiScene」という構造体に変換します。

読み込む手順

1. Assimp::Importerクラスを使って、FBXファイルを読み込む

2. フラグを設定する。(これを設定することで、Assimpが自動的にモデルの「前処理」をしてくれるため、実装が楽になります。)


・フラグの例

aiProcess_Triangulate:ポリゴンを三角形に変換

aiProcess_JoinIdenticalVertices:重複する頂点を結合

aiProcess_LimitBoneWeights: 各頂点に影響するボーンの数を制限

aiProcess_ImproveCacheLocality: 頂点の並びを最適化

aiProcess_FlipUVs: UV座標のY軸を反転

aiProcess_ConvertToLeftHanded: 左手座標系に変換


FBXの読み込みをコードにするとこうなります。


const aiScene* scene = importer.ReadFile(fbxFilePath.narrow(),
    aiProcess_Triangulate
    | aiProcess_JoinIdenticalVertices
    | aiProcess_LimitBoneWeights
    | aiProcess_ImproveCacheLocality
    | aiProcess_FlipUVs
    | aiProcess_ConvertToLeftHanded
);

aiSceneについて

読み込みが成功すると、「const aiScene*」という型が返ってきます。

変な型に見えますが、読み込んだモデルのメッシュ、ボーン、アニメーションなど、3Dアニメーションに必要な全ての情報をまとめた構造体 です。

この型を使って、欲しい情報を取得していきます。


読み込まれるデータを簡単に紹介します。


mMeshes: メッシュの情報(頂点・法線・UVなど)

mMaterials: マテリアルの情報

mRootNode: aiSceneの階層構造

mAnimations: アニメーションの情報

② メッシュとボーンの情報を抽出する

①で読み込んだ 「const aiScene*」 から、メッシュとボーンの情報を取得します。

メッシュ情報取得の流れ

メッシュとは、モデルの形(頂点・法線・UVなど)のことです。


コードとしては、aiScene::mMeshesから取得します。

ボーン情報取得の流れ

ボーンとは、アニメーションを動かすための骨格のことです。

Assimpで読み込んだ段階でボーンは階層構造になっています。

この階層構造を再帰的にたどることで、ボーンの親子関係初期変換を取得できます。


コードとしては、

aiScene::mRootNode を起点に、ノードの階層構造を再帰的処理を使ってたどります。

コードの詳細は後編に任せるとして、ここでは処理の流れだけ分かってもらえれば問題ないです。

③ アニメーションを更新する

今モデルが再生している時刻(例えば1.25秒)に合わせた、各ボーンの位置・回転・スケールを計算して更新します。


コードしては、各アニメーションデータは、aiScene::mAnimationsに格納されています。

この情報を元に、線形補間もしくは球面補間を利用して各ボーンを動かします。


とはいえ、この説明だけでは「それってどういうこと?」感じる方も多いと思います。

この説明だけで分からない人向けに、補足を入れます。分かる人は飛ばしてもらって構いません。

補足:キーフレームとアニメーションの補間について

キーフレームとアニメーションの補間について、長くなったので別記事にしてまとめました。

こちらで詳しく説明しているので、ご覧ください。


プログラムでアニメーションを再生するとは?キーフレームと補間の仕組み

④ スキニング処理(ボーンの変形処理)をする

スキニングとは、各頂点が複数のボーンから「どれだけ影響を受けるか(=ウェイト)」をもち、それに基づいて位置や法線を再計算することです。

つまり、③で取得したキーフレームの情報を元に、影響度合いを再計算し、反映させるとも言えます。


コードとしては、まず、あらかじめどのボーンがどれだけウェイトを持っているかのペアを作成しておきます。

その後、各頂点について、③で取得したボーンの変形情報とあらかじめ設定されたウェイトを使って、最終的な位置や法線を計算します。


簡単に言うと、各頂点に対して③で得た情報+ボーンの影響度合いを反映させるということです。

⑤ メッシュを更新する

④でスキニング処理が終わった頂点データがあります。

このデータを、モデルのメッシュに反映させます。


コードとしては、新しい頂点の配列を用意し、描画処理に渡します。

すると、次のフレームで次のアニメーションの動作をしたモデルが正しく描画されます。


①と②の読み込み処理を最初に行ない、

③~⑤の処理を毎フレーム実行することで、モデルがアニメーションをします。

なんとなくイメージが着いたでしょうか。

おわりに

この記事では、理論的な流れを中心に説明してきましたが、細かくて難解な部分はあえて省略しています。

完璧には分からなくても、なんとなく雰囲気は掴めたのではないでしょうか。


アニメーションの仕組みを知ることで、実装やデバッグの際に「なぜ動かないのか?」を論理的に考えられるようになります。

後編では、いよいよこの理論を元にした、具体的なコードを紹介します。

ぜひ続けてお読みください!