はじめに
本文書は、 3D コンテンツを表現する形式である FBX 形式について、構造と意味の解説を試みるものである。
対象読者、目標
対象読者は以下の通りである。
- FBX データを何らかの方法で自作プログラムなどから読み書きする必要のある人
- FBX データの読み書きのためのライブラリ等を実装する人
- FBX を読み書きするプログラムのバグと思しき挙動を分析したい人
- 3D コンテンツの内部に興味を持っている人
本文書の目標は以下の通りである。
- 読者は FBX のバイナリ形式及びテキスト形式のデータを直接読み、ある程度理解できるようになる。
- 読者は FBX データを必要に応じて低レベルで編集したり、壊れたデータがどう壊れているか説明できるようになる。
- プログラミングのできる読者は、 FBX データを読み書きする簡単なプログラムを実装できるようになる。
以下は本文書の目標ではない。
- 読者はモデリングソフトを利用して 3D コンテンツを作成し、 FBX 形式で出力できるようになる。
- 本文書ではモデリング自体についての解説は行わない。
- 読者は FBX データを描画するためのグラフィックス技術を理解する。
- 部分的な解説は行うかもしれないが、あくまでデータの意味の説明に留める。
- 読者は既存の FBX 読み書きのためのプログラムやライブラリの利用方法を理解する。
- 本文書は特定のプログラムやライブラリのマニュアルではない。
FBX 形式とは
それを知らない人がこの文書を読んで得るものは少ないであろう。
(本文書のリポジトリはプルリクエストを歓迎しています。)
免責
本文書は様々な場所で集め、あるいはファイルを観察して得た知識をまとめたものである。
FBX 形式はプロプライエタリであり、その正確な仕様は公開されていない。 また公式に配布されている FBX SDK もソースコードは公開されていない。 そのため、情報の正確性についての保障はされない。 また、今後のフォーマットの更新などへの追従や誤りの訂正も保障されない。
しかし、修正、追加、更新、翻訳などの貢献は歓迎する。
リポジトリは https://github.com/lo48576/fbx-book/tree/master/ja である。
ライセンス
この 作品 は クリエイティブ・コモンズ 表示 4.0 国際 ライセンスの下に提供されています。
本書での記法
本章では、本書におけるデータ型や値の記法を導入する。 これらは本書での解説のための記法であり、 FBX データ内部にそのような記法が出現することを意味しない。
型の表現
本書では原則的に、データ型の表現としてプログラミング言語 Rust の型の記法を用いる。 具体的には、以下のようなものを用いる。
表記 | 内容 | 補足・詳細 |
---|---|---|
bool | 真偽値 | 値は true と false 。内部表現が 0 と 1 とは限らない。 |
u8 | 符号なし8ビット整数 | |
u16 | 符号なし16ビット整数 | |
u32 | 符号なし32ビット整数 | |
u64 | 符号なし64ビット整数 | |
i16 | 符号付き16ビット整数 | 2の補数表現 |
i32 | 符号付き32ビット整数 | 2の補数表現 |
i64 | 符号付き64ビット整数 | 2の補数表現 |
f32 | 32ビット浮動小数点数 | IEEE 754 |
f64 | 64ビット浮動小数点数 | IEEE 754 |
[T; n] | 要素数 n の、型 T の配列 | |
String | 長さ無指定の文字列データ | UTF-8 |
Vec<T> | 要素数無指定の、型 T の配列 | |
Vec<u8> | 長さ無指定のバイナリデータ | 符号なし8ビット整数の配列と見做せる |
値の表現
データ中に現れる数値などを記述するとき、 42
や 0xff
や 3.14
のように表現する。
もし桁が大きな数値であれば、読みやすさのためアンダースコア _
を挿入することがある。
たとえば 0xcobebeef
は 0xcobe_beef
、 1000000
は 1_000_000
などのように表現することもある。
バイナリデータを記述するときは、 [c0 be be ef]
のように [
と ]
で囲って1バイトずつ16進数を空白区切りで並べて記述する。
1バイトは16進数2桁で表現する。
データは数値やその他の型として読んだ後の値を表現するかバイト列として1バイトずつ記すため、エンディアンを気にする必要はない。
文字列は foobar
のように表現する。
これらが数値やバイナリデータと混同するおそれのある文字列値であるときは、文字列である旨明示する。
明示がないときは、数値やその他の型の値として優先して解釈するものとする。
特殊な記号や非印字文字などは \x00
や foo\x00bar
のように \x
に続けて16進数2桁で表現する。
FBX 7.x
本章では、 FBX 7.x (主に 7.4 以降) の仕様について解説する。
概要
文法とスキーマ
FBX 形式は、文法とスキーマの2つの層(段階)に分けて解釈することができる。
最初に、低レベルな文法 (syntax) が存在する。 これは極めて汎用的な規則で、汎用的なデータ構造をバイナリデータで表現するための配置を決めるものである。 FBX データ中では、整数、文字列、バイナリといった、シンプルかつ多様なデータを保持する必要がある。 構文に従ってバイナリデータを読むことで、これらのデータ型を識別し、また配列や木などの構造を読み取ることができる。
スキーマ (schema) は、文法に従って解釈された汎用的なデータ構造を、3Dモデルとして意味付けする規則である。 たとえば、同じ数値の配列であってもどれが頂点座標でどれがテクスチャ座標なのか、それらはどのように対応するのか、モデルとテクスチャはどう紐付けられるのか。 汎用的なデータ構造に対して、このような解釈可能な意味や、それを表現するための更に上位の構造を与える規則がスキーマである。
文法とスキーマの関係自体は FBX 形式に特有のものではない。 たとえば汎用的でそれ自体が意味を提示しない XML 文法に加えて XHTML や SVG のようなスキーマを与えることで、 HTML 文書や SVG 画像といった解釈可能なデータを得ることができる。 他の 3D データ形式でもこのようなデータ形式定義は行われており、 COLLADA 形式は XML 文法と COLLADA スキーマによって、 glTF 形式は JSON 文法と glTF スキーマによって意味を与えられる。
FBX の文法とスキーマ
文法は汎用的なデータを記述するものであり、また解釈を与えることが少ないため、単純になりやすい。 そのため、多くのデータを観察することにより、その詳細な規則を推測することが可能であることが多い。
対照的にスキーマは表現するデータの複雑さを反映したものになるため、その定義も複雑になりやすい。 特に FBX 形式はプロプライエタリであり仕様が公式に公開されていないため、スキーマの全貌や詳細も明らかでない。 本書の情報を参考にするときはこの点に留意せよ。
共通の構造
この節では、 ASCII 形式かバイナリ形式かに関係なく共通に利用される構造について説明する。
ノード
FBX データの本体は、文法レベルでは木構造で表現される。 ノードは、その木構造を構成するための主要な要素である。
ノードは以下の3種類の情報を保持する。
ノード名
ノードの名前。
観測される範囲では、 ASCII の英数字1文字以上で表現されている。
すなわち、正規表現 /[a-zA-Z0-9]+/
にマッチする名前しか利用されていないようである。
また、バイナリ形式ではノード名の長さに文法上の制限が存在する。
属性
属性はノードに紐付いた情報である。
これは XML における属性のような連想配列 (名前と値の組) ではなく、単なる値の配列として保持される。 よって、属性の出現する順番が意味を持つ。
属性として利用可能な型は以下の通りである。
- プリミティブ
bool
i16
i32
i64
f32
f64
- 配列
Vec<bool>
Vec<i32>
Vec<i64>
Vec<f32>
Vec<f64>
- 特殊
Vec<u8>
String
FBX バイナリ形式では、配列型の値や特殊型の値の要素数やバイト長に文法上の制限が存在する。
これらの型はネイティブコード上あるいは FBX バイナリ形式上では区別・判別されるが、 FBX テキスト形式上では区別できない或いは値が変化する場合がある。
たとえば i16
, i32
, i64
は単なる整数の10進数表記になり、場合によっては f32
や f64
の値も整数値であれば 0
などのように記述され整数型と区別できなくなる。
また、 FBX テキスト形式では文字列のエスケープ規則が不完全であるため、文字列値も保存時と読み出し時で変化することがある。
詳細についてはテキスト文法についての『属性』節を参照。
子ノード
ノードは0個以上の子ノードを持つ。 子ノードは同じ名前を持つこともあるが、観察の限りではその出現の順番が意味を持つようなデータはなかった。 よって、表現上これらは配列であるが、実用上は順序のない集合のようなものとして扱っても問題ないだろう。
バイナリ文法
本節では、 FBX 7.x 、特に FBX 7.4 以降のバイナリ形式の文法について解説する。
前提
バイナリ表現
FBX バイナリ形式では、多バイトデータの表現として基本的にリトルエンディアンが用いられる。
たとえば、u32
型の値 7400
(0x00001ce8
) は、バイナリデータ中では [e8 1c 00 00]
のようなバイト列で表現される。
文字列表現
FBX データにおいて、文字列は基本的に UTF-8 エンコードで表現される。 そのため、文字列についてエンディアンは気にする必要がない。
稀に独自型データとして UTF-16 文字列が入っているなどの状況があるが、これは極めて特殊な例のため普通考える必要はない。 そのような例外がありうる場合は適宜明示する。
基本型の値の表現
エンディアン
FBX バイナリ形式では、多バイトデータの表現として基本的にリトルエンディアンが用いられる。
たとえば、u32
型の値 7400
(0x00001ce8
) は、バイナリデータ中では [e8 1c 00 00]
のようなバイト列で表現される。
真偽値
bool
は true
か false
の値を持つ型だが、これは FBX バイナリ形式中では (驚くべきことに) それぞれ文字 Y
(数値 0x59
) と文字 T
(数値 0x54
) で表現される。
論理的な値 | バイナリ表現 | ASCII 対応する文字 |
---|---|---|
false | 0x54 | T |
true | 0x59 | Y |
T
と Y
の由来が何なのかは不明。
留意すべきなのが、これらは Autodesk 公式の FBX SDK が読み書きする値であって、必ずしもすべてのソフトウェアがこれに準拠しているわけではないことだ。
たとえば Blender 2.72b における FBX プラグインは、真偽値として 0x54
と 0x59
ではなく 0x00
と 0x01
を出力する。
このようなデータは FBX SDK にとって不正であり無視されてしまうため、情報が欠落することになる。
FBX 処理系はこのような壊れた真偽値表現に遭遇した場合、 0x54
と 0x59
のみを受け入れて FBX SDK と同様にデータを読み取るのか、偶数と奇数で判別して出力プログラムが意図した (であろう) データを読み取るのか、選択する必要がある。
ファイル構造
FBX バイナリ形式のデータは、先頭から順に以下のような構造になっている。
内容 | サイズ | 補足 |
---|---|---|
マジックナンバー | 23 bytes | |
FBX バージョン | 4 bytes | |
トップレベルノード | (可変、0個以上) | 可変サイズ |
ノード終了マーカー | 13 or 25 bytes | FBX 7.4 までは13バイト、 FBX 7.5 以降は25バイト |
フッタ1 | 16 bytes | |
パディング | 0 〜 15 bytes | コンテンツの本体。可変サイズ |
フッタ2 | 4 bytes | 全て 0x00 |
FBX バージョン | 4 bytes | ヘッダでの FBX バージョンと同内容 |
フッタ3 | 120 bytes | 全て 0x00 |
フッタ4 | 16 bytes |
パディングが適切なサイズで挿入されていれば、ファイル全体は必ず16の倍数の長さを持つ。
マジックナンバー
FBX バイナリ形式は、ファイル先頭に23バイトのマジックバイナリ [4b 61 79 64 61 72 61 20 46 42 58 20 42 69 6e 61 72 79 20 20 00 1a 00]
を持つ。
文字列で表現すると Kaydara FBX Binary \x00\x1a\x00
のようになるため、印字可能ではないが ASCII の範囲内で表現できる。
(Binary
のあとに半角空白 \x20
が2つ続くことに留意せよ。)
$ hexdump -C sample-74.fbx --length 23
00000000 4b 61 79 64 61 72 61 20 46 42 58 20 42 69 6e 61 |Kaydara FBX Bina|
00000010 72 79 20 20 00 1a 00 |ry ...|
00000017
$
なお Blender の開発者による情報では、最初の21バイトをマジックバイナリ、次の2バイト ([1a 00]
) を不明な情報としている。
どのような理由でこう解釈したかは不明だが、実用上は最初の23バイトをまとめてマジックバイナリであると解釈して問題ない。
FBX バージョン
マジックナンバーに続けて、 FBX バージョンが4バイトの整数 (u32
型)で表現される。
$ : 以下は FBX 7.4 のファイルの例
$ hexdump -C sample-74.fbx --skip 23 --length 4
00000017 e8 1c 00 00 |....|
0000001b
$
バージョンと値の対応は以下のようになっている。
バージョン | 値 (10進数) | バイナリ表現 |
---|---|---|
7.3 | 7300 | [84 1c 00 00] |
7.4 | 7400 | [e8 1c 00 00] |
7.5 | 7500 | [4c 1d 00 00] |
バージョン x.y は 1000 × x + 100 × y で表現されているようである。 100未満の桁が利用されている例は見たことがないため、使われているかは不明。
トップレベルノード
ノードを0個以上並べたもの。 この個数や長さを先頭から読んで事前に知ることはできない。
ノードについては『ノード』節で解説する。
ノード終了マーカー
FBX 7.4 までは13バイトの、 FBX 7.5 以降では25バイトの 0x00
を並べたもの。
これはトップレベルノード(の列)の終了を意味するマーカーである。
このマーカーはファイル中に明示されない暗黙のルートノードの終了マーカーとして捉えることができる。 詳細は『ノード』節で解説する。
フッタ1
16バイトのデータ。 値についても用途についても詳細は不明だが、規則性が見られる。
このフッタの値は、以下のようなビットパターンを持っている。
(x
の部分は 0
から f
までの任意の4ビット。)
fx bx ax 0x dx cx dx 6x bx 7x fx 8x 1x fx 2x 7x
すべてのバイトについて上位4ビットが固定されているのが特徴的である。 実際に観測した例としては、以下のようなデータがある。 (なお、ここでは FBX SDK から出力された可能性の高いデータのみを挙げる。)
FBX 7.4:
fa bc ae 0a d7 ca d3 66 b6 75 f8 86 1a fe 2a 78
fa bc a8 0c d6 c0 dc 60 b7 7c f4 86 1f f7 26 78
fa bc a9 0b d0 c0 dd 67 b1 7c f4 86 1f f7 26 78
FBX 7.3:
fa bc aa 0c d4 c0 dd 65 b1 78 f1 82 1a f3 23 7c
fa bc ab 0f d6 c0 dc 66 b3 78 f1 82 1a f3 23 7c
上位2バイトの [fa bc]
や、末尾バイトの [78]
や [7c]
などを見るに、下位4ビットについても完全な乱数ではなく何らかの規則が存在する可能性は高い。
このフッタを読み飛ばしても、データの解釈に支障はない。
パディング
次のデータであるフッタ2の開始を16バイト境界で整列するためのパディング。 0〜15バイトで可変長。
Blender 2.72b の FBX プラグインは、このパディングを誤った長さで出力することがある。
フッタ2
4バイトの 0x00
である。
用途や意味は不明。
正しくパディング が挿入されていれば、16の倍数位置から始まる。
FBX バージョン
ヘッダ部分の FBX バージョンと全く同じ4バイトのデータ。
フッタ3
120バイトの 0x00
である。
用途や意味は不明。
フッタ4
16バイトの固定データ。
内容は [f8 5a 8c 6a de f5 d9 7e ec e9 0c e3 75 8f 29 0b]
である。
ノード
本節では FBX バイナリ形式におけるノードの表現を解説する。
ノードのデータ構造の概要については『共通の構造』節の『ノード』の項目を参照。
ひとつのノードの (子要素まで含めた) バイナリ構造は以下の通りである。
内容 | サイズ (FBX 7.4 まで) | サイズ (FBX 7.5 以降) | 補足 |
---|---|---|---|
終端位置 | 4 bytes | 8 bytes | |
属性数 | 4 bytes | 8 bytes | |
全属性の合計バイト長 | 4 bytes | 8 bytes | |
ノード名のバイト長 | 1 byte | 1 byte | |
ノード名 | 可変長 | 可変長 | 指定されたバイト長、特別な終端記号なし |
属性 | 可変長 | 可変長 | 0個以上 |
子ノード | 可変長 | 可変長 | 0個以上 |
ノード終端マーカー | 0 または 13 bytes | 0 または 25 bytes | 特定条件を満たしたとき省略される |
これらをグループ化するならば、以下のようになる。
ヘッダ
終端位置
ノードを表現するバイト列の末尾の位置。 正確には、ファイル先頭のバイトの位置を0として、ノードを表現する末尾のバイト列の次のバイトの位置を用いる。
この情報を用いることで、読む必要のないノードをパースせず読み飛ばすことができる。
このフィールドは FBX 7.4 までで4バイト (u32
型)、 FBX 7.5 以降で8バイト (u64
型) である。
属性数
ノードの属性の数。
このフィールドは FBX 7.4 までで4バイト (u32
型)、 FBX 7.5 以降で8バイト (u64
型) である。
全属性の合計バイト長
ノードの全属性の合計バイト長。 これは FBX データ上で占める長さである。 各属性のヘッダのサイズも長さに含め、圧縮された属性は展開せず圧縮済データのままのサイズを用いる。
このフィールドは FBX 7.4 までで4バイト (u32
型)、 FBX 7.5 以降で8バイト (u64
型) である。
ノード名
ノード名のバイト長
ノード名文字列のバイト長。
ノード名は NUL 終端などの特別なルールを持たず、そのままのバイト列で表現される。
このフィールドは1バイト (u8
型) である。
ノード名文字列
ノード名文字列。
ノード名は NUL 終端などの特別なルールを持たず、そのままのバイト列で表現される。
属性
属性はノードに紐付いた情報である。
属性については『属性』節で解説する。
子ノード
ノードは0個以上の子ノードを持つ。
子ノードについては『共通の構造』節の『ノード』節を参照。
ノード終端マーカー
ひとつのノードのバイナリ表現の終端を示すもので、特定条件下で省略される。
このフィールドは FBX 7.4 までで13バイト (u32
型)、 FBX 7.5 以降で25バイト (u64
型) である。
省略条件
ノード終端マーカーが省略される条件は、「ノードが属性を1つ以上持ち、かつ子ノードを持たない」ことである。
これは FBX テキスト形式において子ノード群を囲むための括弧 {}
が省略される条件と等しい。
長さの意味
13バイトや25バイトという長さは、ちょうどヘッダの長さ (すなわち 4+4+4+1 と 8+8+8+1) と同じである。
このマーカーの存在によって、パーサは (正しいデータならば) 理論上は 終端位置情報を記憶せずともノード階層を正しく把握できる。
ノード終端マーカーは最後の子ノードの直後に続くため、子ノードのヘッダを読もうとして13バイトまたは25バイト読み、その全てが 0x00
であるかを確認することで、ノードが終わるか新たな子ノードが続くかを判別できる。
ただし後述の通り複数の出力処理系に不具合が認められるため、ノード階層の把握を終端位置の記憶・照合なしに試みるべきでない。
省略と処理系のバグ
しかし実際には、 Maya 2015 (および FBX SDK 2015.1) や Blender 2.79 および 2.80 などが、ノード終端マーカーを省略すべき場所で誤って出力したり、逆に出力すべき場所で省略したりしている。 そのため、実用上は終端位置を用いなければノード階層を正確に判断することはできないと考えるべきであろう。
以下に参考情報を挙げる。
- ⚓ T71729 Lack of null record on Properties70 node by FBX Exporter
- Blender 2.79 / 2.80 で出力された FBX ファイルで、本来あるべきノード終端マーカーが欠落しているという報告。
- 属性も子ノードも持たない
Properties70
ノードを出力する際、ノード終端マーカーが存在すべきだが Blender が吐いたデータにそれがなかった。 - 最初に本書の著者のプロジェクトへ報告されたことで発覚した: Unable to import fbx from blender 2.79/2.8 · Issue #2 · lo48576/fbxcel
- https://mastodon.cardina1.red/@loliconductor/103510156295709353
Maya 2015 (FBX SDK/FBX Plugins version 2015.1 build=20140408)
で出力された FBX ファイルで、不要なはずのノード終端マーカーが挿入されている。-
具体的には、トップレベルの Objects ノード以下に子ノードを持たないようなノードがある場合 (典型的には CollectionExclusive (DisplayLayer) とか AnimationLayer ノード) に、余計な NULL record (13バイトの0) が吐かれている
— https://mastodon.cardina1.red/@loliconductor/103510165763794263
-
Objects の子は全てが3つくらい属性を持っているので、子ノードを持たない場合は NULL record が存在してはいけないはずなんだけど、どうやら余計に吐かれている。
ノードヘッダに入っているノード終端位置情報が正しいので普通の処理系はこの情報を無視できるんだけど……— https://mastodon.cardina1.red/@loliconductor/103510174844382235
属性
本節では FBX バイナリ形式における属性の表現を解説する。
属性のデータ構造についての概要は『共通の構造』節の『ノード』節を参照。
概要
属性は、以下のように表現される。
値の型
別の節で既に解説したが再掲すると、属性の値の型は以下のいずれかである。
型コード
型コードは属性値の型を表現する1バイトの整数 (u8
) である。
実際には型コードの値には ASCII のアルファベットのみが利用されている。
以下は型コードの一覧である。
型 | 型コード (ASCII) | 型コード (整数) | 命名由来の推測 |
---|---|---|---|
bool | C | 0x43 | Condition の C ? |
i16 | Y | 0x59 | 由来不明 |
i32 | I | 0x49 | Int の I |
i64 | L | 0x4c | Long の L |
f32 | F | 0x46 | Float の F |
f64 | D | 0x44 | Double の D |
Vec<bool> | b | 0x62 | bool の b ? |
Vec<i32> | i | 0x69 | int の i |
Vec<i64> | l | 0x6c | long の l |
Vec<f32> | f | 0x66 | float の f |
Vec<f64> | d | 0x64 | double の d |
Vec<u8> | R | 0x52 | Raw の R ? |
String | S | 0x53 | String の S |
型特有の追加ヘッダ
型特有の追加ヘッダは、配列型のためのヘッダと特殊型のためのヘッダが存在する。 プリミティブ型に追加のヘッダはない。
プリミティブ型
プリミティブ型に追加のヘッダはない。 よって、プリミティブ型の属性は以下のように表現される。
内容 | サイズ | 補足 |
---|---|---|
型コード | 1 byte | ASCII アルファベット |
値 | (型による) | 型コードから明らか |
プリミティブ型の値のサイズは型より明らかである。
値
値は例に漏れずリトルエンディアンで表現される。
配列型
配列型は以下のような追加ヘッダを持つ。
よって属性全体としては以下のようになる。
要素数
配列の要素の個数である。 バイト長ではない。
エンコーディング
配列データ本体の圧縮形式など。 以下は一覧である。
これ以外の値は観測されていないが、 FBX 形式はプロプライエタリであるため他のエンコーディングが存在する可能性は否定できない。 とはいえ実用上はこの2つに対応すれば十分であろう。
0: 非圧縮
無加工。
1: zlib 圧縮
配列は zlib 圧縮されている。 zlib ヘッダが必要である (つまり生の deflate ストリームではない)。
データのバイト長
共通ヘッダと追加ヘッダを除いて、配列データ本体が FBX データ中に占めるサイズ。 配列が圧縮されていれば、展開後でなく圧縮後のバイト長を使う。
データ
素朴に値を並べたものである。 パディングや順序の入れ替えなどは一切ない。
特殊型
特殊型は以下のような追加ヘッダを持つ。
内容 | サイズ | 補足 |
---|---|---|
データのバイト長 | 4 bytes | ファイル中に占める長さ |
よって属性全体としては以下のようになる。
データのバイト長
共通ヘッダと追加ヘッダを除いた、文字列やバイナリデータ本体のバイト長。
データ
文字列やバイナリデータ本体。 圧縮などは一切行われず、生の値がそのまま利用される。
文字列データは、 NUL 終端なども不要の素朴な文字列である。
また、印字不可能な文字などが利用されることがある。
たとえば \x00
(ASCII コード 0x00
) や \x01
(ASCII コード 0x01
) が特定の文脈で実際によく出現する。
データが文字列のとき、符号化形式は通常 UTF-8 である。
Maya から出力されたらしい FBX バイナリファイルで文字列として UTF-16 文字列が用いられているデータを観測したことがあるが、 ASCII 文字の範囲内であったため、 UTF-8 としても妥当な文字列であった。 そのため、 UTF-8 として妥当でないバイトシーケンスが文字列値として許されているのかは不明である。 また、そもそもこのような異常な属性値が混入することは稀であり、そのような属性値はそもそも一般の外部プロセッサで解釈することを想定していないと思われるため、無視してよいだろう。
テキスト文法
本節では、 FBX テキスト形式の文法について解説する。
本節での解説は網羅的でない、また正確でない可能性が高いことに留意せよ。 つまり、本節で説明されていない文法が許されていたり、本節で説明した文法が公式 SDK で読めない文字列を含む可能性がある。
基本
以下は大雑把な文法を ABNF で表現したものである。 これらが正確とは限らない (むしろ、おそらく誤りがある) ことに留意せよ。
ALPHA = %41-5A / %61-7A ; A-Z / a-z
DIGIT = %x30-39 ; 0-9
ALNUM = ALPHA / DIGIT ; alphanumeric
HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F"
; hex digit
DQUOTE = %x22 ; " (double quote)
HTAB = %x09 ; horizontal tab
LF = %x0A ; line feed
WSP = SP / HTAB ; white space
file = *(comment / node / LF)
comment = *WSP ";" *(ALNUM / WSP) LF
node = *WSP name ": " [attributes] " {" LF children *WSP "}" LF
/ *WSP name ": " attributes LF
name = ALPHA *ALNUM
attributes = attr-value *("," [WSP] attr-value)
attr-value = attr-boolean / attr-number / attr-array / attr-string / attr-binary
attr-boolean = "T" / "Y"
integer = 0 / %x31-39 *DIGIT
attr-number = ["-"] integer ["." integer / "E" ["-"] integer]
attr-array = "*" integer " {" LF *WSP "a: " [attr-array-elem *("," attr-array-elem)] [","] LF *WSP "}"
attr-array-elem = attr-boolean / attr-number
string-content-char = %x00-21 / %x23-25 / "&" ("cr" / "lf" / "quot") ";" / #x27-FF
attr-string = DQUOTE *string-content-char DQUOTE
base64-char = ALNUM / "+" / "/"
base64-chunk = *(4*base64-char)
base64-chunk-final = base64-chunk [
base64-char "==="
/ 2*base64-char "=="
/ 3*base64-char "="
/ 4*base64-char
]
attr-binary = *(DQUOTE base64-chunk DQUOTE "," LF SP) DQUOTE base64-chunk-final DQUOTE [","]
; Note that this may have trailing comma.
children = *comment node *(node / comment)
ファイル
ファイルはノードとコメントを並べたものである。 コメントはおそらく任意だが、 FBX SDK が出力したファイルの先頭には以下のようなコメントが付いている。
FBX 7.3 の場合:
; FBX 7.3.0 project file
; Copyright (C) 1997-2010 Autodesk Inc. and/or its licensors.
; All rights reserved.
; ----------------------------------------------------
FBX 7.5 の場合:
; FBX 7.5.0 project file
; Copyright (C) 1997-2010 Autodesk Inc. and/or its licensors.
; All rights reserved.
; ----------------------------------------------------
これが信頼性のあるファイル形式判別に利用できるかは不明。
コメント
;
は行コメント開始記号であり、行末までが無視される。
文字列値中に出現した ;
は、コメント開始記号ではなく単なる文字列の一部として解釈される。
ノード
ノードのテキスト表現には2種類の形式があり、条件次第でどちらを利用すべきかが決まる。
属性がないか子ノードを持つ場合
ひとつは通常の形式で、以下のような形である。
NodeName attr1, attr2, attr3, ... {
; ここに子ノード
}
子ノードは適宜タブ文字などでインデントされているが、これが必要なものかは不明。
この形式は、属性を1つも持たないか、または子ノードを1つ以上持つようなノードで利用される。 属性を持たない場合、以下のようになる。
NodeName {
; ここに子ノード
}
FBX SDK から出力されたデータでは、ノード名と {
の間に空白文字が2つ入っている。
これが読み込みでも2つである必要があるかは不明。
子ノードを持たない場合、以下のような形になる。
NodeName attr1, attr2, attr3, ... {
}
{
と }
の間に改行が必ず入る。
属性を持ち子ノードがない場合
もうひとつは省略された形式で、以下のような形である。
NodeName attr1, attr2, attr3, ...
{
〜 }
が省略された形である。
この形式では属性は必ず存在する。
属性
属性には大雑把に以下の3種類がある。
真偽値
真偽値は ASCII アルファベット1文字で表現される。 この対応関係は FBX バイナリ形式と同じである。
論理的な値 | 文字 |
---|---|
false | T |
true | Y |
T
と Y
の由来が何なのかは不明。
数値
数値は整数であるか浮動小数点数であるかに関係なく、共通の10進数記法で表現される。
すなわち、 0
のような値があったとき、スキーマなしにこれが整数であるか浮動小数点数であるかを判別する術はない。
0
, 42
, -0
, 123E-12
のような記法が観測されている。
-0
や 123E-12
のような記法は浮動小数点数由来のものと思われるが、これを整数としてパースすべきかは不明。
123E-12
はおそらく 123×10-12 である。
配列
配列は、擬似的にノードのように表現される。
以下は、属性として12要素の数値の配列ひとつだけを持つ Edges
という名前のノードの例である。
Edges: *12 {
a: 0,1,2,3,4,5,6,7,8,10,14,18
}
a
は array の略だろうか。
a:
以降に並べられる値は、末尾にコンマを持つことも持たないこともある。
規則があるかは不明。
文字列
文字列は "foo bar"
のように、 ASCII の半角ダブルクォートで括られた形で表現される。
一部の文字についてエスケープが行われる。
元の文字 | 元の文字の ASCII コード | エスケープ後文字列 |
---|---|---|
" | 0x22 | " |
LF | 0x0a | &lf; |
CR | 0x0d | &cr; |
これら以外の文字列についてはエスケープ等は行われない。
少なくとも ASCII 範囲 (0x00
〜 0x7f
) では FBX SDK 2018.1 で上記のもの以外のエスケープが行われないことを確認した。
UTF-8 でエンコードされた日本語の文字列なども、エスケープなしにそのままテキストファイル中に出現する。
ここで注意すべきなのは、少なくとも FBX SDK 2016 では文字 &
自体がエスケープされないことである。
これが仕様か実装のバグかは不明だが、 "
のような文字列自体を含む文字列は、書き込んだ値と異なる値が読まれることになる。
たとえば esc"esc, raw"raw
という文字列を FBX テキスト形式で出力すると、ファイル上では "esc"esc, raw"raw"
のような表現になる。
これを再度読み込むと、 esc"esc, raw"raw
という文字列になる。
バイナリ
FBX テキスト形式でバイナリデータを保持する必要のある場面は極めて限定的である。
バイナリしか持たない FileId
のようなノードはそもそもテキスト形式では存在しないし、テクスチャ等の埋め込みはそもそもテキスト形式では不可能である。
ユーザ定義のプロパティとしてバイナリを与えたとき、バイナリデータは配列と類似した、擬似的なノード風の構文で表現される。
P: "MyBlob", "Blob", "", "U",8 {
BinaryData: "cG95b3BveW8="
}
データ部は base64 でエンコードされた文字列である。
62 と 63 用の記号としては +
と /
が用いられる。
文字列が長すぎる場合、 base64 エンコード後の文字列で4の倍数文字ごとに分割が行われる。 以下は分割の例である。 (通常4文字毎での分割は行われず、3584文字などそこそこ長い単位で分割される。)
P: "MyBlob", "Blob", "", "U",8 {
BinaryData: "cG95",
"b3Bv",
"eW8="
}
分割後の2行目以降の各行先頭に空白文字がひとつ入っている。 これが必要かは不明。
分割されていた場合、最終行末尾のコンマが許されるかは不明。
スキーマ
本節では、文法に従って読み取られた低レベルな構造を 3D コンテンツとして解釈する方法や、構造への意味の割り当てなどを解説する。
TODO: これから書く。
参考文献
- FBX binary file format specification — Blender Developers Blog
- FBX 7.4 のバイナリ形式の文法について網羅的な解説。
- 本書ではこの記事とは一部異なる用語を用いている (特に node record, null record, property など)。
- 一部に古い情報 (FBX 7.5 でヘッダが変化したことなど。コメントで指摘されている) や誤った情報 (真偽値のバイナリ表現について) がある。
- FBX SDK のリファレンス
- バージョン毎に URI が異なる、スクリプトやヘッダ・フッタマシマシで滅茶苦茶重いなどいろいろ難があるが、一応公式 SDK の公式ドキュメント。
- 基礎的な概念が解説されていることがあるので、 (C++ リファレンスではない方の文書に) 一通り目を通すことをおすすめしたい。
- A quick tutorial about the FBX ASCII format – Banex Developer Blog
- FBX テキスト形式の文法とスキーマの一部についての解説。
ReferenceInformationType
とMappingInformationType
についての解説が図付きで特にわかりやすい。 ジオメトリ (メッシュ) を解釈したいならこの図は必見。