flutter3d
Showcase Changelog 38 packages API reference

Loading into a scene

since 0.4.0 or earlier Formats and I/O

GltfLoader, ObjLoader and every other reader in this engine produce a ModelDocument: geometry, materials and a hierarchy, still on the CPU. ModelAsset.fromDocument is the one path from there onto a GPU, whatever format the document came from.

Step 1: A decoded document #

This page builds its own rather than reading a file, but the shape is exactly what a real loader hands back: surfaces, a material, and a node naming each one.

// A decoded document, the shape every format in this package produces.
// `ModelAsset.fromDocument` does not care which one wrote it.
final _document = PlainModelDocument(
  surfaces: <ModelSurface>[
    ModelSurface(
      mesh: SphereShape(segments: 32, rings: 16).build(),
      materialIndex: 0,
    ),
  ],
  materials: <SurfaceMaterial>[
    SurfaceMaterial(
      name: 'shell',
      baseColor: Vector4(0.75, 0.4, 0.6, 1.0),
      metallic: 0.2,
      roughness: 0.4,
    ),
  ],
  nodes: <ModelNode>[
    ModelNode(surfaces: <int>[0]),
  ],
);

Step 2: Upload it once #

Meshes and images are shared by identity, so a document that reuses one mesh across several surfaces uploads it once. Materials come out as the engine's own Material, with textures resolved and bound.

// Meshes and images are uploaded once, deduplicated by identity; a
// document's materials become the engine's own `Material`, ready to be
// placed as many times as a scene wants.
_asset = await ModelAsset.fromDocument(_document, device: context.device);

Step 3: Place it in a scene #

An asset is immutable and GPU-resident; instantiate is what puts it somewhere. Calling it twice makes two independent instances that share the same uploaded mesh, which is the reason the asset and the instance are two different things: loading a model twice would upload it twice.

// `instantiate` rebuilds the asset's hierarchy as scene nodes and
// returns a `ModelInstance`: the node it hangs from, one scene node a
// decoded node, and a skeleton or an animation player when the asset
// carries one.
final scene = Scene();
_instance = _asset.instantiate(scene);

Step 4: Check the claim #

The asset has to have actually uploaded something, instantiate has to have added a mesh to the scene, and the frame has to have drawn it.

if (_asset.vertexCount == 0) {
  throw StateError('the asset uploaded no vertices');
}
if (_instance == null || _instance!.meshes.isEmpty) {
  throw StateError('instantiate did not add a mesh to the scene');
}
if (frame.drawCalls < 1) {
  throw StateError('the instance was not drawn');
}