// glTF / GLB models

Loading 3D models

Besides procedural shapes, flutter_fiber loads finished 3D models in glTF 2.0 format: a binary .glb, or a .gltf with its data embedded. A loaded model keeps its node hierarchy, PBR materials and textures, and sits in a Fiber3DCanvas like any other shape, lit by the same lights and environment.

Loading is two steps: parse the file into a Fiber3DGltfScene, then place that scene in the canvas with the Fiber3DGltfModel widget.

// demo

Demo

A 30-material car model loaded from a .glb with the code below, lit with ACES Filmic tone mapping and the default procedural sky.

// how to use it

How to use it

1. Put the model in your app and declare it under flutter: assets: in pubspec.yaml:

yaml
flutter:
  assets:
    - assets/YOUR_MODEL.glb

2. Load the bytes, parse them, and show the scene. This is a complete widget you can drop into your app:

dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_fiber/flutter_fiber.dart';

class ModelViewer extends StatefulWidget {
  const ModelViewer({super.key});

  @override
  State<ModelViewer> createState() => _ModelViewerState();
}

class _ModelViewerState extends State<ModelViewer> {
  Fiber3DGltfScene? _scene;
  Object? _error;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    try {
      final data = await rootBundle.load('assets/YOUR_MODEL.glb');
      final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
      final scene = await Fiber3DGltfLoader.loadGlb(bytes);
      if (!mounted) return;
      setState(() => _scene = scene);
    } catch (e) {
      if (!mounted) return;
      setState(() => _error = e);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_error != null) return Center(child: Text('Failed to load model:\n$_error'));
    final scene = _scene;
    if (scene == null) return const Center(child: CircularProgressIndicator());

    return Fiber3DCanvas(
      backgroundColor: 0x1a1a1a,
      toneMapping: Fiber3DToneMapping.acesFilmic,
      camera: Fiber3DCamera(
        position: const Fiber3DVector3(0, 2, 8),
        target: const Fiber3DVector3.zero(),
      ),
      orbitEnabled: true,
      lights: const [
        Fiber3DAmbientLight(color: 0xffffff, intensity: 0.3),
        Fiber3DPointLight(
          color: 0xffffff,
          intensity: 3.0,
          position: Fiber3DVector3(4, 5, 5),
        ),
        Fiber3DPointLight(
          color: 0xffffff,
          intensity: 1.5,
          position: Fiber3DVector3(-4, 2, -2),
        ),
      ],
      children: [Fiber3DGltfModel(scene: scene, scale: 1.0)],
    );
  }
}

Scale and framing. glTF files disagree on real-world units (meters versus centimeters is common), so a model can arrive far smaller or larger than your camera expects. Use Fiber3DGltfModel.scale and the camera distance to frame it.

Rotating or moving a model. Wrap it in a Fiber3DGroup and drive that group's transform in onFrame:

dart
Fiber3DGroup(
  onFrame: (elapsed, delta, transform) {
    final t = delta.inMicroseconds / 1e6;
    transform.rotation.y += t * 0.5;
  },
  children: [Fiber3DGltfModel(scene: scene, scale: 1.0)],
)

A .gltf with embedded data. Read the file as text and use loadEmbeddedGltf:

dart
final text = await rootBundle.loadString('assets/YOUR_MODEL.gltf');
final scene = await Fiber3DGltfLoader.loadEmbeddedGltf(text);

// api reference

API reference

Fiber3DGltfLoader.loadGlb(Uint8List bytes)

Parses a binary .glb. Returns Future<Fiber3DGltfScene>.

Fiber3DGltfLoader.loadEmbeddedGltf(String jsonText)

Parses a .gltf whose buffers and images are all base64 data URIs. You read the file text yourself. Returns Future<Fiber3DGltfScene>.

Fiber3DGltfModel(scene:, scale:)

Widget that adds a loaded scene to the canvas. scale (default 1.0) is a uniform scale applied to the whole model.

// supported & limits

Supported & limits

Supported

  • .glb, and .gltf with embedded (data URI) buffers and images; PNG and JPEG images embedded in the file.
  • The node hierarchy, with translation / rotation / scale or a single matrix per node.
  • Triangle meshes with POSITION, NORMAL, TEXCOORD_0 and float COLOR_0 attributes, indexed or not.
  • The metallic-roughness material: base color factor and texture, metallic and roughness factors, the combined metallic-roughness texture, emissiveFactor, and alphaMode BLEND (as transparency).
  • KHR_materials_clearcoat (clearcoat factors) and KHR_materials_emissive_strength.

Not supported (yet)

  • External .bin or image files referenced by URI.
  • Primitives with more than 65,535 vertices (the loader throws a FormatException).
  • Non-triangle primitives (lines, points, strips, fans) are skipped. Skinning, morph targets, animations, cameras and lights in the file are ignored.
  • Normal, emissive and occlusion textures; alphaMode MASK (treated as opaque); texture wrap modes and UV transforms; normalized (non-float) vertex colors.
  • Other KHR_materials_* extensions (specular, transmission and the rest) are ignored, so glass built with transmission renders as a plain alpha blend.
  • There is no depth-sorted transparency pass, so a model with many alpha-blended parts can show sorting artifacts.