Bevy Version: | 0.9 | (outdated!) |
---|
As this page is outdated, please refer to Bevy's official migration guides while reading, to cover the differences: 0.9 to 0.10, 0.10 to 0.11, 0.11 to 0.12, 0.12 to 0.13, 0.13 to 0.14.
I apologize for the inconvenience. I will update the page as soon as I find the time.
3D Models and Scenes (GLTF)
Relevant official examples:
load_gltf
,
update_gltf_scene
.
Bevy uses the GLTF 2.0 file format for 3D assets.
(other formats may be unofficially available via 3rd-party plugins)
Quick-Start: Spawning 3D Models into your World
The simplest use case is to just load a "3D model" and spawn it into the game world.
"3D models" can often be complex, consisting of multiple parts. Think of a house: the windows, roof, doors, etc., are separate pieces, that are likely made of multiple meshes, materials, and textures. Bevy would technically need multiple ECS Entities to represent and render the whole thing.
This is why your GLTF "model" is represented by Bevy as a [Scene][cb::scene]. This way, you can easily spawn it, and Bevy will create all the relevant child entities and configure them correctly.
fn spawn_gltf(
mut commands: Commands,
ass: Res<AssetServer>,
) {
// note that we have to include the `Scene0` label
let my_gltf = ass.load("my.glb#Scene0");
// to position our 3d model, simply use the Transform
// in the SceneBundle
commands.spawn(SceneBundle {
scene: my_gltf,
transform: Transform::from_xyz(2.0, 0.0, -5.0),
..Default::default()
});
}
You could also use GLTF files to load an entire map/level. It works the same way.
The above example assumes that you have a simple GLTF file containing only one "default scene". GLTF is a very flexible file format. A single file can contain many "models" or more complex "scenes". To get a better understanding of GLTF and possible workflows, read the rest of this page. :)
Introduction to GLTF
GLTF is a modern open standard for exchanging 3D assets between different 3D software applications, like game engines and 3D modeling software.
The GLTF file format has two variants: human-readable ascii/text (*.gltf
)
and binary (*.glb
). The binary format is more compact and preferable
for packaging the assets with your game. The text format may be useful for
development, as it can be easier to manually inspect using a text editor.
A GLTF file can contain many objects (sub-assets): meshes, materials, textures, scenes, animation clips. When loading a GLTF file, Bevy will load all of the assets contained inside. They will be mapped to the appropriate Bevy-internal asset types.
The GLTF sub-assets
GLTF terminology can be confusing, as it sometimes uses the same words to refer to different things, compared to Bevy. This section will try explain the various GLTF terms.
To understand everything, it helps to mentally consider how these concepts are represented in different places: in your 3D modeling software (like Blender), in the GLTF file itself, and in Bevy.
GLTF Scenes are what you spawn into your game world. This is typically what you see on the screen in your 3D modeling software. Scenes combine all of the data needed for the game engine to create all the needed entities to represent what you want. Conceptually, think of a scene as one "unit". Depending on your use case, this could be one "3d model", or even a whole map or game level. In Bevy, these are represented as Bevy Scenes with all the child ECS entities.
GLTF Scenes are composed of GLTF Nodes. These describe the "objects"
in the scene, typically GLTF Meshes, but can also be other things like
Cameras and Lights. Each GLTF Node has a transform for positioning it in
the scene. GLTF Nodes do not have a core Bevy equivalent; Bevy just uses
this data to create the ECS Entities inside of a Scene. Bevy has a special
GltfNode
asset type, if you need access to this data.
GLTF Meshes represent one conceptual "3D object". These correspond
to the "objects" in your 3D modeling software. GLTF Meshes may be complex
and composed of multiple smaller pieces, called GLTF Primitives, each of
which may use a different Material. GLTF Meshes do not have a core Bevy
equivalent, but there is a special GltfMesh
asset type,
which describes the primitives.
GLTF Primitives are individual "units of 3D geometry", for the purposes of
rendering. They contain the actual geometry / vertex data, and reference the
Material to be used when drawing. In Bevy, each GLTF Primitive is represented
as a Bevy Mesh
asset, and must be spawned as a separate ECS
Entity to be rendered.
GLTF Materials describe the shading parameters for the surfaces of
your 3D models. They have full support for Physically-Based Rendering
(PBR). They also reference the textures to use. In Bevy, they are represented
as StandardMaterial
assets, as used by the Bevy
PBR 3D renderer.
GLTF Textures (images) can be embedded inside the GLTF file, or stored
externally in separate image files alongside it. For example, you can have
your textures as separate PNG/JPEG/KTX2 files for ease of development, or
package them all inside the GLTF file for ease of distribution. In Bevy,
GLTF textures are loaded as Bevy Image
assets.
GLTF Samplers describe the settings for how the GPU should use a
given Texture. Bevy does not keep these separate; this data is stored
inside the Bevy Image
asset (the sampler
field of type
SamplerDescriptor
).
GLTF Animations describe animations that interpolate various values,
such as transforms or mesh skeletons, over time. In Bevy, these are loaded
as AnimationClip
assets.
GLTF Usage Patterns
A single GLTF file can contain any number of sub-assets of any of the above types, referring to each other however they like.
Because GLTF is so flexible, it is up to you how to structure your assets.
A single GLTF file might be used:
- To represent a single "3D model", containing a single GLTF Scene with the model, so you can spawn it into your game.
- To represent a whole level, as a GLTF Scene, possibly also including the camera. This lets you load and spawn a whole level/map at once.
- To represent sections of a level/map, such as a rooms, as separate GLTF Scenes. They can share meshes and textures if needed.
- To contain a set of many different "3D models", each as a separate GLTF Scene. This lets you load and manage the whole collection at once and spawn them individually as needed.
- … others?
Tools for Creating GLTF Assets
If you are using a recent version of Blender (2.8+) for 3D modeling, GLTF is supported out of the box. Just export and choose GLTF as the format.
For other tools, you can try these exporter plugins:
- Old Blender (2.79)
- 3DSMax
- Autodesk Maya
- (or this alternative)
Be sure to check your export settings to make sure the GLTF file contains everything you expect.
If you need Tangents for normal maps, it is recommended that you include them in your GLTF files. This avoids Bevy having to autogenerate them at runtime. Many 3D editors do not enable this option by default.
Textures
For your Textures / image data, the GLTF format specification officially limits the supported formats to just PNG, JPEG, or Basis. However, Bevy does not enforce such "artificial limitations". You can use any image format supported by Bevy.
Your 3D editor will likely export your GLTF with PNG textures. This will "just work" and is nice for simple use cases.
However, mipmaps and compressed textures are very important to get good GPU performance, memory (VRAM) usage, and visual quality. You will only get these benefits if you use a format like KTX2 or DDS, that supports these features.
We recommend that you use KTX2, which natively supports all GPU texture
functionality + additional zstd
compression on top, to reduce file size.
If you do this, don't forget to enable the ktx2
and zstd
cargo
features for Bevy.
You can use the klafsa
tool to convert all the textures
used in your GLTF files from PNG/JPEG into KTX2, with mipmaps and GPU texture
compression of your choice.
TODO: show an example workflow for converting textures into the "optimal" format
Using GLTF Sub-Assets in Bevy
The various sub-assets contained in a GLTF file can be addressed in two ways:
- by index (integer id, in the order they appear in the file)
- by name (text string, the names you set in your 3D modeling software when creating the asset, which can be exported into the GLTF)
To get handles to the respective assets in Bevy, you can use the
Gltf
"master asset", or alternatively,
AssetPath with Labels.
Gltf
master asset
If you have a complex GLTF file, this is likely the most flexible and useful way of navigating its contents and using the different things inside.
You have to wait for the GLTF file to load, and then use the Gltf
asset.
use bevy::gltf::Gltf;
/// Helper resource for tracking our asset
#[derive(Resource)]
struct MyAssetPack(Handle<Gltf>);
fn load_gltf(
mut commands: Commands,
ass: Res<AssetServer>,
) {
let gltf = ass.load("my_asset_pack.glb");
commands.insert_resource(MyAssetPack(gltf));
}
fn spawn_gltf_objects(
mut commands: Commands,
my: Res<MyAssetPack>,
assets_gltf: Res<Assets<Gltf>>,
) {
// if the GLTF has loaded, we can navigate its contents
if let Some(gltf) = assets_gltf.get(&my.0) {
// spawn the first scene in the file
commands.spawn(SceneBundle {
scene: gltf.scenes[0].clone(),
..Default::default()
});
// spawn the scene named "YellowCar"
commands.spawn(SceneBundle {
scene: gltf.named_scenes["YellowCar"].clone(),
transform: Transform::from_xyz(1.0, 2.0, 3.0),
..Default::default()
});
// PERF: the `.clone()`s are just for asset handles, don't worry :)
}
}
For a more convoluted example, say we want to directly create a 3D PBR entity, for whatever reason. (This is not recommended; you should probably just use scenes)
use bevy::gltf::GltfMesh;
fn gltf_manual_entity(
mut commands: Commands,
my: Res<MyAssetPack>,
assets_gltf: Res<Assets<Gltf>>,
assets_gltfmesh: Res<Assets<GltfMesh>>,
) {
if let Some(gltf) = assets_gltf.get(&my.0) {
// Get the GLTF Mesh named "CarWheel"
// (unwrap safety: we know the GLTF has loaded already)
let carwheel = assets_gltfmesh.get(&gltf.named_meshes["CarWheel"]).unwrap();
// Spawn a PBR entity with the mesh and material of the first GLTF Primitive
commands.spawn(PbrBundle {
mesh: carwheel.primitives[0].mesh.clone(),
// (unwrap: material is optional, we assume this primitive has one)
material: carwheel.primitives[0].material.clone().unwrap(),
..Default::default()
});
}
}
AssetPath with Labels
This is another way to access specific sub-assets. It is less reliable, but may be easier to use in some cases.
Use the AssetServer
to convert a path string into a
Handle
.
The advantage is that you can get handles to your sub-assets immediately, even if your GLTF file hasn't loaded yet.
The disadvantage is that it is more error-prone. If you specify a sub-asset that doesn't actually exist in the file, or mis-type the label, or use the wrong label, it will just silently not work. Also, currently only using a numerial index is supported. You cannot address sub-assets by name.
fn use_gltf_things(
mut commands: Commands,
ass: Res<AssetServer>,
) {
// spawn the first scene in the file
let scene0 = ass.load("my_asset_pack.glb#Scene0");
commands.spawn(SceneBundle {
scene: scene0,
..Default::default()
});
// spawn the second scene
let scene1 = ass.load("my_asset_pack.glb#Scene1");
commands.spawn(SceneBundle {
scene: scene1,
transform: Transform::from_xyz(1.0, 2.0, 3.0),
..Default::default()
});
}
The following asset labels are supported ({}
is the numerical index):
Scene{}
: GLTF Scene as BevyScene
Node{}
: GLTF Node asGltfNode
Mesh{}
: GLTF Mesh asGltfMesh
Mesh{}/Primitive{}
: GLTF Primitive as BevyMesh
Mesh{}/Primitive{}/MorphTargets
: Morph target animation data for a GLTF PrimitiveTexture{}
: GLTF Texture as BevyImage
Material{}
: GLTF Material as BevyStandardMaterial
DefaultMaterial
: as above, if the GLTF file contains a default material with no indexAnimation{}
: GLTF Animation as BevyAnimationClip
Skin{}
: GLTF mesh skin as BevySkinnedMeshInverseBindposes
The GltfNode
and GltfMesh
asset types are only useful to help you navigate the contents of
your GLTF file. They are not core Bevy renderer types, and not used
by Bevy in any other way. The Bevy renderer expects Entities with
MaterialMeshBundle
; for that you need the
Mesh
and StandardMaterial
.
Bevy Limitations
Bevy does not fully support all features of the GLTF format and has some specific requirements about the data. Not all GLTF files can be loaded and rendered in Bevy. Unfortunately, in many of these cases, you will not get any error or diagnostic message.
Commonly-encountered limitations:
- Textures embedded in ascii (
*.gltf
) files (base64 encoding) cannot be loaded. Put your textures in external files, or use the binary (*.glb
) format. - Mipmaps are only supported if the texture files (in KTX2 or DDS format) contain them. The GLTF spec requires missing mipmap data to be generated by the game engine, but Bevy does not support this yet. If your assets are missing mipmaps, textures will look grainy/noisy.
This list is not exhaustive. There may be other unsupported scenarios that I did not know of or forgot to include here. :)