Upgrade to Pro — share decks privately, control downloads, hide ads and more …

エーッ まだFBX読んでる人なんているんですか!? / Who on earth are reading FBX!?

エーッ まだFBX読んでる人なんているんですか!? / Who on earth are reading FBX!?

長らく FBX を読んでいたのでその辺りの話。

東工大ロボット技術研究会 2019年度前期研究報告会 (2019-07-14) 発表資料。

speakerdeck だとスライド中のリンクが使えないので、 https://nextcloud.cardina1.red/s/Aae8AqRHnBs5RLn?path=%2Fpresentations にも上げてあります。

More Decks by らりお・ザ・何らかの天然水ソムリエ

Other Decks in Technology

Transcript

  1. エーッ まだ FBX 読んでる人なんているんで すか!? らりお 2019-07-14 東工大 ロボット技術研究会 1

  2. いません。

  3. 概要 • 成果物紹介 • 実装の流れ • API デザイン • parser

    • loader • FBX 解説 • 展望 2
  4. だれ Figure 1: らりお • 情報工学系 M2 • CG-SQUARE •

    ゲーム完成したことなし • 数年間 FBX 読んでた • 一応 3D 勢 • 静的型付き言語が好き • C++ わからん • Rust はいいぞ • ライブラリ設計が好き • ライブラリばかり作りがち Contact: @lo48576@mastodon.cardina1.red 3
  5. 成果物紹介

  6. fbx_object_depviz • https://github.com/lo48576/fbx_objects_depviz • FBX の高レベルデータ構造を図で吐き出すユーティリティ • FBX の低レベルデータ構造を自前パーサで読む •

    オブジェクト情報と関係を graphviz の dot 形式で吐き出す • FBX のつらさの一端が垣間見える 4
  7. fbx_object_depviz 5

  8. fbx-tree-view • https://github.com/lo48576/fbx-tree-view • FBX の低レベルデータ構造を GUI で閲覧できるユーティリ ティ •

    FBX SDK のドキュメントに無限にリストアップされているク ラス一覧と併せて使うと、深みのある絶望感を味わえる 6
  9. fbx-tree-view 7

  10. fbxcel • https://github.com/lo48576/fbxcel • “Excellent FBX library for Rust” •

    ほんまか? • FBX の低レベルデータ構造を操作できる • 要するに parser • 一応現状で安定している 8
  11. fbxcel-dom • https://github.com/lo48576/fbxcel-dom • FBX の高レベルデータ構造を操作できる • API 設計を弄るとき parser

    と同じライブラリで互換性破壊を繰り返 すのが嫌だったため、 fbxcel から分離 • 破壊的変更を繰り返す可能性が高い 9
  12. fbx-viewer • https://github.com/lo48576/fbx-viewer • FBX ファイルを、人間にとって最も自然な形で可視化するプ ログラム • FBX のロードには

    fbxcel-dom を利用 • 3D バックエンドは vulkano ライブラリ (Vulkan ラッパー) 10
  13. fbx-viewer 11

  14. その他 (破棄した進捗) (1) • fbx_direct • 昔 rogy ゼミで発表した FBX

    parser (1 作目) • 設計が気に入らなくなったので捨てた (後述) • 一応メンテは受け付ける • fbx-binary-reader • FBX parser (2 作目) • 設計が気に入らなくなったので捨てた (後述) 12
  15. その他 (破棄した進捗) (2) • fbx-load • FBX loader (1 作目)

    • 設計が気に入らなくなったので捨てた (後述) • fbxcel (< 0.0.3) • FBX parser (3 作目), FBX loader (2 作目) • fbxcel の前身 • 設計が気に入らなくなったので最初から書き直した (後述) 13
  16. 実装の流れ

  17. FBX を知る (1) • ググる • Blender 開発者のブログや wiki などが良質

    • https://archive.blender.org/wiki/index.php/User: Mont29/Foundation/FBX_File_Structure/ • https://code.blender.org/2013/08/fbx-binary-file-format-specification/ • 誤りがあったり不完全だったりするので、信用しすぎては いけない 14
  18. FBX を知る (2) • いい感じのサンプルデータを入手 • 今は亡き中野シスターズ • https://web.archive.org/web/*/http://nakasis.com/ •

    ライセンスがユルいので重宝 • Blender のデフォルトシーンを FBX でエクスポートしたもの • バイナリを眺める • Vim はいいぞ 15
  19. FBX を知る (3) • FBX SDK のリファレンスを読む • JS がめちゃくちゃ重い

    • 無駄なスタイルがゴテゴテで画面が狭い • ページ内リンクが javascript で実装されていたりする • アドレスバーが変化しないので、リンクをメモするのが面倒 • 同サイト内なのにリンクが切れてたりする • doxygen の使い方がなっていないので、 enum のドキュメントがズレ ていたりする • 継承とオーバーロードだらけなので有用なメソッドを探すのが 面倒 • etc. 16
  20. パーサを書く • Vim でバイナリを眺めつつ、Blender Developers Blog の記事を 参考に • FBX

    形式のマイナーバージョンアップで互換性がなくなるこ ともあるけど頑張ってね♥ • 設計が気に入らなくなったら何度でも書き直す 17
  21. データ構造可視化ツールを書く • ご存知でしたか? 一般に 3D データは Vim で閲覧するもので はありません •

    本題でなくとも、有用ならば周辺ツールの実装の手間を惜 しまない • 自動化、省力化は大事 • 実際、後の実装やデバッグで大変役立った 18
  22. 情報のアウトプット • rogy ゼミ • FBX 解説 (1: 構文編) (1)

    • 第 2 部自作ライブラリ紹介 • rogy blog • コミケも終わったので落ち着いて進捗しましょう: 東京工業大学 ロボット技術研究会 • FBX データの可視化した (ただし表示ではない) : 東京工業大学 ロ ボット技術研究会 19
  23. ユーザコードの実装、問題ドメイン確定 ライブラリがいい感じになってきたら: • そのライブラリを使うコードを書く • 必要な機能を発見できる • 大雑把なパフォーマンスを把握できる • クソ設計に気付ける

    • ライブラリでサポートする機能の範囲を決める • ライブラリの階層化を恐れない • ライブラリのためのライブラリを作ったって良い 20
  24. 進捗アピール • 進捗アピールをする • 「いい感じに動きそう」という段階がピーク • 心理的余裕があるのでアピールに労力を割ける • マイナス進捗で全てが吹き飛ぶ前に、存在の証を刻んでおく •

    おもしろい事故などが発生したら画像も • #3drenderingaccident ハッシュタグに期待 (?) 21
  25. スパイラル • 気に入らなくなったら容赦なく作り直す • さもなくば、未来永劫「なんじゃこのクソ設計!」と過去の自分 に憤ることになる • ユーザが増える前の方が、大きな変更を入れやすい • 作ってみないと対応を決意できない要件や気付けない落と

    し穴は実際ある • 未然に対処しようとすると、無意味に極端な抽象化になってしまう そしてマイナス進捗へ…… 22
  26. API デザイン

  27. 「設計が気に入らない」 • 速度が気に入らない • 場合によっては不要になるメモリ確保が気に入らない • 悪意ある入力への弱さが気に入らない • 致命的でないエラーのハンドリングが気に入らない •

    拡張性の低さが気に入らない • 入力の制約が気に入らない • エラー情報の少なさが気に入らない • etc. 23
  28. ケーススタディ 1: 素朴な設計 • 状況 • FBX の木構造はノード開始・終了イベントの列として表現できる • 各ノードはノード名を持つ

    • 各ノードはそれぞれ属性の列を持つ • 素朴な設計 • parser はイベントを返すイテレータ • pull 型パーサ • イベントはノード開始か終了か、ドキュメント終了 • ノード開始イベントはノード名と属性列を持つ 24
  29. ケーススタディ 1: 要求 • 全てのプログラムが FBX データの全てを使うわけではない • 読み飛ばしたい箇所がある場合も多い •

    作者情報だけ取得したいとか • 画像だけ抽出したいとか • 使わない部分までパースすると無駄 • パースの過程で zlib 圧縮データの展開など必要になることも • ファイルの場合、不要なディスクアクセスが発生 • 使うノードでも、最初のいくつかの属性を見てから読み飛ばす場 合も • ノード名を無視して属性だけ見たい場合もある 25
  30. ケーススタディ 1: 選択 • ノード開始イベントはノード名や属性を読むための I/O ラッ パーとして振る舞う • ノード名と属性列を直に持たない

    • 要求があって初めて、ノード名や属性列をパースする • 属性列も、配列でなく局所的な parser で返す • 属性列 parser で必要に応じて 1 つずつ値を読む 結果: • 値を知る必要のない文字列や属性値のためのメモリ確保が 不要になる 26
  31. ケーススタディ 2: 素朴な設計 • 状況 • ノード属性は、スカラー、配列、文字列、バイナリのいずれか • 素朴な設計 •

    ノード属性 parser は、これらのいずれかの値を持つような型を返す 27
  32. ケーススタディ 2: 要求 • バイナリ属性値は、単体で数 MB 級の可能性あり • テクスチャ埋め込みなど •

    読み飛ばしたい場合、メモリ上に置くのは I/O とメモリの無駄 • 配列やバイナリはストリーム処理を施す場合あり • テクスチャは一度デコードしてから GPU に転送する • デコード前の画像データはメモリに置く必要なし • 頂点配列を double でなく float の配列で欲しい場合 • 頂点配列は FBX では double の配列 • 全部メモリに置いて一気に変換ではなく、読みながら float に直せば良い • メッシュの bounding box を計算したい場合 • 頂点配列をメモリに置く必要なし 28
  33. ケーススタディ 2: 選択 • 属性列 parser は属性値そのものでなく、属性値リーダを返す • スカラの場合は値そのもの •

    配列の場合はイテレータ • 文字列やバイナリの場合は I/O オブジェクト • あたかもファイルのように読み込める • 画像ライブラリと相性が良い 結果: • 大きなデータもメモリにロードせず処理可能に • 読み飛ばしもほぼノーコストに • ストリーム処理に既存のライブラリなどを活用可能に 29
  34. ケーススタディ 3: 素朴な設計 • 状況 • FBX データには致命的でないエラーや誤ったデータがありうる • 昔の

    Blender などの吐く FBX (詳細は過去資料参照) • 3D データを表示したいだけなら、誤りの修正は手動でしたくない • どういうエラーがありうるかなんて知らんがな • 素朴な設計 • 致命的でないエラーはエラー扱いせず対処 • 多少壊れたデータでも読めた方が嬉しい 30
  35. ケーススタディ 3: 要求 • 検査器や修正プログラムを作りたくなったらどうする • 致命的でないエラーも全て検出したい • 本家の FBX

    SDK で欠落するような破損データはどうする • 値を取ってしまうと FBX SDK と挙動が変わる • 値を捨てるとデータ作成者の意図と挙動が変わる 31
  36. ケーススタディ 3: 選択 • 致命的でないエラー (警告, Warning) は warning handler

    に渡す • warning handler が警告を致命的とするか否かを決定する • ついでにログ出力などもできる • デフォルトでは警告を無視 • 警告は簡単にエラーに変換可能 • しかし致命的エラーを無視させることはできないので安全 結果: • エラー時の挙動を柔軟に制御できるようになった • 考えたくなければデフォルトをそのまま使える • 処理の途中で方針を切り替えることさえできる 32
  37. ケーススタディ 4: 素朴な設計 • 状況 • FBX の高レベルデータ構造は可算個の種類がある • オブジェクトの種類や状況で可能な操作は異なる

    • 素朴な設計 • それぞれのデータ構造毎にライブラリ側で型を用意する • 不正なデータからは目的の型を作れずエラーとする • RAII などで標準的なやりかた 33
  38. ケーススタディ 4: 要求 • FBX SDK で確認できるデータ構造は完全ではない • FBX はプロプライエタリ

    • 把握されていないフィールドや制約がありうる • 可算個の型を実装するには大変手間がかかる • ユーザが独自のインターフェースを実装したがるかも • ライブラリで未対応のマイナーなデータ構造とか • カスタムデータ構造とか • 同じフィールドを何度も読むことはそうそうない • 頻繁に使う情報は別の型や配列に変換・整理されやすいはず 34
  39. ケーススタディ 4: 選択 • 高レベルデータ構造の解釈をできるだけ遅延する • 特殊な意味の込められていない一般的な型の wrapper を多数用意 •

    ラッパーは情報へのアクセスを提供 • アクセスのたびに解釈を行う 結果: • ユーザによる親和性の高い wrapper の作成が可能に • 実際に利用しない部分でのエラーが無視できるように 35
  40. その他の工夫 (1) • エラーや警告が発生した場所情報を取得可能 • バイト位置だけでなく、ノードのパスや属性の番号も • シーク可能な入力については更に効率化 • 更に高速な読み飛ばし

    • シーク可能な部分的なバイナリ属性値読み込み • 属性値を特定の型として読むための抽象の提供 • 元データの型ではなく、変換先の型が記述されるべき 36
  41. その他の工夫 (2) • 将来的に FBX 形式の互換性が壊れる場合への事前の対処 • 別バージョンの parser を簡単に統合できるようにしてある

    • 悪意ある入力への耐性 • 木構造の再帰的データをコールスタックで処理しない • ヒープ上のベクタで閉じられていないノードを明示的に管理 • メモリを食い尽くす以外でクラッシュさせられない • 並列処理しやすいデータ構造 • 低レベルデータ構造を素朴な木でなくアリーナで管理 • たとえば複数のボーンを並列に解釈するなどが可能 37
  42. 決断の方針 • あらゆる処理はユーザの意志で能動的に行われるべき • プル型にしない、プッシュ型にする • I/O や変換など、コストのかかる処理は明示的に実行 • ストリームはストリームのまま提供

    • イベント列、属性列、スカラの配列、バイト列…… • せずに済むことをしない • ロードや解釈は可能な限り遅延 • エラーの発生も実際の利用まで遅延 • 使わないなら気付かない • cf. ゼロ・オーバーヘッド原則 38
  43. その他の知見 (1) • 想定用途で頻繁でない処理では、オーバーヘッドを許す • 警告は (通常のイベントほど) 頻繁ではないと想定 → warning

    handler は型消去してよい • 警告やエラー位置の取得も頻繁には発生しないと想定 → エラー位置データでの複数のメモリアロケーションを許す • テストでは羃等性を活用する • 書いて読んだら一致するか? • 読んで書いたら一致するか? • ログを沢山吐く • 最低レベル (trace) のログはビルド設定で消すことさえできる 39
  44. その他の知見 (2) • assert を多用する • 制約を表現する • コンパイラによる最適化を促進する •

    仕様についての誤解が (クラッシュによって) 明示される • 知らない仕様を常に想定する • 完全に新しいバージョン • 高レベルで互換性があり低レベルで互換性のない更新 • 隠された仕様 • 暗黙に非推奨となった仕様 • 処理系独自拡張 40
  45. その他の知見 (3) • 依存ライブラリには躊躇わずパッチを送る • WIN-WIN • ライブラリの (モジュールや関数の) ドキュメントに使用例

    まで含めしっかり書く • もしそこに書きづらい場合、仕様や用法が複雑すぎる可能性がある • Rust だと、ドキュメント中のサンプルコードがテストにもなるの で便利 41
  46. 愚痴

  47. FBX SDK のドキュメントが読みづらい (1) • よくズレていた enum の説明 • もしかして最近諦めて型のコメント中にリストを書くように

    なった? • 豊富なヘッダとフッタ • 20 年前のツールバーてんこもりの IE がフラッシュバックする • MOJIBAKE の例 • 42
  48. FBX SDK のドキュメントが読みづらい (2) • JavaScript が余計なことをする • 無限に列挙される fbxsdk::FbxNew()

    のバリエーション • 全 47 種の fbxsdk::FbxNew() をお楽しみください (白目) • いろいろな型のドキュメントで毎度列挙されている • 他いろいろ • 詳しくは実際に使ってみてね • これ以上つらいこと考えたくない 43
  49. FBX SDK は無限に継承を使っている • 継承前提のデータ構造は極めてつらい • (最近別件でも思い知らされた) • データ構造を OOP

    に合わせるな • パラダイムの違う言語で扱いづらい 44
  50. FBX は FBX SDK での型名を利用する • Definitions 下の ObjectType 下の

    PropertyTemplate ノー ドが、 C++ での型名で適用先オブジェクトを指定している 模様 • C++ での型名と思わしきデータが FBX 中に存在 • FbxNull とか FbxAnimLayer とか FbxFileTexture とか • Definitions 下の ObjectType 下の PropertyTemplate ノード • C++ での型名で適用先オブジェクトを指定している模様 • FBX を読むには、 FBX SDK で提供される型を把握する必要 あり? 45
  51. 前処理が無限に必要なデータ群 • 手元の FBX のメッシュに四角形とか五角形とかが入っていた • 仕方ないので三角形分割アルゴリズムを自前で書いた • マテリアルとメッシュが多対多 •

    メッシュをマテリアル毎に自前で分割した • 頂点、法線、色、マテリアル、 UV 、およびそれらのインデッ クスは別々に dedup されうる • みんな配列長が違ったりして地獄 • 頑張ってマッピングを計算して展開 • ミスると事故画像 46
  52. やたら柔軟 • マテリアルの種類が lambert, phong, unknown の 3 種類存在 •

    まあわかる • マテリアルが unknown のとき、シェーダとして外部ファイルを参 照するらしき XML が入っていたデータがある • しかも UTF-16 • 一体どうしろと 47
  53. Rotation の計算が面倒 • FBX SDK Help: Computing transformation matrices •

    オブジェクトの回転の表現に必要なもの: • parent transform, translation, rotation offset, rotation pivot, pre-rotation, rotation, scaling offset, scaling pivot, scaling. 加えて、次のものの逆 (inverse): post-rotation, rotation pivot, scaling pivot. • 回転: • ParentWorldTransform ∗ ∗ Roff ∗ Rp ∗ Rpre ∗ ∗ Rpost−1 ∗ Rp−1 ∗ Soff ∗ Sp ∗ ∗ Sp−1 • 意味はわかるがひたすら面倒 • それぞれがノード属性として保持される • いったい何個のアクセサが必要になるやら 48
  54. 未来

  55. 展望? • ない。 • とにかく面倒すぎるため。 • 先述の rotation の計算で心折れた •

    アニメーションはきっともっと面倒…… • メンテはする • FBX 形式の新バージョンが出たら parser くらいは対応させても良い • 要望があれば多少は頑張る • プルリクがあればレビューと受け入れはする • なんなら誰か引き継いでくれても良い • これから何か作るなら glTF 形式を使いたい • Vulkan を崇拝し、 Khronos に祈りを 49
  56. 未来 • シャカイは Windows と C++ と FBX でできている •

    強く生きよう • 私は Linux と Rust と glTF で生きていきたい 50
  57. おわり

  58. LICENSE このドキュメントはクリエイティブ・コモンズ 表示 4.0 国際ライ センスの下に提供されています。 51