BlenderのFBX出力メモ

初回更新2022/07/25
最終更新2022/08/02
加筆修正2023/07/19
FBXの中身を覗くツールを作りました。

これを使って、色々調べてみたので、わかったことをこの記事に記録します。
・BlenderのデータがどのようにFBXに反映されるのか
・FBX出力設定がどのようにFBXに効いてくるのか
こっちの記事は、なるべく、プログラムになじみがない人でも読みやすいように書いていきます。


SceneとNode

いきなりですが、FBXの中身の話です。この説明をしないわけにはいかないので。
BlenderのBlendファイルの中身は、FBX内でSceneという名前のデータに変換されます。Blenderのシーンとほとんど同じ意味です。
BlenderのオブジェクトはFBXではNodeというデータの塊になります。でもまったく一対一で対応しているわけではありません。Blenderではシーンの直下にオブジェクトを複数配置しますが、これをFBXに変換すると、まずSceneの下にRootNodeという特別なNodeが作られて、その下にBlenderのシーン直下のオブジェクトがNodeとして配置されます。

シーン直下のオブジェはRootNodeの下に配置

ちなみにBlenderは、このRootNode直下のNodeの回転やスケールに手を加えることで、Blender・FBX間の、軸やスケールの差異の整合を取ろうとします。

シーン直下のトランスフォームには手が加わる

名前

FBXは文字をUnicodeで保持するので、Blender上で付けた名前は、日本語であっても問題無く出力されます。

時間

Blender内では時間はフレームで管理されますが、FBX内では時間は時間そのもので管理されます。厳密には、141120000を1秒とする、謎の整数で管理されてます。(MAYAの内部と同じらしいです。)キーフレームにくっ付いている時間データはこの数値です。なので、Blenderのフレームレート設定は、キーの出力粒度には影響しますが、受け手側アプリ(UEとか)の再生速度に影響しません。

60fpsの場合、30Fのキーの時間は70.56M(141.12Mの半分)

一応、FBX内にはGlobalSettingsというデータブロックがあって、その中のTimeModeという項目で、「このFBXは元々はいくつのfpsで作られましたよ」という情報が記録されているのですが、読み込む側(UEやSmile Game Builder)は、時間の絶対値を使うはずですので、あくまで参考情報にとどまると思います。

Blender上のfpsが一応記録される

トランスフォーム

Blender上のトランスフォームの適用順は、スケール→回転→位置です。FBX上のトランスフォームの適用順も、スケール→回転→位置です。
そして、Blender上の回転の適用順は、デフォルトでは「XYZオイラー角」です。名前の通り、X→Y→Zの順に適用されます。これは、オブジェクトのプロパティで他の順番のオイラーや、クォータニオンに変更することができます。一方、FBXもオイラーの回転の適用順は変更可能です。しかし、FBXにはクォータニオンが存在しません。
そこでBlenderは、Blender上のすべての回転を、FBX上では、X→Y→Zのオイラー角に変換します。クォータニオンが実現できないので、割り切った処理にしたのでしょう。

すべてXYZオイラーに修正される

なので、Blender上でせっかく10度単位や45度単位の綺麗な数字に設定したのに、FBXを経由すると、中途半端な数字になるなーというのは、結構あると思います。YXZオイラーで綺麗な数字(30とか60とか)でも、XYZに変換すると見慣れない数字になるというのは、数学的な結果です。
また、シーン直下のオブジェクトの場合、前述の通り、FBX出力時に回転に補正が入るので、例え「XYZオイラー角」に設定していても、綺麗な数字にはなりません。

※FBXはクォータニオンを持てない。なので、すべての回転をXYZオイラー角に変換する。これが、BlenderがFBX出力時にアニメーションを「ベイク」するしかない根本的な理由だと思います。

座標系

Blenderは右手ZUpの座標系を採用しています。これは変えることができません。一方FBXは、右手YUpの座標系を基本としていますが、他の座標系にも対応しています。
FBXにはGlobalSettingsというデータブロックがあって、この中に座標系(前は+-XYZのうちどれか、上は+-XYZのうちどれか)を記録しています。
BlenderのFBX出力設定には、「前方」と「上」という項目があって、これにどの座標軸が前と上かをセットすることができます。この設定は、前述のGlobalSettingの座標系に"反映"されます。ただし、右手系であることは変えられないようです。

「右手+YUpの+ZFront」と記録されている例

また、「前方」と「上」の設定によって、シーン直下のオブジェクトに回転が加わります。

デフォルト設定では、「前方」は「-Zが前方」、「上」は「Yが上」です。この場合、オブジェクトが回転無しで配置されていても、X軸で-90度回転されて出力されます。そしてGlobalSettingsの座標系は、右手系の+YUp/+ZFrontになります。
・・・え!? 「-Zが前方」にしたのに+ZFrontになるの!? そうなんです。「前方」に設定した座標軸は正負反転されます。おそらくBlenderにとっての"前"と、FBXにとっての"前"に食い違いが生じているのです。

「前方」のプラスとマイナスが逆転する

きっとこんな感じ
・Blenderは+Yが前方だとしている。
 ワールドにおける「向こう側」のような意味での前方。
・スザンヌは-Y方向を向いているので、Blenderは、
 キャラクターというものは後方(-Y)を向くように作るものとしている。
 (後方に居る観測者と向き合っている、というテイ。)
 (舞台と観客席のような関係)
・FBXにとっての"前方"の定義は、キャラクターが向いている側。
 家なら玄関のある側。
・Blenderはこの行き違いをちゃーんと認識しているので、
 -Zを前方にしろと設定されたら、FBX内では+Zを前方にする。

スケール

Blenderはメートルを長さの基本単位にしています。これは、シーンプロパティの「単位の倍率」設定で変えることができます。一方、FBXの採用している単位は、FBX SDKの公式ドキュメントによると「暗黙にcm」とのことです。
このように、BlenderとFBXでは基本単位が違いますし、Blenderのシーンプロパティの設定や、FBX出力時の設定によっても、関係は変わってきます。BlenderはFBXに適切にスケールを施してやる必要があるわけです。BlenderがFBXにスケーリングを適用する方法は2つあります。
まず1つ目は、GlobalSettingsです。この中にはScaleFactorという項目があります。つまり「このファイルの中身はこういうスケールで扱ってね」という項目です。

2つ目は、シーン直下のオブジェクトのスケール値です。これを調整することで、シーン全体のスケールの整合を取ろうとします。
この2つの手段は併用されます。

FBXのスケールに影響するパラメータ項目は次の4つです。他にもあるかも。
A. シーンプロパティの「単位の倍率」
B. FBXエクスポート設定の「スケール」
C. FBXエクスポート設定の「スケールを適用」
D. FBXエクスポート設定の「単位を適用」
(シーンプロパティには「単位系」や「長さ」という項目があります。しかしこれは、単にエディタ上の見せ方の違いにしか影響しません。"Blender内部値は1メートル"というのは、大前提になってるように思います。)

スケールに関わるのは、この4つ

Blenderは、「単位スケーリング」と「カスタムスケーリング」という2つのスケールをFBXに適用します。
 単位スケーリング = 100.0 × 「A. 単位の倍率(※)」
 カスタムスケーリング = 「B. スケール」
 ※A.は「D. 単位を適用」オンの場合のみ適用
そして、この2つのスケールを掛け合わせたものが、FBX全体に適用したいスケールです。
 単位スケーリング × カスタムスケーリング
2つのスケールをどのように適用するかは、「C. スケールを適用」の設定によって決まります。
「C. スケールを適用」が、
「全ローカル」の場合:
 単位スケーリングもカスタムスケーリングも、オブジェクトに適用
「FBX単位スケール」の場合:
 単位スケーリングは、GlobalSettingsに適用
 カスタムスケーリングは、オブジェクトに適用
「カスタムスケール」の場合:
 カスタムスケーリングは、GlobalSettingsに適用
 単位スケーリングは、オブジェクトに適用
「すべてFBX」の場合:
 単位スケーリングもカスタムスケーリングも、GlobalSettingsに適用

厳密には、シーン直下のオブジェクトそのものがアニメーションするときのTranslationにもこの倍率が適用されます。でもこれはレアケースなのであまり気にしなくていいと思います。キャラクター作成においてアーマチュアオブジェクト自体をアニメーション対象にすることは無いでしょう。ムービーシーンをBlenderで作る、みたいなときに思い出せばいいと思います。

フレーム範囲

タイムラインエディタや出力プロパティ(FBX出力設定ではないよ)には、「フレーム範囲」という設定があります。アニメーションを作る時に、再生する範囲を設定するアレです。これはFBX出力時のアニメーションに影響しません。安心してください。
一方で、「現在のフレーム」(タイムラインエディタ上のバー)は、FBXの内容にバリバリ影響します。Unityでblendファイルをインポートして使ってた人はピンと来るはずです。毎回FBXの内容が変わることを気にする人は、フレーム0にしてからFBX出力する、という癖を付けたほうがいいと思います。

フレーム範囲(10~50)と現在のフレーム(55)

アニメーション

アニメーションの種類

Blender上のアニメーションは次の4つくらいの種類があります。
・アクション
・NLA
・NLA上のストリップ
・NLAを使ったスタッシュ

アクション、NLA、ストリップ、スタッシュ

アクションは、ゲームデータを作る時におなじみのアレです。WalkとかAttackとかそういう単位のデータです。オブジェクト、特にアーマチュアに紐づきます。
NLAはストリップを組み合わせて一連のアニメーションを作る機能です。Walkを繰り返すことで長距離の歩行を表現したり。なんというか、ゲーム用のモデル制作ではあまり使わない機能です。ムービーシーンをBlenderで作るときとかに思い出すといいと思います。
ストリップはNLAを構成する要素です。NLAエディタの各トラックに配置されている太いバーです。アクションから変換されて作られます。変換された後も、アクションとは紐づいていて、管理されます。
スタッシュは、アクションの新規作成時に、そのオブジェクトにもともと付いていたアクションがユーザー数1だけだった場合、自動的にNLAストリップを追加してそのユーザーにしておく機能です。ゲーム作成における「うっかりアクション消し」防止のために、後から追加された機能らしいです。
ゲーム用途なら、NLAもそれを構成するストリップも消しちゃっていいです。カットシーン(ムービー)を作るときに思い出せばいいと思います。

出力設定

これらのアニメーションデータのうち、どれが出力されるのかを決定するのは、FBX出力設定の次の3つの項目です。
・「アニメーションをベイク」
・「NLAストリップ」
・「全アクション」

この項目で出力対象が決定される

「アニメーションをベイク」がオフの場合、一切アニメーションが出力されません。Blenderにおいてはアニメーションの出力は、「ベイク」を伴います。この場合のベイクは、本来のキーフレームとは別に、全フレーム(もしくは間引いたフレーム)に対してキーを追加することです。なぜそんなことをするかというと、Blender上のアニメーションのデータ構造が、そのままでは上手くFBXに乗らないのです(後述)。
「アニメーションをベイク」がオンの場合、残り2つの項目の組み合わせで出力内容が決定され、それは3パターンの構成があります。
A. 「NLAストリップ」オフ「全アクション」オフ
 →NLAだけが出力されます。ムービーシーンを作りたいあなた向け。
B. 「NLAストリップ」オン「全アクション」オフ
 →ストリップだけが出力されます。マニアック用途。
C. 「NLAストリップ」オフ/オン「全アクション」オン
 →アクションだけが出力されます。ゲーム用途。

2項目の組み合わせで出力対象が決定

このとき、Fや0の有無に関わらず、アクションのプルダウンに現れるアクションが全部出力されます。0が付いてるアクションでも、blendファイルを再読み込みしないと出力されます。スタッシュ入りして消え切れていない(ユーザー数がある)アクションも出力されます。

キーフレーム

キーフレームの範囲

繰り返しになりますが、出力されるキーフレームの範囲は、Blender上で再生する時の再生範囲とは無関係です。
出力されるキーフレームは、対象アクションの全チャンネルのうち、最初のキーがあるフレームから、最後のあるキーがあるフレームまでです。そして、最初のキーがあるフレームを0秒とします。何が言いたいかというと、キーフレームはどこから始まってもいいということです。人やチームによっては、フレーム0ではなく、フレーム1を最初のキーにすることもあると思います。それで結果に差が出ることは無いということです。

この場合は20f目を0秒として出力される

出力されるキー

キーフレームは、基本的には、Blenderのフレームレート設定に従って、ほぼ全フレーム出力されます。ここで言う全フレームとは、キーを打っていないフレームにもキーが出力されるということです。えー!? そうなんです。せっかくシンプルに少ないキーでアニメーションを表現していても、FBXにするとキーが膨大に増えます。これは例えアニメカーブを「リニア」に設定していても変わりません。ただし、キーの値が変わらない区間(つまりボーンが動かない区間)は、省略してくれます。出力設定の「ベイク」って表現はこのことかー。

変化する区間にキーが打たれる(下のIn FBXは想像図です)

上記の画像のようにキーがどばーと増えます。FBXファイルのサイズがやたら大きくなりがちなのはこれが一因でしょう。さらに、あなたがせっかく「意図を込めて」打ったキーは、自動で追加されたキーに埋もれてしまいます。後述しますが、設定次第では、せっかく「意図を込めて」打ったキーが逆に間引かれてしまうことさえあります。
この仕様は、FBXの回転がクォータニオンに対応していないことと、無関係ではないと思います。キーフレームだけを出力しても、回転方法が失われていては、受け手側(UE等)でキーフレーム間の回転を補間できないからです。だから仕方なく、補間済みのキーを全区間に追加するわけです。これがベイクです。Blenderさんは悪くない。

出力設定

FBX出力設定のうち、キーフレームに影響する設定項目は次の4つです。
・「全ボーンのキー」 デフォルト: オン
・「強制的に開始/終了キーを挿入」 デフォルト: オン
・サンプリングレート デフォルト: 1.0
・簡略化 デフォルト: 1.0

「全ボーンのキー」と「強制的に開始/終了キーを挿入」に関しては、エディタ上でポップアップされるヘルプでは次のように説明されています。

すべてのボーンに少なくとも一つ以上のアニメーションを強制的にエクスポートします(UE4などいくつかのターゲットアプリケーションで必須)

全ボーンのキー

アニメーションチャンネルのアクションの開始と終了に、常にキーフレームを挿入します

強制的に開始/終了キーを挿入

でも解析した結果、ちょっと説明文と挙動が違うんですよね・・・。

設定「強制的に開始/終了キーを挿入」

「強制的に開始/終了キーを挿入」がオンの場合、開始終了フレームにキーが無いチャンネルに、開始終了のキーが追加されます。本来キーを打たなかったボーンにも打たれます。さらに、トランスフォーム三点セット(位置・回転・スケール)すべてのXYZチャンネルが自動的に追加され、そこにもキーが打たれます。つまり、すべてのボーンの、位置・回転・スケールの、すべての軸(XYZ)にキーが打たれます。
なので、せっかく少ないチャンネルだけでシンプルにアニメーションを作っても、チャンネル数がどばーっと増えます。(とはいえ、追加されたチャンネルは、もともと変化してないはずなので、前述の通り間引かれていて、2キーしか打たれません。容量がそんなに増えるわけではないです。)

これが下の画像のようになる
FBXの中身(あくまで想像図)

この仕様は覚えておいた方がいいと思います。Blender上で無駄なチャンネルを増やさないのは、単に作業のしやすさだけでなく、「関与しない」ことを表現するために省いている側面もあるからです。(例えば、ゲーム中に手が大きくなるBuffがあるなら、HandのScaleはアニメで上書きしてほしくないはずです。)
「強制的に開始/終了キーを挿入」がオフの場合、上記のように自動でチャンネルが追加されることは無いのですが、今度は逆に削除されるキーが出てきます。チャンネルの値が変化し始める前のキーと、最後の変化の後のキーが削除されます。(下の画像見たほうがすぐわかると思います。)

開始終了キーの挿入をしない場合、キーが削除されることがある

この仕様にも気を付けたほうがいいです。動かないことを表現したくて、値の変化しないキーを打ってるはずなのに、それが自動で取り除かれてしまうのは、望まない結果になることがあります。最初はじっとして動かないけど、いきなり動き出すアニメとか、あるでしょ? もしくは残心を表現したくて、攻撃後にピタっと止まるとか。

設定「全ボーンのキー」

「強制的に開始/終了キーを挿入」がオフの場合、「全ボーンのキー」がオンでもオフでも、出力されるキーに影響は出ません。全ボーンのキーは出力されません。(え~~????)
「強制的に開始/終了キーを挿入」はオンで、「全ボーンのキー」オフの場合、値が変化しない軸(X/Y/Z)のキーが出力されません。しかし、元から1個もキーが無いボーンについては、キーがXYZ、各2個ずつ出力されます。・・・言ってる意味が分からん! 下の画像見るのが早いと思います。Keyの後に並んでる数字が、そのボーンのXYZ各軸のキーの数です。

仕様の意図はよくわからないけど、きっと、上の画像の下半分のような状態は、UE4にとって都合が悪いのでしょう。UE4さん的には、XYZどれか1つ出すなら3つ全部出してくれ、と。だから後から仕方なく追加されたオプションなのだと思います。それにしたって、名称や説明文と、実際の動作が一致していません。

実際の動作は次の通りです。「強制的に開始/終了キーを挿入」がオンの場合、全ボーンのキーを少なくとも1チャンネルは出力します。さらに、「全ボーンのキー」も同時にオンだと、全軸(X/Y/Z)のチャンネルを必ずセットで出力します。これが今のところの解釈です。

設定「サンプリングレート」

「サンプリングレート」を1以外に設定すると、そのフレーム間隔でしかキーを出力しなくなります。最初のキーを0フレーム目として、その後は設定値の整数倍のフレームで、強制的にキーが打たれます。かならず整数倍で終わるので、はみ出した分のフレームは、たとえキーがあっても捨てられます。

この設定項目は、手作りの普通のゲーム用アニメーションには使えないと思います。肝心な、文字通り「キー」となるキーフレームが、すっ飛ばされてしまうからです。武器を振りかぶる時の最後の瞬間、振りかぶりのピークのキーが消えてしまうのは、作者として好ましくないはずです。キーフレームの「キー」は、外せない・重要なという意味のはずです。
これは例えば、よっぽど長尺の何かがあって、それを荒くプレビューしたいとか、高フレームレートでモーションキャプチャーしたデータを間引きたいとか、そういう用途の設定だと思います。

設定「簡略化」

「簡略化」はキーフレームを、キーの値を考慮して間引いてくれます。しかし、Blender上で打ったキーがFBX内で維持されるとは限らないのは、「サンプリングレート」の場合と同じです。この設定値を大きな値にするほど、キーフレームが減ります。0で無効。デフォルトが1。最大値は100です。この設定値が、具体的にどのようなアルゴリズムで使われるのかわかりませんでしたが、少しサンプルを採ってみました。
下の表は、簡略化の値を0、1、2、10、100と変えた時の、各アクションA、B、Cのキーフレーム数を示しています。各アクションは、1秒間(60F)の間に、位置・回転・スケールを変化させます。ActAは少しだけ動かしたとき、ActCは大きく動かした時、ActBはその中間です。

受け手側のアプリ(UE等)がどう処理するにせよ、FBXで回転方法(オイラーの回転順、クォータニオンの使用)が失われている以上、回転のキーが減りすぎるのも嫌だなーという気がします。しかも「サンプリングレート」設定のときと同様、手で打ったキーが消えちゃうのも悲しいです。
さらに、この機能は、キーフレームの間隔決定にちょっと癖があることが分かりました。どうやら、前回出力したキーから値が一定割合変化した時に、次のキーを打つ、という動作になっているようです。例えば、「簡略化」を最大値の100に設定した場合は、キー値が±30%くらい変化するたびにキーが打たれます。これは、位置やスケールに関しては悪くない動作だと思うのですが、回転にも同じアルゴリズムを適用しているようで、これは嬉しくない仕様に思います。どういうこと?↓こういうこと。

上の画像は、「簡略化」100で、60Fで180度回転する2つのキーの間に、どのようにキーが追加されたかを示してます。上の上の表の右下、ActCの結果の25個のキーの内訳です。-90からはじまって、+90に至るまでのキーの間隔が、一定でないことが分かります。最初と最後は28度ずつキーが打たれてますが、中間あたりでは3度ずつキーが打たれてます。位置やスケールと違って、角度変化には相対的な大小がありません。180度から3度変わるのと、10度から3度変わるのとでは、見え方の重要度は全く同じはずです。幾何学的に変なのです。
こうなると最早、デフォルト設定の1でさえ使うのをためらってしまいます。作ったものに対して自動で手が加えられるのは別にいいんです。ちゃんと説明があれば。でもこれは、思わぬ方向からの、気づきにくい、しかし確実にどこかで効いてくる改変です。「いい感じに間引いてくれてるはずなのに、このアニメーションのこの部分だけ、なんか変だな」みたいな、そういうのを起こしうるわけです。「簡略化」は0固定で運用するしかありません(つまり全フレーム出力)。

おわり

FBXを覗くツールを作り始めてから1か月。もっといろいろ解析してみる予定でした。マテリアルとかアーマチュアとか。でもここまで調べた時点で心が折れてしまいました。FBXは自分のような神経質なタイプには向いてないようです。(Blenderさんも、FBXに関してだけは、色々と諦めてる感があります。)

今後はglTFを試してみようと思います。WikipediaのglTFのページには、"「3DにおけるJPEG」と表現されることもある"と書かれてあったので、これまであまり興味がありませんでした。てっきり、軽量化のためにいろいろ情報を非可逆的に欠落させてしまうのかな、と。しかしBlenderで出力して中身を覗いた限りだと(.gltf形式だとテキストエディタである程度見れる)、むしろ情報に過不足が無く、好印象でした。自分にとっては、JPEGというよりむしろPNGな気がします。素直な形式。

glTF試すぞ!

この記事が気に入ったらサポートをしてみませんか?