The status of loading and viewing FBX models using different WebGL libraries.
Test environment: Windows 11 + Chrome 147
Legend:
| Model | Three.js r184 | Babylon.js custom loader | PlayCanvas | WebGL 1.0 | WebGL 2.0 | WebGPU | Notes |
|---|---|---|---|---|---|---|---|
| Samba Dancing | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned animation model. |
| morph_test | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Morph targets. WebGL 1.0: ?morph=0..1 URL param overrides DeformPercent when no curve is bundled. |
| monkey | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Static mesh with UV texture. |
| monkey_embedded_texture | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Static mesh with embedded texture. |
| vCube | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Static mesh with vertex colors. |
| archer/ArcherRi01 | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned animation model with multi-mesh + textures. |
| warrior/Warrior | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned animation model. ByVertice UV/normal mapping fix applied. |
| stanford-bunny | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Large static mesh (ASCII FBX). |
| mixamo | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned animation model. |
| RotationTest | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Non-skinned node animation. |
| exampleWindow | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Multi-mesh static model. |
| Head_69 | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Multi-material model (43 materials via per-polygon LayerElementMaterial). The bundled texture path is an absolute drive path so the file falls back to per-material diffuse colors. |
| morph-translation | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Morph targets (8 meshes). WebGL 1.0: ?morph=0..1 URL param overrides DeformPercent when no curve is bundled. |
| test/anim_euler_jump | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Euler angle jump test (parent-hierarchy animation). |
| test/anim_skin_bend | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinning bend test. |
| test/anim_root_motion | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Root motion animation test (parent-hierarchy animation). |
| gltf/AnimatedTriangle | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Node animation (glTF sample converted to FBX). |
| gltf/SimpleSkin | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned mesh (glTF sample). |
| gltf/RiggedSimple | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned mesh (glTF sample). |
| gltf/RiggedFigure | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned mesh (glTF sample). |
| gltf/Fox | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | :white_check_mark: Sample | Skinned mesh with multiple animation clips (glTF sample). |
Dimensions were measured by importing each FBX into Blender 5.1 and reading the world-space bounding box (Z-up, units in meters). The Unit column reflects the FBX file’s UnitScaleFactor value in GlobalSettings (1.0 = centimeters, 100.0 = meters). stanford-bunny.fbx is an ASCII FBX that Blender refuses to import, so its size is derived from the raw Vertices array with UnitScaleFactor and UpAxis applied.
| Model | Format | Unit | W × D × H (m) | Dimension notes |
|---|---|---|---|---|
| Samba Dancing | Binary | cm (UnitScaleFactor=1) | 0.65 × 1.17 × 1.75 | Character in dancing pose. |
| morph_test | Binary | cm (UnitScaleFactor=1) | 1.00 × 1.00 × 1.00 | 1 m cube. |
| monkey | Binary | cm (UnitScaleFactor=1) | 4.00 × 3.94 × 1.74 | Scaled-up Blender Suzanne. |
| monkey_embedded_texture | Binary | cm (UnitScaleFactor=1) | 4.00 × 3.94 × 1.74 | Same geometry as monkey. |
| vCube | Binary | cm (UnitScaleFactor=1) | 2.00 × 2.00 × 2.00 | 2 m cube. |
| archer/ArcherRi01 | Binary | m (UnitScaleFactor=100) | 1.32 × 1.24 × 1.67 | Only model authored in meters. |
| warrior/Warrior | Binary | cm (UnitScaleFactor=1) | 0.0043 × 0.0034 × 0.0067 | Authored at ~7 mm scale; needs upscaling at display time. |
| stanford-bunny | ASCII | cm (UnitScaleFactor=1) | 5.00 × 3.87 × 4.95 | Vertex coordinates reach ±250 cm; appears very large unless scaled down. |
| mixamo | Binary | cm (UnitScaleFactor=1) | 0.73 × 0.32 × 1.79 | Character in T-pose. |
| RotationTest | Binary | cm (UnitScaleFactor=1) | 2.00 × 2.00 × 2.00 | 2 m cube (rotation test). |
| exampleWindow | Binary | cm (UnitScaleFactor=1) | 0.56 × 0.37 × 0.071 | Window panel. |
| Head_69 | Binary | cm (UnitScaleFactor=1) | 0.0073 × 0.0080 × 0.0067 | Authored at ~7 mm scale; needs upscaling at display time. |
| morph-translation | Binary | cm (UnitScaleFactor=1) | 0.75 × 0.17 × 0.26 | Multi-mesh morph translation rig. |
| test/anim_euler_jump | Binary | cm (UnitScaleFactor=1) | 0.40 × 0.04 × 0.04 | Small animated bar. |
| test/anim_skin_bend | Binary | cm (UnitScaleFactor=1) | 0.16 × 0.16 × 1.00 | Thin skinned beam. |
| test/anim_root_motion | Binary | cm (UnitScaleFactor=1) | 0.12 × 0.12 × 0.80 | Root motion test bar. |
| gltf/AnimatedTriangle | Binary | cm (UnitScaleFactor=1) | 1.00 × 0 × 1.00 | Flat triangle. |
| gltf/SimpleSkin | Binary | cm (UnitScaleFactor=1) | 1.00 × 0 × 2.00 | Flat skinned plane. |
| gltf/RiggedSimple | Binary | cm (UnitScaleFactor=1) | 2.00 × 2.00 × 9.15 | Tall rigged column. |
| gltf/RiggedFigure | Binary | cm (UnitScaleFactor=1) | 0.57 × 0.35 × 1.48 | Rigged human figure. |
| gltf/Fox | Binary | cm (UnitScaleFactor=1) | 29.2 × 163.3 × 75.8 | glTF→FBX retained large scale; viewer applies an explicit scale. |
A standalone binary and ASCII FBX parser and Babylon.js mesh builder. No build step or npm install required — just include it as a <script> tag.
<script src="example/babylonjs/fbx-loader.js"></script>
FBXLoader.loadFBX(url, scene, options) — Load from URLconst nodes = await FBXLoader.loadFBX(url, scene, options);
| Parameter | Type | Description |
|---|---|---|
url |
string |
URL of the .fbx file to load |
scene |
BABYLON.Scene |
Target Babylon.js scene |
options |
object |
Optional settings (see below) |
Returns: Promise<BABYLON.Node[]> — all created nodes (meshes and transform nodes).
FBXLoader.loadFBXFromBuffer(buffer, baseDir, scene, options) — Load from ArrayBufferconst nodes = await FBXLoader.loadFBXFromBuffer(buffer, baseDir, scene, options);
| Parameter | Type | Description |
|---|---|---|
buffer |
ArrayBuffer |
Raw FBX file data |
baseDir |
string |
Base URL for resolving relative texture paths (e.g. "assets/models/") |
scene |
BABYLON.Scene |
Target Babylon.js scene |
options |
object |
Optional settings (see below) |
| Option | Type | Default | Description |
|---|---|---|---|
animation |
boolean |
true |
Start animation playback automatically |
animationTime |
number |
0 |
Initial animation time in seconds |
fbx-loader.js automatically registers itself as a Babylon.js Scene Loader Plugin when included, enabling the standard scene loader API for .fbx files.
// Babylon.js v9+: pass a single full URL
const result = await BABYLON.ImportMeshAsync('https://example.com/assets/model.fbx', scene);
console.log(result.meshes); // BABYLON.Mesh[]
console.log(result.skeletons); // BABYLON.Skeleton[]
// Load into an AssetContainer (can be added/removed from scene later)
const container = await BABYLON.LoadAssetContainerAsync('https://example.com/assets/model.fbx', scene);
container.addAllToScene();
Loader options can be passed via pluginOptions:
const result = await BABYLON.ImportMeshAsync('https://example.com/assets/model.fbx', scene, {
pluginOptions: { fbx: { animation: false, animationTime: 2.0 } },
});
To register manually, use FBXLoader.createPlugin():
BABYLON.RegisterSceneLoaderPlugin({
name: 'fbx',
extensions: '.fbx',
createPlugin: (opts) => FBXLoader.createPlugin(opts?.fbx ?? {}),
});
For Babylon.js v8 and earlier, use the SceneLoader class methods with a split rootUrl + fileName:
const result = await BABYLON.SceneLoader.ImportMeshAsync('', 'https://example.com/assets/', 'model.fbx', scene);
When a skinned animation is present, the root node’s metadata.fbxAnimationControls contains an array of animation control objects.
const nodes = await FBXLoader.loadFBX(url, scene);
// Find the root node (__root__)
const root = nodes.find(n => n.name === '__root__');
const controls = root?.metadata?.fbxAnimationControls ?? [];
// Each control exposes:
const ctrl = controls[0];
ctrl.name; // animation stack name (string)
ctrl.duration; // total duration in seconds (number)
ctrl.time; // current playback time (number)
ctrl.playing; // true if playing (boolean)
ctrl.setTime(t); // seek to time t (seconds)
ctrl.setPlaying(bool); // play or pause
ctrl.dispose(); // clean up observer when done
The root node’s metadata.fbxSkeletons contains an array of BABYLON.Skeleton objects for the loaded model.
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.babylonjs.com/babylon.js"></script>
<script src="fbx-loader.js"></script>
</head>
<body>
<canvas id="c" style="width:100%;height:100vh"></canvas>
<script>
const canvas = document.getElementById('c');
const engine = new BABYLON.Engine(canvas, true);
const scene = new BABYLON.Scene(engine);
const camera = new BABYLON.ArcRotateCamera('cam', -Math.PI / 2, Math.PI / 2.5, 5, BABYLON.Vector3.Zero(), scene);
camera.attachControl(canvas, true);
new BABYLON.HemisphericLight('light', new BABYLON.Vector3(0, 1, 0), scene);
// Direct load via FBXLoader.loadFBX
FBXLoader.loadFBX('model.fbx', scene, { animation: true }).then(nodes => {
console.log('Loaded nodes:', nodes.length);
});
// Or via the Scene Loader Plugin (auto-registered when fbx-loader.js is included)
// await BABYLON.ImportMeshAsync('https://example.com/model.fbx', scene); // v9+
// await BABYLON.SceneLoader.ImportMeshAsync('', '', 'model.fbx', scene); // v8
engine.runRenderLoop(() => scene.render());
window.addEventListener('resize', () => engine.resize());
</script>
</body>
</html>
The example viewer also accepts these query parameters:
| Parameter | Example | Description |
|---|---|---|
model |
?model=monkey |
Select a bundled model by name |
url |
?url=https://…/model.fbx |
Load an arbitrary FBX URL |
scale |
?scale=0.01 |
Override the model scale |
animation |
?animation=0 |
Disable auto-play (0 / false / off) |
time |
?time=1.5 |
Set the initial animation time (seconds) |
The Babylon.js sample uses a custom FBX parser supporting both binary and ASCII formats. It currently focuses on static mesh loading, transform hierarchy validation, basic skinning data, and sampled skeleton animation.
Supported or partially supported:
DeformPercent animation. In-between shapes (multi-FullWeights) currently use only the final shape.