ufbx Documentation(Japanese)
ufbx Documentation(Japanese)
- OverView
- Getting started
- Elements
- Elements(要素) — ufbx ドキュメント日本語訳
- Nodes(ノード)
- Meshes(メッシュ) — ufbx ドキュメント日本語訳
- 🌀 Animation(アニメーション)
- 🦴 Deformers(デフォーマー)
- FBX
- Advanced
- Reference
OverView
ufbx
🧩 ufbx — 単一ソースファイルの FBX ローダー
ufbx は、単一のソースファイルで構成された FBX ローダーです。
このライブラリは、FBX ファイル形式を簡単に扱える ユーザーフレンドリーなインターフェースを提供することを目的とし、必要に応じて 高度な機能にも完全対応しています。
特徴
- 単一ファイル構成:C99 / C++11 に対応した1つのソースファイルで完結
- 堅牢なエラーハンドリング:無効なファイルやメモリ不足などにも安全に対応
- 型安全なAPI:C++およびRustバインディングでは境界チェック付きスライスを使用
- 徹底したテストとファジング(fuzzing) による高信頼性
💾 ダウンロードと使用例
ufbx_load_opts opts = { 0 }; // 任意設定、デフォルトを使う場合は NULL を渡す
ufbx_error error; // 任意、エラーを無視する場合は NULL を渡す
ufbx_scene *scene = ufbx_load_file("my_scene.fbx", &opts, &error);
if (!scene) {
fprintf(stderr, "読み込みに失敗しました: %s\n", error.description.data);
exit(1);
}
// 読み込まれた `scene` を自由に利用できます。
// これはただのプレーンなデータ構造です!
// 例: シーン内のすべてのオブジェクトを列挙して表示
for (size_t i = 0; i < scene->nodes.count; i++) {
ufbx_node *node = scene->nodes.data[i];
if (node->is_root) continue;
printf("オブジェクト: %s\n", node->name.data);
if (node->mesh) {
printf("→ メッシュ(%zu 面)\n", node->mesh->faces.count);
}
}
ufbx_free_scene(scene);💡 補足
上記コードをコンパイルすれば、任意の .fbx ファイルを読み込み、ノード(オブジェクト)やメッシュ情報を列挙できます。scene 構造体には、マテリアル、トランスフォーム、アニメーションなどの詳細情報も格納されています。
Getting started
Getting started
Setup
C / C++
ufbxはシングルソースライブラリなので、必要なのは2つのファイルだけ( ufbx.c and ufbx.h)です。最も簡単な方法は、https://github.com/bqqbarbhg/ufbxからダウンロードすることです。マスターブランチには、ライブラリの最新の安定バージョンが含まれています。
単一のヘッダーライブラリとは異なり、残りのコードと一緒にコンパイルする必要があります。あるいは、#include "ufbx.c"で単一のファイルでコンパイルすることも可能です。
ufbx はlibc 外部に依存しませんが、C 標準数学ライブラリ-lmをリンクするために渡す必要がある場合があります。
gcc -lm ufbx.c main.c -o main
clang -lm ufbx.c main.c -o mainシーンの読み込み
ufbxを使い始めるには、まずシーンをロードする必要があります。シーンをロードした後は、返されたufbx_scene構造体を調べるだけで、かなり先の処理を行うことができます。ファイルからシーンをロードし、シーン階層内のオブジェクトを表すすべてのノードの名前を出力してみましょう。
#include <stdio.h>
#include "ufbx.h"
int main()
{
ufbx_scene *scene = ufbx_load_file("my_scene.fbx", nullptr, nullptr);
for (ufbx_node *node : scene->nodes) {
printf("%s\n", node->name.data);
}
ufbx_free_scene(scene);
return 0;
}上記の例では、シーンをデフォルトオプションでロードし、エラー処理に関してはかなり厳格に行いました。これを修正するには、ロード中に発生したエラーに関する情報を取得するためにufbx_errorを使用します。
オプションを渡すこともできますufbx_load_opts。FBXシーンには様々な座標系と単位系があり、ufbxは読み込み時にそれらを正規化することをサポートしています。ここでは、1メートル単位の右手Y軸上座標系を要求しています。
#include <stdio.h>
#include "ufbx.h"
int main()
{
ufbx_load_opts opts = { };
opts.target_axes = ufbx_axes_right_handed_y_up;
opts.target_unit_meters = 1.0f;
ufbx_error error;
ufbx_scene *scene = ufbx_load_file("my_scene.fbx", &opts, &error);
if (!scene) {
fprintf(stderr, "Failed to load scene: %s\n", error.description.data);
return 1;
}
for (ufbx_node *node : scene->nodes) {
printf("%s\n", node->name.data);
}
ufbx_free_scene(scene);
return 0;
}必要に応じて、 を使用してメモリからシーンをロードしufbx_load_memory()たり、 を使用してカスタム ストリームをロードすることもできますufbx_load_stream()。
データ型
ufbx はシーンをデータとして表現することを目的としているため、返されたデータを調べるだけで多くのことを行うことができますufbx_scene。
多くのCライブラリとは異なり、ufbxは長さ情報のない生のポインタを公開しないため、C以外の言語でも境界チェックが可能です。ufbx内の可変長データはすべて、 以下の型で表現されます。
// UTF-8 encoded string of `length` bytes, always NULL terminated
struct ufbx_string {
const char *data;
size_t length;
};
// Arbitrary binary data of `size` bytes
struct ufbx_blob {
const char *data;
size_t size;
};
// List of `count` objects of type T
struct ufbx_T_list {
T *data;
size_t count;
// Bounds-checked indexing and iterator support in C++
T &operator[](size_t index) const;
T *begin() const;
T *end() const;
};ufbx_string、ufbx_blob、または ufbx_T_list に含まれていないすべてのポインタは、
1つのオブジェクトを指す か、あるいは ufbx_nullableが指定されている場合には NULL である可能性があります。
逆に言えば、ufbx_nullableが付いていないポインタは、必ず 1つの有効なオブジェクト を指すことが保証されています。
デバッグ時にこれらの構造体を視覚的に確認しやすくするために、ufbx.natvis をダウンロードして利用できます。
これは少なくとも MSVC(Visual Studio)および VS Code でサポートされています。
Elements
Elements(要素) — ufbx ドキュメント日本語訳
概要
ufbx のほぼすべては、struct ufbx_element のような「基底クラス」風の表現を持つ 要素(element) で構成されます。要素は name(名前)や、FBX のキー/値プロパティのための props(プロパティ集合)といった共通プロパティを含みます。
ufbx_node、ufbx_mesh、ufbx_material といった「派生」型は、無名 union として ufbx_element ヘッダを埋め込んでおり、ufbx_element へキャストしたり、ufbx_element の共通プロパティへ直接アクセスできます。
例(C)
void list_nodes(ufbx_scene *scene)
{
for (size_t i = 0; i < scene->nodes.count; i++) {
ufbx_node *node = scene->nodes.data[i];
printf("Node: '%s'\n", node->element.name.data);
// 同等の省略形:
printf("Node: '%s'\n", node->name.data);
}
}Element IDs(要素ID)
ufbx_scene は利便性のためにネストしたポインタ構造をとりますが、識別子(ID)を使えると便利なことがよくあります:
ufbx_element.element_id:シーン全体で一意な連番インデックス(scene.elements内)ufbx_element.typed_id:同一種別内での連番インデックス(例:scene.nodes内)
これらのインデックスを使えば、ufbx_scene 内の配列をインデックス指定で取り出せます。
C
assert(node == scene->nodes.data[node->typed_id]);
assert(node == (ufbx_node*)scene->elements.data[node->element_id]);
assert(mesh == scene->meshes.data[mesh->typed_id]);
assert(mesh == (ufbx_mesh*)scene->elements.data[mesh->element_id]);C++
assert(node == scene->nodes[node->typed_id]);
assert(node == (ufbx_node*)scene->elements[node->element_id]);
assert(mesh == scene->meshes[mesh->typed_id]);
assert(mesh == (ufbx_mesh*)scene->elements[mesh->element_id]);Rust
use std::ptr;
assert!(ptr::eq(node, scene.nodes[node.element.typed_id]));
assert!(ptr::eq(&node.element, scene.elements[node.element.element_id]));
assert!(ptr::eq(mesh, scene.meshes[mesh.element.typed_id]));
assert!(ptr::eq(&mesh.element, scene.elements[mesh.element.element_id]));これらのインデックスは、同じファイルを複数回読み込む範囲では安定していますが、ファイルを再エクスポートしただけでも必ずしも安定とは限りません。
Properties(プロパティ)
FBX は各要素に対して汎用的なキー/値のプロパティを持っており、ufbx はそれを ufbx_element.props 経由で公開します。多くの場合 ufbx はこれらを内部でフィールドへ解釈します(例:ufbx_node.local_transform、ufbx_light.intensity)。ただし、以下のようなケースでは ufbx_props を直接使うとよいでしょう:
ufbx_anim_curveを用いたアニメーションカーブの手動解釈- ユーザー定義のカスタムプロパティ
- 非標準なマテリアル定義の取り扱い
ufbx は FBX の値を直感的に見えるよう多くの補正を行いますが、ufbx_props を直接読む場合には FBX 固有のクセ に注意してください。例えばライトには "Intensity"(強度)というプロパティがあり、FBX はしばしば DCC ツールで入力した値の 100 倍 を格納します。ufbx はこのクセを緩和するため 値を 100 で割る 補正を試みますが、FBX プロパティを直接読むと期待と異なる結果になることがあります。
C 例(Intensity を比較表示)
void print_intensity(ufbx_light *light)
{
// `light->props` は `light->element.props` の省略形
ufbx_prop *prop = ufbx_find_prop(&light->props, "Intensity");
assert(prop);
printf("ufbx_light.intensity: %.2f\n", light->intensity);
printf("ufbx_props.Intensity: %.2f\n", prop->value_real);
}C++ 例
void print_intensity(ufbx_light *light)
{
// `light->props` は `light->element.props` の省略形
ufbx_prop *prop = ufbx_find_prop(&light->props, "Intensity");
assert(prop);
printf("ufbx_light.intensity: %.2f\n", light->intensity);
printf("ufbx_props.Intensity: %.2f\n", prop->value_real);
}Rust 例
fn print_intensity(light: &ufbx::Light) {
let prop = light.element.props
.find_prop("Intensity")
.expect("expected to find 'Intensity'");
println!("ufbx_light.intensity: {:.2}", light.intensity);
// 注意:Rust バインディングの表現は実装に依存する場合があります
println!("ufbx_props.Intensity: {:.2}", prop.value_vec4.x);
}例(Blender で強度 2.0 のライトを持つシーン)
ufbx_light.intensity: 2.00
ufbx_props.Intensity: 200.00脚注
- FBX のファイルフォーマットではこれらを Objects と呼びますが、3D モデルの文脈では “object” という語が多義的すぎるため、ufbx では elements と呼んでいます。
クレジット / ライセンス
本文・コード例は ufbx の公開ライセンス(MIT / Unlicense)に基づき翻訳・再構成しています。必要に応じてクレジット表記を併記してください:© 2020 Samuli Raivio — Original docs under Unlicense/MIT. Japanese translation by <YzLearning>.
- 原文: ufbx docs “Elements” ページ
- ライセンス: MIT / Unlicense(二重ライセンス)
- 推奨クレジット:
© 2020 Samuli Raivio — Original docs under Unlicense/MIT. - 訳者:[YzLearnig](必要に応じて記入)
Nodes(ノード)
概要
Nodes(ノード)(ufbx_node)は、FBX ファイルの シーングラフ(Scene Graph) を構成する要素です。
ノード自体は、変換情報(ufbx_node.local_transform)と階層構造(ufbx_node.parent / ufbx_node.children[])を保持します。
ノードは 属性(attribute) によって機能が拡張されます。たとえば、ufbx_mesh や ufbx_light などです。
1つの属性が複数のノードに参照されることもあり、同じメッシュを異なるトランスフォームでインスタンス化することができます。
ノードは共通属性を直接保持しており、たとえば:
ufbx_node.meshufbx_node.light
などを通じてアクセスできます。
より珍しい属性は ufbx_node.attrib に格納されており、ufbx_as_bone() のようなヘルパ関数で具体的な型へキャスト(または NULL)できます。
逆に、「ノードがどの属性を持つか」を列挙する代わりに、「ある属性がどのノードで使用されているか」を調べることもできます。
各属性(例:ufbx_mesh)には ufbx_element.instances[] フィールドがあり、それを参照しているすべてのノードを取得できます。
Transforms(変換)
ノードのローカル変換は、平行移動(translation)・回転(rotation)・スケール(scale) の組み合わせで表されます。
これらは ufbx_node.local_transform に格納され、親ノードに対する相対的な変換 を表します。
ノードはさらに以下のような便利フィールドも持っています:
ufbx_node.node_to_parent(親ノード座標系への行列)ufbx_node.node_to_world(ワールド座標系への行列)
FBX の内部変換は非常に複雑ですが、ufbx ではこれを隠蔽するための機能を数多く備えています。ufbx_node のフィールドや ufbx_evaluate_transform()、ufbx_bake_anim() などを利用すれば、複雑さを意識せずに扱えます。
(FBX の内部的な変換構造については「Node Transforms」セクションで詳細に説明されています。)
Coordinate Spaces(座標系)
FBX ファイルは任意の座標系(軸向きや単位スケール)で保存されている場合があります。
たとえば、前方/右/上方向の軸や単位長(1.0 の意味)が異なるケースです。
これに対応するため、ufbx_scene.settings から 軸(axes) と 単位メートル値(unit_meters) を取得できます。
また、ufbx_load_opts.target_axes および ufbx_load_opts.target_unit_meters を設定することで、
読み込んだシーンを希望の座標系に変換することも可能です。
変換方法は ufbx_load_opts.space_conversion によって指定します:
定数名 | 内容 |
|---|---|
| ルートノードで空間変換を行う |
| 各ノードのトランスフォームを補正する |
| ジオメトリにスケールを焼き込み、軸変換は ADJUST_TRANSFORMS と同様に行う |
ufbx では、ufbx_load_opts.handedness_conversion_axis を使用して 左右座標系(右手/左手) の変換も可能です。
通常、FBX は右手座標系が主流なので、右手系を使用する場合は不要です。
左手座標系でシーンをロードする場合は、ミラー変換が必要になります。
また、カメラ(ローカル +X 向き)やライト(デフォルトではローカル -Y 向き)の軸を修正する機能もあります。
これには ufbx_load_opts.target_camera_axes および ufbx_load_opts.target_light_axes を使用します。
Coordinate Spaces in Files(ファイル内の座標系)
FBX の座標系やエクスポータの違いは、多くのユーザーを混乱させてきました。
(例:「FBX scale 100」「FBX scale 0.01」で検索するとよく出てきます。)
これは ufbx にも影響しますが、いくつかの方法で軽減可能です。
- Blender:通常 Z-up メートル単位で動作しますが、FBX 書き出し時に Y-up / cm単位(100倍) に変換します。
UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMSを使うと、この100倍スケールを打ち消し、不要なスケーリングを取り除けます。
一方、UFBX_SPACE_CONVERSION_MODIFY_GEOMETRYはジオメトリ自体を0.01倍にスケールして正しい形状を維持しますが、中間ノードにスケール要素が残ります。 - Maya:多くの場合 cm 単位がネイティブです。
こちらではUFBX_SPACE_CONVERSION_MODIFY_GEOMETRYの方が適しており、ノードスケールを変更せずにシーンを正しいスケールへ変換できます。
結論として、すべてのケースで完全に一貫した変換方法は存在しません。
ユーザーに変換方法を選択させるオプションを提供するのが望ましいです。
また、軽量ロード(ufbx_load_opts.ignore_all_content = true)を使ってシーンを一度読み込み、ufbx_metadata.exporter を確認することで、エクスポータ(Blender / Maya 等)を特定し、
最適な変換方法を事前設定することも可能です。
💡 Blender でエクスポートする場合は、「Apply Scalings」を “FBX Units Scale” に設定するのが推奨です。
これにより、追加スケールなしのメートル単位(unit_meters = 1.0)でエクスポートされます。
サンプルコード
// シーンをメートル単位・右手Y-upに変換してロード
ufbx_load_opts opts = { 0 };
opts.target_axes = ufbx_axes_right_handed_y_up;
opts.target_unit_meters = 1.0f;
opts.target_camera_axes = ufbx_axes_right_handed_y_up;
opts.target_light_axes = ufbx_axes_right_handed_y_up;
if (prefer_blender) {
opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
} else {
opts.space_conversion = UFBX_SPACE_CONVERSION_MODIFY_GEOMETRY;
}Geometry Transforms(ジオメトリ変換)
FBX では ジオメトリ変換(geometry transform) と呼ばれる特殊な変換をサポートしています。
これはノード直下のメッシュなどにのみ適用され、子ノードには継承されません。
多くのシーングラフではこの仕組みを直接サポートしていないため、ufbx は代替方法を提供します。
ジオメトリ変換の利用
ジオメトリ変換は ufbx_node.geometry_transform に格納されます。
また、補助的な行列:
ufbx_node.geometry_to_nodeufbx_node.geometry_to_world
もあり、特に静的メッシュをワールド座標で扱う際に便利です。
ジオメトリ変換を除去する
非静的シーンでのジオメトリ変換は扱いが難しいため、ufbx_load_opts.geometry_transform_handling によりロード時に削除することも可能です。
定数名 | 内容 |
|---|---|
| 補助ノードを挿入して対応(確実だがノード数が増える) |
| ジオメトリ変換を頂点データに焼き込み(きれいだが制約あり) |
| 補助ノードを絶対に作らない(ただし変換誤差が発生する) |
Inherit Modes(継承モード)
FBX では非標準的な変換継承も可能です。
これは ufbx_node.inherit_mode によって示されます。
モード | 内容 |
|---|---|
| 親のスケールを無視 |
| スケールと回転を独立して合成 |
ufbx では、ロード時にこれらを標準的なシーングラフへ変換するオプションを用意しています:
定数 | 内容 |
|---|---|
| 継承モードをそのまま保持(正確だが複雑) |
| 補助ノードを追加して対応 |
| 子ノードのスケールを逆補正(できない場合は補助ノード) |
| 非標準継承をすべて無視(単純だが不正確) |
Pivots(ピボット)
FBX ノードでは、回転ピボット と スケールピボット を個別に設定できます。
ufbx では、デフォルトでこれらの効果をノードの平行移動へ焼き込みます。
回転ピボットとスケールピボットが同一である場合、ufbx_load_opts.pivot_handling = UFBX_PIVOT_HANDLING_ADJUST_TO_PIVOT
を設定することで、ピボットをジオメトリ変換へ変換することも可能です。
この場合は、ufbx_load_opts.geometry_transform_handling も併せて指定します(例:UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY)。
その他のプロパティ
ノードには変換以外にも以下のプロパティがあります:
- 可視性:
ufbx_node.visibleでノードを非表示にできる - マテリアルの上書き:
ufbx_node.materials[]でインスタンスごとに別マテリアルを適用できる
備考
FBX 仕様ではこれらを “Geometric” transforms(ジオメトリック変換) と呼びますが、
ufbx では明確化のため geometry transforms と呼んでいます。
Meshes(メッシュ) — ufbx ドキュメント日本語訳
概要
Meshes(メッシュ)(ufbx_mesh)は、ポリゴンジオメトリデータ を保持する要素です。
ufbx では次の用語を使用します:
用語 | 説明 |
|---|---|
Vertex(頂点) | 位置情報を持つ頂点。3Dモデリングソフトで選択可能な頂点に相当。 |
Index(インデックス) | 頂点にUV・法線・カラーなどの属性を組み合わせたもの。 |
Face(面) | 1枚の平面(三角形・四角形・N-gon)を構成するインデックスの範囲。 |
同じ頂点が複数の面から参照されることがあります。
各参照では異なるインデックスを持ち、これにより 同一頂点に異なるUVや法線を設定 できます。
メッシュのデータ構造
メッシュデータは各種属性として格納されます:
ufbx_mesh.vertex_position— 頂点座標ufbx_mesh.vertex_normal— 頂点法線ufbx_mesh.vertex_uv— 頂点UV座標
各属性には独自の インデックス配列(ufbx_vertex_attrib.indices[])と 値配列(ufbx_vertex_attrib.values[])があり、
インデックス指定で値を取得できます:
data[indices[index]]あるいはヘルパー関数 ufbx_get_vertex_vec3() や、
C++/Rust の attrib[index] 構文でもアクセス可能です。
描画例
以下は仮想的な即時モードポリゴンAPIを使ってメッシュを描画する例です。
void draw_polygons(ufbx_mesh *mesh)
{
for (ufbx_face face : mesh->faces) {
begin_polygon();
// ポリゴンの各コーナーをループ
for (uint32_t corner = 0; corner < face.num_indices; corner++) {
// 各コーナーに対応するインデックス
uint32_t index = face.index_begin + corner;
// 頂点属性を取得
ufbx_vec3 position = mesh->vertex_position[index];
ufbx_vec3 normal = mesh->vertex_normal[index];
ufbx_vec2 uv = mesh->vertex_uv[index];
polygon_corner(position, normal, uv);
}
end_polygon();
}
}Materials(マテリアル)
1つのFBXメッシュには、異なる部位に複数のマテリアル が割り当てられる場合があります。ufbx_mesh.face_material[] には、面ごとのマテリアルインデックスが格納されており、
これを使って ufbx_mesh.materials[] にアクセスできます。
ただし、正確な結果を得るには ufbx_node.materials[] を使うのが推奨です。
理由は、同じメッシュが異なるマテリアルでインスタンス化されることがあるためです。
ゲームエンジンでは、マテリアル境界ごとにメッシュを分割する必要がある場合があります。
ufbx ではこれを容易にするために ufbx_mesh.material_parts[] を提供しています。
これには、各マテリアルごとの面数・三角形数・面リストが含まれます。
マテリアルが存在しない場合でも、便宜上1つのマテリアルパートが存在します。
例:GPU用フォーマットへの変換
以下は、メッシュデータを GPU向けインデックス付きフォーマット に変換する例です。
使用しているヘルパー関数:
ufbx_triangulate_face():面を三角形化してインデックス配列を生成ufbx_generate_indices():頂点配列を重複排除し、インデックスバッファを生成
// GPU向け頂点構造体
// 実際にはよりコンパクトな型を使うべきです。
// `ufbx_real` はデフォルトで64bitです。
struct Vertex {
ufbx_vec3 position;
ufbx_vec3 normal;
ufbx_vec2 uv;
};
void convert_mesh_part(ufbx_mesh *mesh, ufbx_mesh_part *part)
{
std::vector<Vertex> vertices;
std::vector<uint32_t> tri_indices;
tri_indices.resize(mesh->max_face_triangles * 3);
// このマテリアルを使用している各面を処理
for (uint32_t face_index : part->face_indices) {
ufbx_face face = mesh->faces[face_index];
// 面を三角形化
uint32_t num_tris = ufbx_triangulate_face(
tri_indices.data(), tri_indices.size(), mesh, face);
// 各三角形の頂点を取得
for (size_t i = 0; i < num_tris * 3; i++) {
uint32_t index = tri_indices[i];
Vertex v;
v.position = mesh->vertex_position[index];
v.normal = mesh->vertex_normal[index];
v.uv = mesh->vertex_uv[index];
vertices.push_back(v);
}
}
assert(vertices.size() == part->num_triangles * 3);
// 頂点ストリームを生成
ufbx_vertex_stream streams[1] = {
{ vertices.data(), vertices.size(), sizeof(Vertex) },
};
std::vector<uint32_t> indices;
indices.resize(part->num_triangles * 3);
// 頂点重複を削除し、インデックスバッファを生成
size_t num_vertices = ufbx_generate_indices(
streams, 1, indices.data(), indices.size(), nullptr, nullptr);
vertices.resize(num_vertices);
create_vertex_buffer(vertices.data(), vertices.size());
create_index_buffer(indices.data(), indices.size());
}Attributes(属性)
上記の属性に加えて、FBXメッシュには他の属性も存在します。
多くの属性は 頂点ごと(またはインデックスごと) に定義されていますが、
一部は 面単位・エッジ単位 のデータも含まれます。
エッジはオプションで、ufbx_mesh.edges[] で2つのインデックス間を定義します。
頂点(インデックス単位、最大 ufbx_mesh.num_indices)
フィールド | 内容 |
|---|---|
| 頂点座標 |
| 法線ベクトル |
| 接線方向(Tangent)UV.x |
| 接線空間UV.y |
| UV座標(第1セット) |
| 頂点カラー(第1セット) |
| サブディビジョン用クリース値 |
面(最大 ufbx_mesh.num_faces)
フィールド | 内容 |
|---|---|
| 面ごとのマテリアル |
| ポリゴングループ |
| スムーズシェーディングフラグ |
| 穴として扱うかどうか |
エッジ(最大 ufbx_mesh.num_edges)
フィールド | 内容 |
|---|---|
| 法線生成用スムーズフラグ |
| 編集用のエッジ表示フラグ |
| サブディビジョン用エッジクリース |
🌀 Animation(アニメーション)
FBX ファイル内のアニメーションは、スタック (ufbx_anim_stack) と呼ばれる レイヤー (ufbx_anim_layer) の集合として表現されます。
各スタックは、ファイル内の 1 つのアニメーションクリップ(または「テイク」)に対応します。
ufbx ではアニメーションを ufbx_anim デスクリプタを通して扱います。
これにより、「複数レイヤーを合成したスタック」か「単一レイヤー」かを
統一されたインターフェースで選択・評価できます。
🔹 ufbx_anim インスタンスを取得できる場所
ufbx_scene.anim:シーンのデフォルトアニメーションスタックufbx_anim_stack.anim:合成済みアニメーションスタックufbx_anim_layer.anim:個別のアニメーションレイヤーufbx_create_anim():カスタムアニメーションデスクリプタを作成
🎛 評価(Evaluation)
ufbx はファイル内のアニメーションカーブを直接扱えますが、
それらを手動で解釈するのは非常に複雑です。
主な理由は次の通りです:
- ⏩ 時間が非線形な キュービック補間カーブ
- 🔄 回転順序を持つ オイラー角回転カーブ
- ⚙️ プリ/ポスト回転、ピボット、オフセット などの複雑なノード変換
- 🎨 アニメーションレイヤーの合成
こうした複雑さを避けるため、
これらを内部で処理してくれる ufbx の「評価ユーティリティ」を使用することが推奨されます。
特に、下記の「アニメーションのベイク(baking)」は、
複雑な FBX アニメーションを扱いやすい形式に変換する良い出発点です。
🌍 シーン全体の評価(Scene Evaluation)
最も簡単な方法は ufbx_evaluate_scene() を使うことです。
これは指定した時刻におけるアニメーションをすべて適用し、
新しい ufbx_scene を生成する「重い」関数です。
結果のシーンは通常の ufbx_scene と同様に扱えます。
🔧 アニメーションのベイク(Animation Baking)
FBX ファイル内のアニメーションは ufbx_bake_anim() を使って
より単純な形式に「ベイク」できます。
この関数は、トランスフォームアニメーションを
線形補間のトラック(translation/quaternion rotation/scale)に変換します。
トランスフォーム以外のプロパティも線形補間キーとしてベイクされます。
ベイクアルゴリズムは単純な再サンプリングではなく、
キーフレームの頻度などを考慮して効率的に処理します。
ただし、キュービック補間やオイラー回転は
クォータニオンに再サンプリングする必要があります。
🎚 サンプリング設定
ufbx_bake_opts.resample_rate:再サンプリングレートの設定ufbx_bake_opts.minimum_sample_rate:高頻度キーをスキップして二重サンプリングを防止
💻 C 言語例
void bake_animation(ufbx_scene *scene, ufbx_anim *anim)
{
ufbx_baked_anim *bake = ufbx_bake_anim(scene, anim, NULL, NULL);
assert(bake);
for (size_t i = 0; i < bake->nodes.count; i++) {
ufbx_baked_node *bake_node = &bake->nodes.data[i];
ufbx_node *scene_node = scene->nodes.data[bake_node->typed_id];
printf(" node %s:\n", scene_node->name.data);
printf(" translation: %zu keys\n", bake_node->translation_keys.count);
printf(" rotation: %zu keys\n", bake_node->rotation_keys.count);
printf(" scale: %zu keys\n", bake_node->scale_keys.count);
}
ufbx_free_baked_anim(bake);
}
void bake_animations(ufbx_scene *scene)
{
for (size_t i = 0; i < scene->anim_stacks.count; i++) {
ufbx_anim_stack *stack = scene->anim_stacks.data[i];
printf("stack %s:\n", stack->name.data);
bake_animation(scene, stack->anim);
}
}💻 C++ 例
void bake_animation(ufbx_scene *scene, ufbx_anim *anim)
{
ufbx_baked_anim *bake = ufbx_bake_anim(scene, anim, NULL, NULL);
assert(bake);
for (const ufbx_baked_node &bake_node : bake->nodes) {
ufbx_node *scene_node = scene->nodes[bake_node.typed_id];
printf(" node %s:\n", scene_node->name.data);
printf(" translation: %zu keys\n", bake_node.translation_keys.count);
printf(" rotation: %zu keys\n", bake_node.rotation_keys.count);
printf(" scale: %zu keys\n", bake_node.scale_keys.count);
}
ufbx_free_baked_anim(bake);
}
void bake_animations(ufbx_scene *scene)
{
for (ufbx_anim_stack *stack : scene->anim_stacks) {
printf("stack %s:\n", stack->name.data);
bake_animation(scene, stack->anim);
}
}💻 Rust 例
fn bake_animation(scene: &ufbx::Scene, anim: &ufbx::Anim) {
let bake = ufbx::bake_anim(scene, anim, ufbx::BakeOpts::default())
.expect("expected to bake animation");
for bake_node in &bake.nodes {
let scene_node = &scene.nodes[bake_node.typed_id as usize];
println!(" node {}:", scene_node.element.name);
println!(" translation: {} keys", bake_node.translation_keys.len());
println!(" rotation: {} keys", bake_node.rotation_keys.len());
println!(" scale: {} keys", bake_node.scale_keys.len());
}
}
fn bake_animations(scene: &ufbx::Scene) {
for stack in &scene.anim_stacks {
println!("stack {}:", stack.element.name);
bake_animation(scene, &stack.anim);
}
}⚙️ トランスフォーム・プロパティの評価
ufbx は個々の要素を特定の時刻で評価するための低レベル API も提供しています。
関数名 | 内容 |
|---|---|
| ノードの位置・回転(クォータニオン)・スケールを評価 |
| ブレンドシェイプのウェイトを評価 |
| 任意の FBX プロパティ値を評価 |
| 要素全体のプロパティをまとめて評価 |
さらに低レベルの関数:
関数名 | 内容 |
|---|---|
/
|
を評価 |
| 単一の
を評価 |
🦴 Deformers(デフォーマー)
FBX では、メッシュ変形(Mesh deformation) は Deformer を通じて実装されています。
ufbx は以下の 3 種類の FBX デフォーマーをサポートしています。
- 🦴
ufbx_skin_deformer
→ ボーンに頂点を重み付きで結びつける「スキニング」 - 😶🌫️
ufbx_blend_deformer
→ ブレンドシェイプ(モーフターゲット)による頂点オフセット - 💾
ufbx_cache_deformer
→ ディスクキャッシュからメッシュデータを置き換えるデフォーマー
これらのデフォーマーはメッシュに接続されています。
例:ufbx_mesh.skin_deformers[] や ufbx_mesh.blend_deformers[]。
もし正確な適用順序が必要な場合は、ufbx_mesh.all_deformers[] ですべてのデフォーマーを型なしリストで取得できます。
🦾 Skin Deformer(スキンデフォーマー)
FBX のスケルトンは、通常のノード(ufbx_node)で構成され、
多くの場合ボーン属性(ufbx_bone)を持ちます。
ボーン属性はスキニングに必須ではありませんが、可視化やスケルトン検出に役立ちます。
📐 クラスタ(Cluster)
ボーンによる影響は クラスタ (ufbx_skin_cluster) で定義されます。
クラスタはメッシュをボーンに結びつけ、ufbx_skin_cluster.geometry_to_bone は
メッシュ空間からボーン空間への変換を表します。
これはいわゆる「逆バインド行列(inverse bind matrix)」です。
影響を受ける頂点は ufbx_skin_cluster.vertices[] と
対応する ufbx_skin_cluster.weights[] に格納されています。
🧮 頂点ごとの重み情報
どのボーンが各頂点に影響するかを解析するのはよくある処理です。
そこで ufbx は便利な簡略アクセスとして:
ufbx_skin_deformer.vertices[]ufbx_skin_deformer.weights[]
を提供しています。
これらは影響度の高い順にソートされているため、
もし 4 ~ 8 つのウェイトしか対応しない場合は
上位 N 個を取るだけで十分です。
💻 C 言語サンプル
#define MAX_WEIGHTS 4
typedef struct Vertex {
ufbx_vec3 position;
ufbx_vec3 normal;
float weights[MAX_WEIGHTS];
uint32_t bones[MAX_WEIGHTS];
} Vertex;
Vertex get_skinned_vertex(ufbx_mesh *mesh, ufbx_skin_deformer *skin, size_t index)
{
Vertex v = { 0 };
v.position = ufbx_get_vertex_vec3(&mesh->vertex_position, index);
v.normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index);
uint32_t vertex = mesh->vertex_indices.data[index];
ufbx_skin_vertex skin_vertex = skin->vertices.data[vertex];
size_t num_weights = skin_vertex.num_weights;
if (num_weights > MAX_WEIGHTS) num_weights = MAX_WEIGHTS;
float total_weight = 0.0f;
for (size_t i = 0; i < num_weights; i++) {
ufbx_skin_weight skin_weight = skin->weights.data[skin_vertex.weight_begin + i];
v.bones[i] = skin_weight.cluster_index;
v.weights[i] = (float)skin_weight.weight;
total_weight += (float)skin_weight.weight;
}
// FBX では重みの正規化が保証されていないため再正規化する
for (size_t i = 0; i < num_weights; i++) {
v.weights[i] /= total_weight;
}
return v;
}💻 C++ 版
#define MAX_WEIGHTS 4
struct Vertex {
ufbx_vec3 position;
ufbx_vec3 normal;
float weights[MAX_WEIGHTS];
uint32_t bones[MAX_WEIGHTS];
};
Vertex get_skinned_vertex(ufbx_mesh *mesh, ufbx_skin_deformer *skin, size_t index)
{
Vertex v = { 0 };
v.position = mesh->vertex_position[index];
v.normal = mesh->vertex_normal[index];
uint32_t vertex = mesh->vertex_indices[index];
ufbx_skin_vertex skin_vertex = skin->vertices[vertex];
size_t num_weights = skin_vertex.num_weights;
if (num_weights > MAX_WEIGHTS) num_weights = MAX_WEIGHTS;
float total_weight = 0.0f;
for (size_t i = 0; i < num_weights; i++) {
ufbx_skin_weight skin_weight = skin->weights[skin_vertex.weight_begin + i];
v.bones[i] = skin_weight.cluster_index;
v.weights[i] = (float)skin_weight.weight;
total_weight += (float)skin_weight.weight;
}
for (size_t i = 0; i < num_weights; i++) {
v.weights[i] /= total_weight;
}
return v;
}💻 Rust 版
const MAX_WEIGHTS: usize = 4;
#[derive(Clone, Copy, Default)]
struct Vertex {
position: ufbx::Vec3,
normal: ufbx::Vec3,
weights: [f32; MAX_WEIGHTS],
bones: [u32; MAX_WEIGHTS],
}
fn get_skinned_vertex(mesh: &ufbx::Mesh, skin: &ufbx::SkinDeformer, index: usize) -> Vertex {
let mut v = Vertex{
position: mesh.vertex_position[index],
normal: mesh.vertex_normal[index],
..Default::default()
};
let vertex = mesh.vertex_indices[index] as usize;
let skin_vertex = skin.vertices[vertex];
let num_weights = (skin_vertex.num_weights as usize).min(MAX_WEIGHTS);
let mut total_weight: f32 = 0.0;
for i in 0..num_weights {
let skin_weight = skin.weights[skin_vertex.weight_begin as usize + i];
v.bones[i] = skin_weight.cluster_index;
v.weights[i] = skin_weight.weight as f32;
total_weight += skin_weight.weight as f32;
}
for i in 0..num_weights {
v.weights[i] /= total_weight;
}
v
}⚙️ Skinning Modes(スキニングモード)
FBX は ufbx_skin_deformer.skinning_method により複数のスキニング方式をサポートします。
基本的には無視しても問題ありませんが、必要なら以下の通りです。
定数 | 内容 |
|---|---|
| 単一ボーン固定(補間なし) |
| 一般的な線形ブレンドスキニング |
| デュアルクォータニオンスキニング |
| 線形とデュアルクォータニオンを補間するモード(
により制御) |
🎭 Blend Deformer(ブレンドデフォーマー)
ブレンドシェイプ(モーフターゲット) は
ブレンドチャンネル (ufbx_blend_channel) によって制御されます。
FBX 形式では「中間ブレンドキー(in-between keyframes)」をサポートしており、ufbx_blend_channel.keyframes[] にキーが定義されています。
もしそれを扱わない場合は、便利なフィールド:
ufbx_blend_channel.target_shape
→ 最後のキー(ターゲットシェイプ)を直接参照
を使用できます。
各ブレンドシェイプ(ufbx_blend_shape)は頂点の部分集合に対してオフセットを持ちます:
ufbx_blend_shape.offset_vertices[]:影響を受ける頂点インデックスufbx_blend_shape.position_offsets[]:対応する頂点位置のオフセットufbx_blend_shape.normal_offsets[]:法線オフセット(多くの場合は未使用またはゼロ)
また、便利関数として以下も用意されています:
ufbx_get_blend_shape_offset_index()ufbx_get_blend_shape_vertex_offset()
💻 C 例
#define MAX_BLENDS 4
typedef struct Vertex {
ufbx_vec3 position;
ufbx_vec3 normal;
ufbx_vec3 blend_offsets[MAX_BLENDS];
} Vertex;
Vertex get_blend_vertex(ufbx_mesh *mesh, ufbx_blend_deformer *deformer, size_t index)
{
Vertex v = { 0 };
v.position = ufbx_get_vertex_vec3(&mesh->vertex_position, index);
v.normal = ufbx_get_vertex_vec3(&mesh->vertex_normal, index);
uint32_t vertex = mesh->vertex_indices.data[index];
size_t num_blends = deformer->channels.count;
if (num_blends > MAX_BLENDS) num_blends = MAX_BLENDS;
for (size_t i = 0; i < num_blends; i++) {
ufbx_blend_channel *channel = deformer->channels.data[i];
ufbx_blend_shape *shape = channel->target_shape;
assert(shape);
v.blend_offsets[i] = ufbx_get_blend_shape_vertex_offset(shape, vertex);
}
return v;
}💻 C++ 例
#define MAX_BLENDS 4
struct Vertex {
ufbx_vec3 position;
ufbx_vec3 normal;
ufbx_vec3 blend_offsets[MAX_BLENDS];
};
Vertex get_blend_vertex(ufbx_mesh *mesh, ufbx_blend_deformer *deformer, size_t index)
{
Vertex v = { };
v.position = mesh->vertex_position[index];
v.normal = mesh->vertex_normal[index];
uint32_t vertex = mesh->vertex_indices[index];
size_t num_blends = deformer->channels.count;
if (num_blends > MAX_BLENDS) num_blends = MAX_BLENDS;
for (size_t i = 0; i < num_blends; i++) {
ufbx_blend_channel *channel = deformer->channels[i];
ufbx_blend_shape *shape = channel->target_shape;
assert(shape);
v.blend_offsets[i] = ufbx_get_blend_shape_vertex_offset(shape, vertex);
}
return v;
}💻 Rust 例
const MAX_BLENDS: usize = 4;
#[derive(Clone, Copy, Default)]
struct Vertex {
position: ufbx::Vec3,
normal: ufbx::Vec3,
blend_offsets: [ufbx::Vec3; MAX_BLENDS],
}
fn get_blend_vertex(mesh: &ufbx::Mesh, deformer: &ufbx::BlendDeformer, index: usize) -> Vertex {
let mut v = Vertex{
position: mesh.vertex_position[index],
normal: mesh.vertex_normal[index],
..Default::default()
};
let vertex = mesh.vertex_indices[index] as usize;
let num_blends = (deformer.channels.len() as usize).min(MAX_BLENDS);
for i in 0..num_blends {
let channel = &deformer.channels[i];
let shape = channel.target_shape.as_ref().expect("no blend shape, broken file");
v.blend_offsets[i] = shape.get_vertex_offset(vertex);
}
v
}📘 備考
このドキュメントは MIT / Public Domain ライセンスのもとで公開された
ufbx (c) 2020 Samuli Raivio の内容を翻訳・整形したものです。
FBX
📦 FBX フォーマットの内部構造
このセクションでは、FBX ファイルフォーマットの内部仕様 について詳しく説明します。
これらの情報は、主に 高度な利用ケース においてのみ必要となります。
なぜなら、ufbx が FBX フォーマット特有の挙動や癖を
内部で自動的に処理するよう設計されているためです。
⚠️ 注意
FBX フォーマットは非常に複雑で、
あまり使われないが存在する多くの機能 を持っています。
そのため、FBX ファイルを直接扱う場合は次の点に注意が必要です。
💡 新しい FBX ファイルを読み込むたびに、
そのファイルが利用している未対応の機能に対応する必要があるかもしれません。
もし自分自身で インポータと出力ファイルの両方を管理している 場合は、
必要な機能だけをサポートすれば十分ですが、
そうでない場合は広範囲な仕様への対応が求められます。
📘 補足
この内容は MIT / Public Domain ライセンスのもとで公開された
ufbx (c) 2020 Samuli Raivio のドキュメントを翻訳・整形したものです。
🧭 Node Transforms(ノード変換)
FBX のノード変換は、一連の変換のチェーンとして構成されています。
もし特別な理由がなければ、FBX 固有の変換モデルを直接扱うよりも、
ufbx が提供する変換表現を使用することを強く推奨します。
詳細は → Elements / Nodes / Transforms を参照してください。
🔩 基本構造(Transform Properties)
FBX 内でのノード変換は、いくつかのプロパティで構成されています。
プロパティ名 | 意味 |
|---|---|
| 親ノードに対する平行移動 |
| 親ノードに対する非一様スケール |
| 親ノードに対する回転(オイラー角) |
|
のオイラー回転順序(
) |
🎯 ピボット・オフセット関連
以下のプロパティによって、回転やスケールの中心・ずれが定義されます。
プロパティ名 | 内容 |
|---|---|
| スケーリングの中心点 |
| スケール後のオフセット移動 |
| 回転の中心点 |
| 回転後のオフセット移動 |
さらに "Lcl Rotation" 以外に、2種類の補助回転が存在します:
プロパティ名 | 内容 |
|---|---|
|
の前に適用される回転(常に XYZ 順) |
|
の後に適用される 逆回転 (常に XYZ 順) |
⚙️ 変換チェーンの計算例
以下は、ufbx の内部補正(adjust transform)を考慮しない
基本的なノード変換の計算例です。
// 手動で `ufbx_node.node_to_parent` を計算
// ※ ufbx固有の調整変換(adjust transform)は考慮しない
Matrix4 get_transform(ufbx_node *node)
{
ufbx_props *props = &node->props;
// 主要プロパティを取得(必要に応じてカーブから評価)
int64_t rotation_order = ufbx_find_int(props, "RotationOrder", 0);
Vector3 lcl_translation = ufbx_find_vec3(props, "Lcl Translation", ufbx_zero_vec3);
Vector3 lcl_scaling = ufbx_find_vec3(props, "Lcl Scaling", ufbx_zero_vec3);
Vector3 lcl_rotation = ufbx_find_vec3(props, "Lcl Rotation", ufbx_zero_vec3);
Vector3 rotation_pivot = ufbx_find_vec3(props, "RotationPivot", ufbx_zero_vec3);
Vector3 scaling_pivot = ufbx_find_vec3(props, "ScalingPivot", ufbx_zero_vec3);
Vector3 rotation_offset = ufbx_find_vec3(props, "RotationOffset", ufbx_zero_vec3);
Vector3 scaling_offset = ufbx_find_vec3(props, "ScalingOffset", ufbx_zero_vec3);
Vector3 pre_rotation = ufbx_find_vec3(props, "PreRotation", ufbx_zero_vec3);
Vector3 post_rotation = ufbx_find_vec3(props, "PostRotation", ufbx_zero_vec3);
// オイラー角 → クォータニオン変換
Quaternion lcl_quat = Quaternion_euler(lcl_rotation, (EulerOrder)rotation_order);
Quaternion pre_quat = Quaternion_euler(pre_rotation, EulerOrder_XYZ);
Quaternion post_quat = Quaternion_euler(post_rotation, EulerOrder_XYZ);
Matrix4 m = Matrix4_identity;
// スケール適用(ピボット・オフセット含む)
m = Matrix4_translate(-scaling_pivot) * m;
m = Matrix4_scale_nonuniform(lcl_scaling) * m;
m = Matrix4_translate(scaling_pivot) * m;
m = Matrix4_translate(scaling_offset) * m;
// 回転適用(PostRotation は反転)
m = Matrix4_translate(-rotation_pivot) * m;
m = Matrix4_rotate(Quaternion_inverse(post_quat)) * m;
m = Matrix4_rotate(lcl_quat) * m;
m = Matrix4_rotate(pre_quat) * m;
m = Matrix4_translate(rotation_pivot) * m;
m = Matrix4_translate(rotation_offset) * m;
// 最後に平行移動
m = Matrix4_translate(lcl_translation) * m;
return m;
}🧮 Adjust Transforms(調整変換)
FBX の座標系やピボットの扱いに応じて、
ufbx は内部でいくつかの「調整変換(adjust transform)」を自動的に付与します。
これらを考慮しないと、見た目上の座標がずれることがあります。
主な adjust transform の対応表
機能 | 対応するフィールド |
|---|---|
|
,
|
|
,
|
|
|
|
|
|
|
|
|
ufbx_node.has_adjust_transform が true の場合は、
ノードが非単位(≠ identity)の調整変換を持っています。
ただし、常に適用しても安全です。
💻 Adjust 変換対応版の完全計算例
// ufbx固有の adjust transform を考慮した `ufbx_node.node_to_parent` の計算
Matrix4 get_transform(ufbx_node *node)
{
ufbx_props *props = &node->props;
// プロパティの取得
int64_t rotation_order = ufbx_find_int(props, "RotationOrder", 0);
Vector3 lcl_translation = ufbx_find_vec3(props, "Lcl Translation", ufbx_zero_vec3);
Vector3 lcl_scaling = ufbx_find_vec3(props, "Lcl Scaling", ufbx_zero_vec3);
Vector3 lcl_rotation = ufbx_find_vec3(props, "Lcl Rotation", ufbx_zero_vec3);
Vector3 rotation_pivot = ufbx_find_vec3(props, "RotationPivot", ufbx_zero_vec3);
Vector3 scaling_pivot = ufbx_find_vec3(props, "ScalingPivot", ufbx_zero_vec3);
Vector3 rotation_offset = ufbx_find_vec3(props, "RotationOffset", ufbx_zero_vec3);
Vector3 scaling_offset = ufbx_find_vec3(props, "ScalingOffset", ufbx_zero_vec3);
Vector3 pre_rotation = ufbx_find_vec3(props, "PreRotation", ufbx_zero_vec3);
Vector3 post_rotation = ufbx_find_vec3(props, "PostRotation", ufbx_zero_vec3);
// ufbx 特有の調整パラメータ
Vector3 adjust_pre_translation = node->adjust_pre_translation;
Quaternion adjust_pre_rotation = node->adjust_pre_rotation;
Quaternion adjust_post_rotation = node->adjust_post_rotation;
float adjust_pre_scale = (float)node->adjust_pre_scale;
float adjust_post_scale = (float)node->adjust_post_scale;
float adjust_translation_scale = (float)node->adjust_translation_scale;
// クォータニオン化
Quaternion lcl_quat = Quaternion_euler(lcl_rotation, (EulerOrder)rotation_order);
Quaternion pre_quat = Quaternion_euler(pre_rotation, EulerOrder_XYZ);
Quaternion post_quat = Quaternion_euler(post_rotation, EulerOrder_XYZ);
Matrix4 m = Matrix4_identity;
// 後処理(post-adjust)
m = Matrix4_rotate(adjust_post_rotation) * m;
m = Matrix4_scale(adjust_post_scale) * m;
// スケール
m = Matrix4_translate(-scaling_pivot) * m;
m = Matrix4_scale_nonuniform(lcl_scaling) * m;
m = Matrix4_translate(scaling_pivot) * m;
m = Matrix4_translate(scaling_offset) * m;
// 回転
m = Matrix4_translate(-rotation_pivot) * m;
m = Matrix4_rotate(Quaternion_inverse(post_quat)) * m;
m = Matrix4_rotate(lcl_quat) * m;
m = Matrix4_rotate(pre_quat) * m;
m = Matrix4_translate(rotation_pivot) * m;
m = Matrix4_translate(rotation_offset) * m;
// 平行移動
m = Matrix4_translate(lcl_translation) * m;
// 前処理(pre-adjust)
m = Matrix4_translate(adjust_pre_translation) * m;
m = Matrix4_rotate(adjust_pre_rotation) * m;
m = Matrix4_scale(adjust_pre_scale) * m;
// translation のみスケーリング
m.m03 *= adjust_translation_scale;
m.m13 *= adjust_translation_scale;
m.m23 *= adjust_translation_scale;
return m;
}📘 実際の実装例:ufbxi_get_transform() on GitHub
🧱 Geometric Transforms(ジオメトリ変換)
ジオメトリ変換(geometry transforms)は、
ノードの子ノードには影響せず、ノード自身の内容(例:メッシュ)だけを変換します。
詳細は → Elements / Nodes / Geometry Transforms
プロパティ名 | 内容 |
|---|---|
| ノード内コンテンツの平行移動 |
| ノード内コンテンツのスケール |
| ノード内コンテンツの回転(常に XYZ 順) |
💻 Geometry Transform の計算例
// 手動で `ufbx_node.geometry_to_node` を計算
Matrix4 get_geometry_transform(ufbx_node *node)
{
ufbx_props *props = &node->props;
Vector3 geo_translation = ufbx_find_vec3(props, "GeometricTranslation", ufbx_zero_vec3);
Vector3 geo_scaling = ufbx_find_vec3(props, "GeometricScaling", ufbx_zero_vec3);
Vector3 geo_rotation = ufbx_find_vec3(props, "GeometricRotation", ufbx_zero_vec3);
Quaternion geo_quat = Quaternion_euler(geo_rotation, EulerOrder_XYZ);
Matrix4 m = Matrix4_identity;
m = Matrix4_scale_nonuniform(geo_scaling) * m;
m = Matrix4_rotate(geo_quat) * m;
m = Matrix4_translate(geo_translation) * m;
return m;
}📘 備考
このドキュメントは MIT / Public Domain ライセンスのもとで公開された
ufbx (c) 2020 Samuli Raivio の内容を翻訳・整形しています。
Advanced
⚙️ Advanced(応用編)
このセクションでは、ufbx の高度な利用方法や特殊なケース について解説します。
標準的なモデルの読み込み・描画に加え、より深い制御や拡張を行いたい場合に参照してください。
💡 この章に含まれるトピック(例)
項目 | 概要 |
|---|---|
🧩 Custom Load Options | 読み込み時に座標系・軸設定・空間変換を指定する方法 |
🔄 Memory Management | 外部アロケータや独自メモリ管理の統合 |
🧮 Low-level Access | ファイル内部のノード・プロパティ・カーブへの直接アクセス |
🧠 Threading / Async Loading | スレッドセーフな利用・非同期読み込みの実装例 |
🧱 Internal Structures | ufbx 内部の構造体設計・評価順序の理解 |
📘 備考
このドキュメントは MIT / Public Domain ライセンスのもとで公開された
ufbx (c) 2020 Samuli Raivio の内容を翻訳・整形しています。
🧩 Build Options(ビルドオプション)
ufbx のビルドは、主に プリプロセッサマクロ(#define) によってカスタマイズします。
マクロの定義方法は次のいずれかです。
- コンパイラオプションでグローバルに指定する
"ufbx.h"や"ufbx.c"をインクルードする前にマクロを定義する- または以下を定義して独自設定ファイルを読み込む:
#define UFBX_CONFIG_HEADER "my-config.h"
#define UFBX_CONFIG_SOURCE "my-source-config.h"これにより、ufbx.h および ufbx.c 内で指定した設定ファイルが自動的にインクルードされます。
📘 ヘッダ設定(Header)
多くの設定マクロは "ufbx.c" 内だけで有効にすれば十分です。
ただし、型やインライン関数に影響を与えるマクロ は "ufbx.h" にも定義する必要があります。
// アサートを独自実装に置き換える
#define ufbx_assert(cond) my_assert(cond)
// 浮動小数型を double → float に変更
#define UFBX_REAL_IS_FLOAT⚙️ オプション機能の無効化
ufbx のサイズを小さくしたい場合、
特定の機能をマクロで無効化できます。
// メッシュの細分化
#define UFBX_NO_SUBDIVISION
// NURBS のテッセレーション
#define UFBX_NO_TESSELLATION
// ジオメトリキャッシュの読み込み
#define UFBX_NO_GEOMETRY_CACHE
// シーン評価
#define UFBX_NO_SCENE_EVALUATION
// スキン評価
#define UFBX_NO_SKINNING_EVALUATION
// アニメーションベイク
#define UFBX_NO_ANIMATION_BAKING
// 面の三角化
#define UFBX_NO_TRIANGULATION
// インデックス生成
#define UFBX_NO_INDEX_GENERATION
// OBJ 形式の読み込みを無効化
#define UFBX_NO_FORMAT_OBJ💾 メモリアロケーション(Memory Allocation)
ufbx は標準の malloc() / realloc() / free() を使用します。
これを独自アロケータに置き換える方法は3通りあります:
方法①:マクロでフックする
#define ufbx_malloc(size) my_alloc(size)
#define ufbx_realloc(ptr, old_size, new_size) my_realloc(ptr, old_size, new_size)
#define ufbx_free(ptr, size) my_free(ptr, size)
#include "ufbx.c"方法②:外部関数として定義する
#define UFBX_EXTERNAL_MALLOC
void *ufbx_malloc(size_t size);
void *ufbx_realloc(void *ptr, size_t old_size, size_t new_size);
void ufbx_free(void *ptr, size_t size);方法③:アロケータを完全に無効化する
#define UFBX_NO_MALLOCこの場合、ユーザーが ufbx_allocator を提供しない限り、
メモリ確保を行う API はすべて失敗します。
📂 ファイル入出力(File I/O)
標準の FILE API を使用して ufbx_load_file() が動作します。
独自のファイルI/Oを使いたい場合は、以下の方法で上書き可能です。
外部定義で差し替え
#define UFBX_EXTERNAL_STDIO
void *ufbx_stdio_open(const char *path, size_t path_len);
size_t ufbx_stdio_read(void *file, void *data, size_t size);
bool ufbx_stdio_skip(void *file, size_t size);
uint64_t ufbx_stdio_size(void *file);
void ufbx_stdio_close(void *file);標準I/Oを完全に無効化
#define UFBX_NO_STDIOこの場合、ufbx_load_file() など FILE に依存する機能は利用できません。
代わりに、ufbx_load_opts.open_file_cb によるカスタム読み込みを利用します。
🧮 数学関数(Math)
ufbx は <math.h> の一部関数を利用しますが、
ビット単位で一致する結果を保証したい場合や
標準ライブラリを使用できない環境では、
外部定義で上書きすることができます。
#define UFBX_EXTERNAL_MATH
double ufbx_sqrt(double x);
double ufbx_sin(double x);
double ufbx_cos(double x);
double ufbx_tan(double x);
double ufbx_asin(double x);
double ufbx_acos(double x);
double ufbx_atan(double x);
double ufbx_atan2(double y, double x);
double ufbx_pow(double x, double y);
double ufbx_fmin(double a, double b);
double ufbx_fmax(double a, double b);
double ufbx_fabs(double x);
double ufbx_copysign(double x, double y);
double ufbx_nextafter(double x, double y);
double ufbx_rint(double x);
double ufbx_ceil(double x);
int ufbx_isnan(double x);これらは自前で定義するか、extra/ufbx_math.c を使用可能です。
🧱 標準ライブラリの除去(C Standard Library)
UFBX_NO_LIBC を定義すると、
ほとんどの標準ライブラリ依存を無効化できます。
これにより組み込み環境などでも利用可能になります。
#define UFBX_NO_LIBCこの定義により、デフォルトで以下が暗黙的に有効になります:
UFBX_EXTERNAL_MALLOCUFBX_EXTERNAL_STDIOUFBX_EXTERNAL_MATH
標準機能を完全に使わない場合は:
#define UFBX_NO_MALLOC
#define UFBX_NO_STDIOを併用します。
<string.h> 関数の再実装が必要
標準ライブラリを使わない場合、以下の関数を提供する必要があります。
(または extra/ufbx_libc.c を使用)
size_t ufbx_strlen(const char *str);
void *ufbx_memcpy(void *dst, const void *src, size_t count);
void *ufbx_memmove(void *dst, const void *src, size_t count);
void *ufbx_memset(void *dst, int ch, size_t count);
const void *ufbx_memchr(const void *ptr, int value, size_t count);
int ufbx_memcmp(const void *a, const void *b, size_t count);
int ufbx_strcmp(const char *a, const char *b);
int ufbx_strncmp(const char *a, const char *b, size_t count);📦 最低限必要なヘッダ
以下のヘッダは、ライブラリなしでも通常利用可能です:
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>もしこれらすら利用できない環境では:
#define UFBX_NO_LIBC_TYPESを定義し、自前でこれらの型定義を "ufbx.h" と "ufbx.c" の両方に用意する必要があります。
📘 備考
このドキュメントは MIT / Public Domain ライセンスのもとで公開された
ufbx (c) 2020 Samuli Raivio の内容を翻訳・整形しています。