Skip to content

Assets and loading

Most scenes are built from 3D models authored elsewhere. Flutter Scene imports glTF binaries (.glb) two ways. You can load a .glb directly at runtime, or preprocess it into the engine’s .fsceneb package at build time and load that. Both produce the same live Node tree you add to a scene.

The demo below loads a .glb model at runtime and frames it from its own bounds.

A glTF model loaded at runtime with Node.fromGlbAsset.

Node.fromGlbAsset parses a .glb from the asset bundle and returns a ready-to-add Node. Declare the file under assets in your pubspec.yaml, then load it.

final model = await Node.fromGlbAsset('assets/model.glb');
scene.add(model);

The imported tree keeps the model’s node hierarchy, meshes, and materials. The materials are lit by the scene’s environment like any other.

Node.fromGlbBytes does the same from bytes you already have (a network download, a file the user picked), so runtime import is the path for content that is not known at build time.

final model = await Node.fromGlbBytes(bytes);

Parsing glTF at runtime costs time on the first frame. For content you ship with the app, convert it to the engine’s .fsceneb package at build time instead. A build hook does the conversion, and loadScene loads the result by its source path.

Add a hook/build.dart that calls buildScenes.

import 'package:hooks/hooks.dart';
import 'package:flutter_scene/build_hooks.dart';
void main(List<String> args) {
build(args, (input, output) async {
buildScenes(
buildInput: input,
buildOutput: output,
inputFilePaths: ['assets_src/model.glb'],
);
});
}

Then load it by the same source path. The engine loads the prebuilt package, not the original .glb.

final model = await loadScene('assets_src/model.glb');
scene.add(model);

loadScene also supports seamless hot reload of the asset during development through its onReload callback.

Building a scene takes several frames. Models decode, textures upload, and the environment bakes. If you add nodes as each one arrives, the scene renders half-built, with objects appearing one at a time. To avoid that, hand SceneView a ResourceGroup and it holds the scene off-screen until everything is ready, then reveals it in one frame.

Track each load in the group, then give the view a loadingBuilder to show while it waits. The builder receives the group’s progress from 0 to 1, so you can drive a progress bar.

final loading = ResourceGroup();
Future<void> _load() async {
final modelFuture = loading.add(Node.fromGlbAsset('assets/model.glb'));
final skyFuture = loading.add(
EnvironmentMap.fromAssets(radianceImagePath: 'assets/sky.png'),
);
scene.add(await modelFuture);
scene.environment = await skyFuture;
}
// ...in build():
SceneView(
scene,
loading: loading,
loadingBuilder: (context, progress) => MyLoadingScreen(progress: progress),
)

Add the loaded nodes to the scene as usual. The view simply does not draw the scene until the group finishes, so nothing pops in at the origin along the way. Register every load up front, before awaiting, so the progress reflects the whole set. revealMinDuration keeps a fast load from flashing the loading screen for a single frame, and a view with no loading renders immediately, exactly as before.

Even once a scene’s data is loaded, the first frame that draws a new material has to compile its render pipeline, which can stall that one frame. Set warmUp on SceneView to compile the scene’s pipelines while the loading screen is still up, so the first visible frame is smooth.

SceneView(
scene,
loading: loading,
warmUp: true,
loadingBuilder: (context, progress) => MyLoadingScreen(progress: progress),
)

Warm-up runs once the loading group is ready and before the scene is revealed, so populate the scene first; it compiles whatever is attached when it runs. For manual control outside a SceneView, call Scene.warmUp with the views you will render.

Large models loaded at runtime with Node.fromGlbAsset also pack their geometry off the UI thread, so a heavy import does not freeze the app while the loading screen animates.

.fsceneb is the binary form of the .fscene document format, the engine’s own representation of a scene. The build hook writes it, and loadScene reads it. You rarely touch the format directly; the loadScene loader and the document types in the fscene library cover authoring and inspecting one.

Textures referenced by a glTF are imported with the model, so a textured asset arrives fully shaded. Standalone textures and environment panoramas are loaded separately from assets; see Materials for textures and Lighting and environment for environment maps.

For large scenes you can load subtrees on demand and reuse instanced subtrees as prefabs rather than loading everything up front. See loadSceneSubtree and the prefab types in the fscene library.

Node.fromGlbAsset, Node.fromGlbBytes, loadScene, and buildScenes (from package:flutter_scene/build_hooks.dart) for loading. ResourceGroup, SceneView.loading, SceneView.warmUp, and Scene.warmUp for loading screens and warm-up.