flutter3d
Showcase Changelog 63 packages API reference

Bringing assets in

flutter3d convert reads what other tools save and writes the engine's own files. Each input becomes one .f3d: the model with its lights and cameras, its materials and textures, its material language programs, and its scene as prefab documents, with any other model the scene names carried inside it. --split writes the same things as separate files instead: models as .f3d, materials as .fmat (or .f3dmat when a material is a program), and scenes and prefabs as level documents with prefabs. It is the flutter3d command from flutter3d_build:

dart pub global activate flutter3d_build 1.0.0-rc.1
flutter3d doctor                       # what is installed, and what is missing
flutter3d convert MyUnityGame/Assets/Prefabs/Crate.prefab -o assets_src/imported
flutter3d convert 'MyGodotGame/**.tscn' -o assets_src/imported --dry-run

Every run reads every input first and writes at the end. --dry-run writes nothing and says what it would. A file that already exists with other contents is refused unless --overwrite is given, and then nothing at all is written; a file that already holds exactly what would be written is not a conflict, so running the same conversion twice is fine. The output is the same bytes on every machine.

Every input gets a report: what it wrote, what mapped, what was approximated, and what was dropped and why. --json prints it as JSON, and --report file.json saves it as well.

What each format becomes #

With --split, each format writes the files below. Without it, all of them go into the one <name>.f3d (see what a bundle carries).

Input Output with --split Notes
glTF, GLB <name>.f3d, materials/*.fmat, <name>.level.json The scene is a prefab placing the model with its own node tree. PBR and the KHR_materials_* layers the engine draws (clear coat, sheen, transmission, volume, IOR, specular, iridescence, anisotropy, dispersion) are kept.
OBJ + MTL <name>.f3d, materials/*.fmat The MTL's Phong terms are approximated as metal-rough, as the loader does.
STL <name>.f3d Geometry only.
PLY mesh <name>.f3d ASCII and binary; positions, normals, colours, UVs and faces. A file without faces is a point cloud and is refused.
PLY / SPZ splat capture <name>.f3dsplat A PLY whose header names f_dc_0.
USDA, USDZ <name>.f3d, materials/*.fmat, <name>.level.json One model per layer. A reference to another file is an instance of that file's prefab. UsdPreviewSurface and MaterialX standard_surface become metal-rough. Custom attributes ride the rows (see USD for twins). upAxis Z is turned to Y and metersPerUnit to metres.
USDC (binary) as USDA Through usdcat, when installed.
MaterialX .mtlx materials/*.fmat, or *.f3dmat beside an .fmat See materials.
Unity .prefab, .unity <name>.level.json, models/*.f3d, materials/*.fmat Needs the project's .meta files to find meshes, materials and textures.
Unity .mat materials/*.fmat Built-in Standard, URP Lit and HDRP Lit.
Godot .tscn <name>.level.json, models/*.f3d, materials/*.fmat Godot 4 (format 3) and Godot 3 (format 2). res:// is the directory holding project.godot.
Godot .tres materials/*.fmat StandardMaterial3D, ORMMaterial3D, Godot 3's SpatialMaterial.
FBX as glTF Through FBX2glTF, or Blender when FBX2glTF is absent.
.blend as glTF Through Blender in the background.

A converted scene is a level document, format version 2: every prefab it needs under prefabs, the materials it names under materials (each one deferring to its .fmat through the fmat key), and one instance of the input's own prefab at the origin, so the document opens as the thing it was converted from. Rows are model entities, which ModelVisuals draws from the file named by asset, prop entities for primitive shapes, which PropVisuals draws by shape and size, and prefab instances. The paths in the document are asset paths: the output directory as typed, or --asset-prefix. The document carries generatedBy, so the editor offers to save a copy instead of writing over it.

What a bundle carries #

The default <name>.f3d holds, besides the model:

From Dart, F3dDocument.lights, cameras, programs, prefabs (JSON objects for Level.fromJson) and files read them back. No section is must-understand: a reader that predates one opens a poorer asset, never a different one.

Placement #

A level places a row by at and yaw, and an instance turns its prefab by its yaw alone. A converter writes the rest of a rotation as tilt, a quaternion applied before the yaw, and a scale as scale; ModelVisuals reads both. Since an instance cannot carry a tilt or a scale to its rows, an instance placed at an angle or scaled is written out row by row instead, which keeps the picture and loses the link to the template. The report says which ones.

Unity is left-handed and the engine is right-handed. Every Unity transform is mirrored through Z: a position's z and a rotation's x and y change sign. Unity also mirrors a model through X when it imports it, and the engine's .f3d of the same file is not mirrored, so a Unity model row carries a half turn about Y on top. Godot and glTF need nothing: both are right-handed, Y up, metres.

Materials #

A material that is only parameters of a built-in lighting model is an .fmat, which the loader reads with no build step. That covers glTF, MTL, UsdPreviewSurface, Unity's and Godot's materials, and every MaterialX material whose inputs are constants or images. An image multiplied by a constant is folded into the factor.

A MaterialX node graph that computes an input, such as a mix of two colours across the surface, becomes a program: <name>.f3dmat in the material language, and an .fmat beside it that names the program as its lighting and binds its images. The program's diffuse and highlight come from the graph; the highlight is a normalised Blinn-Phong lobe, an approximation of the engine's own metal-rough response. Convert into assets_src/ and the build hook compiles it. The converter parses the program before writing it. A graph with a node the language has no word for (noise, for example) is folded to the nearest metal-rough surface, with a warning naming the node.

Unity and Godot keep metal and roughness in other channels than glTF does. Unity's metallic/smoothness map, HDRP's mask map and Godot's separate metallic and roughness maps are repacked into one image with occlusion in red, roughness in green and metal in blue. Godot's ORM image already is one.

USD for twins #

A digital twin usually arrives as a USD stage exported from a plant or building model: a root layer that places equipment, each piece referencing its own file, and on every element the ids and ratings the twin's database knows it by, written as custom attributes. The converter keeps that shape.

[
  { "type": "prefab", "name": "Pump01",
    "props": { "prefab": "pump",
               "attributes": { "twin:id": "P-101", "twin:ratedFlow": 12.5 } } },
  { "type": "model", "name": "plant",
    "props": { "asset": "plant.f3d",
               "nodeAttributes": { "Plant/Tank": { "twin:id": "T-1" } } } }
]

From Dart, PrefabAttributes.of(entity) reads both, and findByAttribute finds an element in a level after expandPrefabs, on an entity or on a node of its model. Expanding an instance gives its attributes to every row it becomes, so each part of a pump placed as P-101 is P-101 in the played level, and an instance overrides one of a template row's with the key attributes.twin:id. The engine itself reads none of them.

What the report says was not carried, one line each: a relationship (rel twin:feeds) or a connection, which name prims rather than values; an attribute with no value; lights, cameras, skeletons, instancers, curves and volumes; over and class prims. A time-sampled attribute carries its first sample, with a warning. The report's mapped lines count the attributes and say where they went.

What does not survive #

Lost Why
Lights and cameras from USD, Unity and Godot Place level lights. glTF's stay on their nodes inside the model.
Scripts, colliders, rigidbodies, particles Behaviour, not content.
Animation in USD The first time sample is the rest pose. glTF and FBX keep their animation inside the model.
USD skeletons, instancers, curves, points, volumes Not read. Export glTF for skinned models.
USD composition beyond references over and class prims, variants and inherits are not evaluated. A reference to a sub-prim places the whole layer. Prims defined under a referencing prim are kept; prims written over it are not.
USD relationships as attributes A rel names prims, which a row cannot hold. The report lists each one.
Units between USD layers Each layer is converted in its own units, as its author meant. USD itself would compose the referenced numbers unscaled. The report says when layers disagree.
Unity overrides other than placement and materials A level overrides a row's own keys, and m_CastShadows has no counterpart. Added and removed components and objects are dropped.
Unity detail, parallax and coat maps; HDRP's other inputs No slot in the engine's surface.
The specular workflow Read as a dielectric with its smoothness.
Godot rim, subsurface, refraction, detail, height No counterpart.
ArrayMesh data inside a Godot scene, CSG Save the mesh as glTF, bake the CSG.
Unity's capsule, plane and quad Drawn as a cylinder and thin boxes.

External tools #

Tool For Install
FBX2glTF .fbx A release from github.com/godotengine/FBX2glTF, on PATH or named by FLUTTER3D_FBX2GLTF
Blender .blend, and .fbx without FBX2glTF blender.org, blender on PATH or named by FLUTTER3D_BLENDER
usdcat binary .usdc layers pip install usd-core

Without them those inputs are refused with what to install, and the command exits 3. flutter3d doctor lists them beside the SDK checks: Dart and Flutter against the floors and the versions CI runs, impellerc, and glslangValidator and naga for the WebGPU section of a material.

Exit codes #

Code Meaning
0 Every input converted
1 An input could not be read or written
2 Usage
3 A needed external tool is not installed
4 An output exists with other contents and --overwrite was not given

As a library #

A service that takes uploads calls the same conversion with bytes on both sides, from package:flutter3d_build/convert.dart:

import 'package:flutter3d_build/convert.dart';

final result = await convertFiles(
  files,                         // Map<String, Uint8List>, by path in the bundle
  to: ConversionTarget.everything,
  entry: 'Assets/Scenes/Room.unity',
);
result.files;                    // Map<String, Uint8List>: one Room.f3d bundle
                                 // (bundle: false gives .f3d, .fmat, .level.json)
result.reports;                  // what mapped, what was dropped

It is plain Dart and runs no external program: FBX, .blend and binary USD come back as unsupported, with exit code 3 in result.exitCode.