Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_gltf/src/lib.rs
9370 views
1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![forbid(unsafe_code)]
3
#![doc(
4
html_logo_url = "https://bevy.org/assets/icon.png",
5
html_favicon_url = "https://bevy.org/assets/icon.png"
6
)]
7
8
//! Plugin providing an [`AssetLoader`](bevy_asset::AssetLoader) and type definitions
9
//! for loading glTF 2.0 (a standard 3D scene definition format) files in Bevy.
10
//!
11
//! The [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) defines the format of the glTF files.
12
//!
13
//! # Quick Start
14
//!
15
//! Here's how to spawn a simple glTF scene
16
//!
17
//! ```
18
//! # use bevy_ecs::prelude::*;
19
//! # use bevy_asset::prelude::*;
20
//! # use bevy_scene::prelude::*;
21
//! # use bevy_transform::prelude::*;
22
//! # use bevy_gltf::prelude::*;
23
//!
24
//! fn spawn_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
25
//! commands.spawn((
26
//! // This is equivalent to "models/FlightHelmet/FlightHelmet.gltf#Scene0"
27
//! // The `#Scene0` label here is very important because it tells bevy to load the first scene in the glTF file.
28
//! // If this isn't specified bevy doesn't know which part of the glTF file to load.
29
//! SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"))),
30
//! // You can use the transform to give it a position
31
//! Transform::from_xyz(2.0, 0.0, -5.0),
32
//! ));
33
//! }
34
//! ```
35
//! # Loading parts of a glTF asset
36
//!
37
//! ## Using `Gltf`
38
//!
39
//! If you want to access part of the asset, you can load the entire `Gltf` using the `AssetServer`.
40
//! Once the `Handle<Gltf>` is loaded you can then use it to access named parts of it.
41
//!
42
//! ```
43
//! # use bevy_ecs::prelude::*;
44
//! # use bevy_asset::prelude::*;
45
//! # use bevy_scene::prelude::*;
46
//! # use bevy_transform::prelude::*;
47
//! # use bevy_gltf::Gltf;
48
//!
49
//! // Holds the scene handle
50
//! #[derive(Resource)]
51
//! struct HelmetScene(Handle<Gltf>);
52
//!
53
//! fn load_gltf(mut commands: Commands, asset_server: Res<AssetServer>) {
54
//! let gltf = asset_server.load("models/FlightHelmet/FlightHelmet.gltf");
55
//! commands.insert_resource(HelmetScene(gltf));
56
//! }
57
//!
58
//! fn spawn_gltf_objects(
59
//! mut commands: Commands,
60
//! helmet_scene: Res<HelmetScene>,
61
//! gltf_assets: Res<Assets<Gltf>>,
62
//! mut loaded: Local<bool>,
63
//! ) {
64
//! // Only do this once
65
//! if *loaded {
66
//! return;
67
//! }
68
//! // Wait until the scene is loaded
69
//! let Some(gltf) = gltf_assets.get(&helmet_scene.0) else {
70
//! return;
71
//! };
72
//! *loaded = true;
73
//!
74
//! // Spawns the first scene in the file
75
//! commands.spawn(SceneRoot(gltf.scenes[0].clone()));
76
//!
77
//! // Spawns the scene named "Lenses_low"
78
//! commands.spawn((
79
//! SceneRoot(gltf.named_scenes["Lenses_low"].clone()),
80
//! Transform::from_xyz(1.0, 2.0, 3.0),
81
//! ));
82
//! }
83
//! ```
84
//!
85
//! ## Asset Labels
86
//!
87
//! The glTF loader let's you specify labels that let you target specific parts of the glTF.
88
//!
89
//! Be careful when using this feature, if you misspell a label it will simply ignore it without warning.
90
//!
91
//! You can use [`GltfAssetLabel`] to ensure you are using the correct label.
92
//!
93
//! # Supported KHR Extensions
94
//!
95
//! glTF files may use functionality beyond the base glTF specification, specified as a list of
96
//! required extensions. The table below shows which of the ratified Khronos extensions are
97
//! supported by Bevy.
98
//!
99
//! | Extension | Supported | Requires feature |
100
//! | --------------------------------- | --------- | ----------------------------------- |
101
//! | `KHR_animation_pointer` | ❌ | |
102
//! | `KHR_draco_mesh_compression` | ❌ | |
103
//! | `KHR_lights_punctual` | ✅ | |
104
//! | `KHR_materials_anisotropy` | ✅ | `pbr_anisotropy_texture` |
105
//! | `KHR_materials_clearcoat` | ✅ | `pbr_multi_layer_material_textures` |
106
//! | `KHR_materials_dispersion` | ❌ | |
107
//! | `KHR_materials_emissive_strength` | ✅ | |
108
//! | `KHR_materials_ior` | ✅ | |
109
//! | `KHR_materials_iridescence` | ❌ | |
110
//! | `KHR_materials_sheen` | ❌ | |
111
//! | `KHR_materials_specular` | ✅ | `pbr_specular_textures` |
112
//! | `KHR_materials_transmission` | ✅ | `pbr_transmission_textures` |
113
//! | `KHR_materials_unlit` | ✅ | |
114
//! | `KHR_materials_variants` | ❌ | |
115
//! | `KHR_materials_volume` | ✅ | |
116
//! | `KHR_mesh_quantization` | ❌ | |
117
//! | `KHR_texture_basisu` | ❌\* | |
118
//! | `KHR_texture_transform` | ✅\** | |
119
//! | `KHR_xmp_json_ld` | ❌ | |
120
//! | `EXT_mesh_gpu_instancing` | ❌ | |
121
//! | `EXT_meshopt_compression` | ❌ | |
122
//! | `EXT_texture_webp` | ❌\* | |
123
//!
124
//! \*Bevy supports ktx2 and webp formats but doesn't support the extension's syntax, see [#19104](https://github.com/bevyengine/bevy/issues/19104).
125
//!
126
//! \**`KHR_texture_transform` is only supported on `base_color_texture`, see [#15310](https://github.com/bevyengine/bevy/issues/15310).
127
//!
128
//! See the [glTF Extension Registry](https://github.com/KhronosGroup/glTF/blob/main/extensions/README.md) for more information on extensions.
129
130
mod assets;
131
pub mod convert_coordinates;
132
mod label;
133
mod loader;
134
mod material;
135
mod vertex_attributes;
136
137
extern crate alloc;
138
139
use alloc::sync::Arc;
140
use serde::{Deserialize, Serialize};
141
use std::sync::Mutex;
142
use tracing::warn;
143
144
use bevy_platform::collections::HashMap;
145
146
use bevy_app::prelude::*;
147
use bevy_asset::AssetApp;
148
use bevy_ecs::prelude::Resource;
149
use bevy_image::{CompressedImageFormatSupport, CompressedImageFormats, ImageSamplerDescriptor};
150
use bevy_mesh::MeshVertexAttribute;
151
152
/// The glTF prelude.
153
///
154
/// This includes the most common types in this crate, re-exported for your convenience.
155
pub mod prelude {
156
#[doc(hidden)]
157
pub use crate::{assets::Gltf, assets::GltfExtras, label::GltfAssetLabel};
158
}
159
160
use crate::{convert_coordinates::GltfConvertCoordinates, extensions::GltfExtensionHandlers};
161
162
pub use {assets::*, label::GltfAssetLabel, loader::*, material::GltfMaterial};
163
164
/// Re-exports for GLTF
165
pub mod gltf {
166
#[doc(hidden)]
167
pub use gltf::{Animation, Gltf, Material, Mesh, Primitive, Scene, Texture};
168
}
169
170
// Has to store an Arc<Mutex<...>> as there is no other way to mutate fields of asset loaders.
171
/// Stores default [`ImageSamplerDescriptor`] in main world.
172
#[derive(Resource)]
173
pub struct DefaultGltfImageSampler(Arc<Mutex<ImageSamplerDescriptor>>);
174
175
impl DefaultGltfImageSampler {
176
/// Creates a new [`DefaultGltfImageSampler`].
177
pub fn new(descriptor: &ImageSamplerDescriptor) -> Self {
178
Self(Arc::new(Mutex::new(descriptor.clone())))
179
}
180
181
/// Returns the current default [`ImageSamplerDescriptor`].
182
pub fn get(&self) -> ImageSamplerDescriptor {
183
self.0.lock().unwrap().clone()
184
}
185
186
/// Makes a clone of internal [`Arc`] pointer.
187
///
188
/// Intended only to be used by code with no access to ECS.
189
pub fn get_internal(&self) -> Arc<Mutex<ImageSamplerDescriptor>> {
190
self.0.clone()
191
}
192
193
/// Replaces default [`ImageSamplerDescriptor`].
194
///
195
/// Doesn't apply to samplers already built on top of it, i.e. `GltfLoader`'s output.
196
/// Assets need to manually be reloaded.
197
pub fn set(&self, descriptor: &ImageSamplerDescriptor) {
198
*self.0.lock().unwrap() = descriptor.clone();
199
}
200
}
201
202
/// Controls the bounds related components that are assigned to skinned mesh
203
/// entities. These components are used by systems like frustum culling.
204
#[derive(Default, Copy, Clone, PartialEq, Serialize, Deserialize)]
205
pub enum GltfSkinnedMeshBoundsPolicy {
206
/// Skinned meshes are assigned an `Aabb` component calculated from the bind
207
/// pose `Mesh`.
208
BindPose,
209
/// Skinned meshes are created with [`SkinnedMeshBounds`](bevy_mesh::skinning::SkinnedMeshBounds)
210
/// and assigned a [`DynamicSkinnedMeshBounds`](bevy_camera::visibility::DynamicSkinnedMeshBounds)
211
/// component. See `DynamicSkinnedMeshBounds` for details.
212
#[default]
213
Dynamic,
214
/// Same as `BindPose`, but also assign a `NoFrustumCulling` component. That
215
/// component tells the `bevy_camera` plugin to avoid frustum culling the
216
/// skinned mesh.
217
NoFrustumCulling,
218
}
219
220
/// Adds support for glTF file loading to the app.
221
pub struct GltfPlugin {
222
/// The default image sampler to lay glTF sampler data on top of.
223
///
224
/// Can be modified with the [`DefaultGltfImageSampler`] resource.
225
pub default_sampler: ImageSamplerDescriptor,
226
227
/// The default glTF coordinate conversion setting. This can be overridden
228
/// per-load by [`GltfLoaderSettings::convert_coordinates`].
229
pub convert_coordinates: GltfConvertCoordinates,
230
231
/// Registry for custom vertex attributes.
232
///
233
/// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
234
pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
235
236
/// The default policy for skinned mesh bounds. Can be overridden by
237
/// [`GltfLoaderSettings::skinned_mesh_bounds_policy`].
238
pub skinned_mesh_bounds_policy: GltfSkinnedMeshBoundsPolicy,
239
}
240
241
impl Default for GltfPlugin {
242
fn default() -> Self {
243
GltfPlugin {
244
default_sampler: ImageSamplerDescriptor::linear(),
245
custom_vertex_attributes: HashMap::default(),
246
convert_coordinates: GltfConvertCoordinates::default(),
247
skinned_mesh_bounds_policy: Default::default(),
248
}
249
}
250
}
251
252
impl GltfPlugin {
253
/// Register a custom vertex attribute so that it is recognized when loading a glTF file with the [`GltfLoader`].
254
///
255
/// `name` must be the attribute name as found in the glTF data, which must start with an underscore.
256
/// See [this section of the glTF specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
257
/// for additional details on custom attributes.
258
pub fn add_custom_vertex_attribute(
259
mut self,
260
name: &str,
261
attribute: MeshVertexAttribute,
262
) -> Self {
263
self.custom_vertex_attributes.insert(name.into(), attribute);
264
self
265
}
266
}
267
268
impl Plugin for GltfPlugin {
269
fn build(&self, app: &mut App) {
270
app.init_asset::<Gltf>()
271
.init_asset::<GltfNode>()
272
.init_asset::<GltfPrimitive>()
273
.init_asset::<GltfMesh>()
274
.init_asset::<GltfSkin>()
275
.init_asset::<GltfMaterial>()
276
.preregister_asset_loader::<GltfLoader>(&["gltf", "glb"])
277
.init_resource::<GltfExtensionHandlers>();
278
}
279
280
fn finish(&self, app: &mut App) {
281
let supported_compressed_formats = if let Some(resource) =
282
app.world().get_resource::<CompressedImageFormatSupport>()
283
{
284
resource.0
285
} else {
286
warn!("CompressedImageFormatSupport resource not found. It should either be initialized in finish() of \
287
RenderPlugin, or manually if not using the RenderPlugin or the WGPU backend.");
288
CompressedImageFormats::NONE
289
};
290
291
let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
292
let default_sampler = default_sampler_resource.get_internal();
293
app.insert_resource(default_sampler_resource);
294
295
let extensions = app.world().resource::<GltfExtensionHandlers>();
296
297
app.register_asset_loader(GltfLoader {
298
supported_compressed_formats,
299
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
300
default_sampler,
301
default_convert_coordinates: self.convert_coordinates,
302
extensions: extensions.0.clone(),
303
default_skinned_mesh_bounds_policy: self.skinned_mesh_bounds_policy,
304
});
305
}
306
}
307
308