Allow setting correct glTF coordinate conversions globally (#19685)
# Objective - Followup to https://github.com/bevyengine/bevy/pull/19633 - As discussed, it's a bit cumbersome to specify that you want the correct orientation every single time - Also, glTFs loaded from third parties will still be loaded incorrectly ## Solution - Allow opting into the new behavior globally or per-asset - Also improved some docs while on it :) ## Testing - Ran the animation examples - Ran the test scene from the last PR with all configuration combinations
This commit is contained in:
parent
546711b807
commit
f3d94f3958
@ -156,8 +156,24 @@ impl DefaultGltfImageSampler {
|
|||||||
pub struct GltfPlugin {
|
pub struct GltfPlugin {
|
||||||
/// The default image sampler to lay glTF sampler data on top of.
|
/// The default image sampler to lay glTF sampler data on top of.
|
||||||
///
|
///
|
||||||
/// Can be modified with [`DefaultGltfImageSampler`] resource.
|
/// Can be modified with the [`DefaultGltfImageSampler`] resource.
|
||||||
pub default_sampler: ImageSamplerDescriptor,
|
pub default_sampler: ImageSamplerDescriptor,
|
||||||
|
|
||||||
|
/// Whether to convert glTF coordinates to Bevy's coordinate system by default.
|
||||||
|
/// If set to `true`, the loader will convert the coordinate system of loaded glTF assets to Bevy's coordinate system
|
||||||
|
/// such that objects looking forward in glTF will also look forward in Bevy.
|
||||||
|
///
|
||||||
|
/// The exact coordinate system conversion is as follows:
|
||||||
|
/// - glTF:
|
||||||
|
/// - forward: Z
|
||||||
|
/// - up: Y
|
||||||
|
/// - right: -X
|
||||||
|
/// - Bevy:
|
||||||
|
/// - forward: -Z
|
||||||
|
/// - up: Y
|
||||||
|
/// - right: X
|
||||||
|
pub convert_coordinates: bool,
|
||||||
|
|
||||||
/// Registry for custom vertex attributes.
|
/// Registry for custom vertex attributes.
|
||||||
///
|
///
|
||||||
/// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
|
/// To specify, use [`GltfPlugin::add_custom_vertex_attribute`].
|
||||||
@ -169,6 +185,7 @@ impl Default for GltfPlugin {
|
|||||||
GltfPlugin {
|
GltfPlugin {
|
||||||
default_sampler: ImageSamplerDescriptor::linear(),
|
default_sampler: ImageSamplerDescriptor::linear(),
|
||||||
custom_vertex_attributes: HashMap::default(),
|
custom_vertex_attributes: HashMap::default(),
|
||||||
|
convert_coordinates: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -219,10 +236,12 @@ impl Plugin for GltfPlugin {
|
|||||||
let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
|
let default_sampler_resource = DefaultGltfImageSampler::new(&self.default_sampler);
|
||||||
let default_sampler = default_sampler_resource.get_internal();
|
let default_sampler = default_sampler_resource.get_internal();
|
||||||
app.insert_resource(default_sampler_resource);
|
app.insert_resource(default_sampler_resource);
|
||||||
|
|
||||||
app.register_asset_loader(GltfLoader {
|
app.register_asset_loader(GltfLoader {
|
||||||
supported_compressed_formats,
|
supported_compressed_formats,
|
||||||
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
|
custom_vertex_attributes: self.custom_vertex_attributes.clone(),
|
||||||
default_sampler,
|
default_sampler,
|
||||||
|
default_convert_coordinates: self.convert_coordinates,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,20 @@ pub struct GltfLoader {
|
|||||||
pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
|
pub custom_vertex_attributes: HashMap<Box<str>, MeshVertexAttribute>,
|
||||||
/// Arc to default [`ImageSamplerDescriptor`].
|
/// Arc to default [`ImageSamplerDescriptor`].
|
||||||
pub default_sampler: Arc<Mutex<ImageSamplerDescriptor>>,
|
pub default_sampler: Arc<Mutex<ImageSamplerDescriptor>>,
|
||||||
|
/// Whether to convert glTF coordinates to Bevy's coordinate system by default.
|
||||||
|
/// If set to `true`, the loader will convert the coordinate system of loaded glTF assets to Bevy's coordinate system
|
||||||
|
/// such that objects looking forward in glTF will also look forward in Bevy.
|
||||||
|
///
|
||||||
|
/// The exact coordinate system conversion is as follows:
|
||||||
|
/// - glTF:
|
||||||
|
/// - forward: Z
|
||||||
|
/// - up: Y
|
||||||
|
/// - right: -X
|
||||||
|
/// - Bevy:
|
||||||
|
/// - forward: -Z
|
||||||
|
/// - up: Y
|
||||||
|
/// - right: X
|
||||||
|
pub default_convert_coordinates: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of
|
/// Specifies optional settings for processing gltfs at load time. By default, all recognized contents of
|
||||||
@ -188,11 +202,16 @@ pub struct GltfLoaderSettings {
|
|||||||
pub include_source: bool,
|
pub include_source: bool,
|
||||||
/// Overrides the default sampler. Data from sampler node is added on top of that.
|
/// Overrides the default sampler. Data from sampler node is added on top of that.
|
||||||
///
|
///
|
||||||
/// If None, uses global default which is stored in `DefaultGltfImageSampler` resource.
|
/// If None, uses the global default which is stored in the [`DefaultGltfImageSampler`](crate::DefaultGltfImageSampler) resource.
|
||||||
pub default_sampler: Option<ImageSamplerDescriptor>,
|
pub default_sampler: Option<ImageSamplerDescriptor>,
|
||||||
/// If true, the loader will ignore sampler data from gltf and use the default sampler.
|
/// If true, the loader will ignore sampler data from gltf and use the default sampler.
|
||||||
pub override_sampler: bool,
|
pub override_sampler: bool,
|
||||||
/// If true, the loader will convert glTF coordinates to Bevy's coordinate system.
|
/// Overrides the default glTF coordinate conversion setting.
|
||||||
|
///
|
||||||
|
/// If set to `Some(true)`, the loader will convert the coordinate system of loaded glTF assets to Bevy's coordinate system
|
||||||
|
/// such that objects looking forward in glTF will also look forward in Bevy.
|
||||||
|
///
|
||||||
|
/// The exact coordinate system conversion is as follows:
|
||||||
/// - glTF:
|
/// - glTF:
|
||||||
/// - forward: Z
|
/// - forward: Z
|
||||||
/// - up: Y
|
/// - up: Y
|
||||||
@ -201,7 +220,9 @@ pub struct GltfLoaderSettings {
|
|||||||
/// - forward: -Z
|
/// - forward: -Z
|
||||||
/// - up: Y
|
/// - up: Y
|
||||||
/// - right: X
|
/// - right: X
|
||||||
pub convert_coordinates: bool,
|
///
|
||||||
|
/// If `None`, uses the global default set by [`GltfPlugin::convert_coordinates`](crate::GltfPlugin::convert_coordinates).
|
||||||
|
pub convert_coordinates: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GltfLoaderSettings {
|
impl Default for GltfLoaderSettings {
|
||||||
@ -214,7 +235,7 @@ impl Default for GltfLoaderSettings {
|
|||||||
include_source: false,
|
include_source: false,
|
||||||
default_sampler: None,
|
default_sampler: None,
|
||||||
override_sampler: false,
|
override_sampler: false,
|
||||||
convert_coordinates: false,
|
convert_coordinates: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,6 +295,11 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
paths
|
paths
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let convert_coordinates = match settings.convert_coordinates {
|
||||||
|
Some(convert_coordinates) => convert_coordinates,
|
||||||
|
None => loader.default_convert_coordinates,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(feature = "bevy_animation")]
|
#[cfg(feature = "bevy_animation")]
|
||||||
let (animations, named_animations, animation_roots) = {
|
let (animations, named_animations, animation_roots) = {
|
||||||
use bevy_animation::{animated_field, animation_curves::*, gltf_curves::*, VariableCurve};
|
use bevy_animation::{animated_field, animation_curves::*, gltf_curves::*, VariableCurve};
|
||||||
@ -318,7 +344,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
let translations: Vec<Vec3> = tr
|
let translations: Vec<Vec3> = tr
|
||||||
.map(Vec3::from)
|
.map(Vec3::from)
|
||||||
.map(|verts| {
|
.map(|verts| {
|
||||||
if settings.convert_coordinates {
|
if convert_coordinates {
|
||||||
Vec3::convert_coordinates(verts)
|
Vec3::convert_coordinates(verts)
|
||||||
} else {
|
} else {
|
||||||
verts
|
verts
|
||||||
@ -375,7 +401,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
.into_f32()
|
.into_f32()
|
||||||
.map(Quat::from_array)
|
.map(Quat::from_array)
|
||||||
.map(|quat| {
|
.map(|quat| {
|
||||||
if settings.convert_coordinates {
|
if convert_coordinates {
|
||||||
Quat::convert_coordinates(quat)
|
Quat::convert_coordinates(quat)
|
||||||
} else {
|
} else {
|
||||||
quat
|
quat
|
||||||
@ -663,7 +689,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
accessor,
|
accessor,
|
||||||
&buffer_data,
|
&buffer_data,
|
||||||
&loader.custom_vertex_attributes,
|
&loader.custom_vertex_attributes,
|
||||||
settings.convert_coordinates,
|
convert_coordinates,
|
||||||
) {
|
) {
|
||||||
Ok((attribute, values)) => mesh.insert_attribute(attribute, values),
|
Ok((attribute, values)) => mesh.insert_attribute(attribute, values),
|
||||||
Err(err) => warn!("{}", err),
|
Err(err) => warn!("{}", err),
|
||||||
@ -786,7 +812,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
.map(|mats| {
|
.map(|mats| {
|
||||||
mats.map(|mat| Mat4::from_cols_array_2d(&mat))
|
mats.map(|mat| Mat4::from_cols_array_2d(&mat))
|
||||||
.map(|mat| {
|
.map(|mat| {
|
||||||
if settings.convert_coordinates {
|
if convert_coordinates {
|
||||||
mat.convert_coordinates()
|
mat.convert_coordinates()
|
||||||
} else {
|
} else {
|
||||||
mat
|
mat
|
||||||
@ -875,7 +901,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
&node,
|
&node,
|
||||||
children,
|
children,
|
||||||
mesh,
|
mesh,
|
||||||
node_transform(&node, settings.convert_coordinates),
|
node_transform(&node, convert_coordinates),
|
||||||
skin,
|
skin,
|
||||||
node.extras().as_deref().map(GltfExtras::from),
|
node.extras().as_deref().map(GltfExtras::from),
|
||||||
);
|
);
|
||||||
@ -926,6 +952,7 @@ async fn load_gltf<'a, 'b, 'c>(
|
|||||||
#[cfg(feature = "bevy_animation")]
|
#[cfg(feature = "bevy_animation")]
|
||||||
None,
|
None,
|
||||||
&gltf.document,
|
&gltf.document,
|
||||||
|
convert_coordinates,
|
||||||
);
|
);
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
err = Some(result);
|
err = Some(result);
|
||||||
@ -1345,9 +1372,10 @@ fn load_node(
|
|||||||
#[cfg(feature = "bevy_animation")] animation_roots: &HashSet<usize>,
|
#[cfg(feature = "bevy_animation")] animation_roots: &HashSet<usize>,
|
||||||
#[cfg(feature = "bevy_animation")] mut animation_context: Option<AnimationContext>,
|
#[cfg(feature = "bevy_animation")] mut animation_context: Option<AnimationContext>,
|
||||||
document: &Document,
|
document: &Document,
|
||||||
|
convert_coordinates: bool,
|
||||||
) -> Result<(), GltfError> {
|
) -> Result<(), GltfError> {
|
||||||
let mut gltf_error = None;
|
let mut gltf_error = None;
|
||||||
let transform = node_transform(gltf_node, settings.convert_coordinates);
|
let transform = node_transform(gltf_node, convert_coordinates);
|
||||||
let world_transform = *parent_transform * transform;
|
let world_transform = *parent_transform * transform;
|
||||||
// according to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#instantiation,
|
// according to https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#instantiation,
|
||||||
// if the determinant of the transform is negative we must invert the winding order of
|
// if the determinant of the transform is negative we must invert the winding order of
|
||||||
@ -1616,6 +1644,7 @@ fn load_node(
|
|||||||
#[cfg(feature = "bevy_animation")]
|
#[cfg(feature = "bevy_animation")]
|
||||||
animation_context.clone(),
|
animation_context.clone(),
|
||||||
document,
|
document,
|
||||||
|
convert_coordinates,
|
||||||
) {
|
) {
|
||||||
gltf_error = Some(err);
|
gltf_error = Some(err);
|
||||||
return;
|
return;
|
||||||
|
@ -141,12 +141,21 @@ impl<'a> VertexAttributeIter<'a> {
|
|||||||
VertexAttributeIter::F32x2(it) => Ok(Values::Float32x2(it.collect())),
|
VertexAttributeIter::F32x2(it) => Ok(Values::Float32x2(it.collect())),
|
||||||
VertexAttributeIter::U32x2(it) => Ok(Values::Uint32x2(it.collect())),
|
VertexAttributeIter::U32x2(it) => Ok(Values::Uint32x2(it.collect())),
|
||||||
VertexAttributeIter::F32x3(it) => Ok(if convert_coordinates {
|
VertexAttributeIter::F32x3(it) => Ok(if convert_coordinates {
|
||||||
|
// The following f32x3 values need to be converted to the correct coordinate system
|
||||||
|
// - Positions
|
||||||
|
// - Normals
|
||||||
|
//
|
||||||
|
// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview>
|
||||||
Values::Float32x3(it.map(ConvertCoordinates::convert_coordinates).collect())
|
Values::Float32x3(it.map(ConvertCoordinates::convert_coordinates).collect())
|
||||||
} else {
|
} else {
|
||||||
Values::Float32x3(it.collect())
|
Values::Float32x3(it.collect())
|
||||||
}),
|
}),
|
||||||
VertexAttributeIter::U32x3(it) => Ok(Values::Uint32x3(it.collect())),
|
VertexAttributeIter::U32x3(it) => Ok(Values::Uint32x3(it.collect())),
|
||||||
VertexAttributeIter::F32x4(it) => Ok(if convert_coordinates {
|
VertexAttributeIter::F32x4(it) => Ok(if convert_coordinates {
|
||||||
|
// The following f32x4 values need to be converted to the correct coordinate system
|
||||||
|
// - Tangents
|
||||||
|
//
|
||||||
|
// See <https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview>
|
||||||
Values::Float32x4(it.map(ConvertCoordinates::convert_coordinates).collect())
|
Values::Float32x4(it.map(ConvertCoordinates::convert_coordinates).collect())
|
||||||
} else {
|
} else {
|
||||||
Values::Float32x4(it.collect())
|
Values::Float32x4(it.collect())
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
title: Allow importing glTFs with a corrected coordinate system
|
title: Allow importing glTFs with a corrected coordinate system
|
||||||
authors: ["@janhohenheim"]
|
authors: ["@janhohenheim"]
|
||||||
pull_requests: [19633]
|
pull_requests: [19633, 19685]
|
||||||
---
|
---
|
||||||
|
|
||||||
glTF uses the following coordinate system:
|
glTF uses the following coordinate system:
|
||||||
@ -24,17 +24,34 @@ Long-term, we'd like to fix our glTF imports to use the correct coordinate syste
|
|||||||
But changing the import behavior would mean that *all* imported glTFs of *all* users would suddenly look different, breaking their scenes!
|
But changing the import behavior would mean that *all* imported glTFs of *all* users would suddenly look different, breaking their scenes!
|
||||||
Not to mention that any bugs in the conversion code would be incredibly frustating for users.
|
Not to mention that any bugs in the conversion code would be incredibly frustating for users.
|
||||||
|
|
||||||
This is why we are now gradually rolling out support for corrected glTF imports. Starting now you can opt into the new behavior by setting the `GltfLoaderSettings`:
|
This is why we are now gradually rolling out support for corrected glTF imports. Starting now you can opt into the new behavior by setting `convert_coordinates` on `GltfPlugin`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// old behavior, ignores glTF's coordinate system
|
// old behavior, ignores glTF's coordinate system
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
// new behavior, converts the coordinate system of all glTF assets into Bevy's coordinate system
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins.set(GltfPlugin {
|
||||||
|
convert_coordinates: true,
|
||||||
|
..default()
|
||||||
|
}))
|
||||||
|
.run();
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also control this on a per-asset-level:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Use the global default
|
||||||
let handle = asset_server.load("fox.gltf#Scene0");
|
let handle = asset_server.load("fox.gltf#Scene0");
|
||||||
|
|
||||||
// new behavior, converts glTF's coordinate system into Bevy's coordinate system
|
// Manually opt in or out of coordinate conversion for an individual asset
|
||||||
let handle = asset_server.load_with_settings(
|
let handle = asset_server.load_with_settings(
|
||||||
"fox.gltf#Scene0",
|
"fox.gltf#Scene0",
|
||||||
|settings: &mut GltfLoaderSettings| {
|
|settings: &mut GltfLoaderSettings| {
|
||||||
settings.convert_coordinates = true;
|
settings.convert_coordinates = Some(true);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
Loading…
Reference in New Issue
Block a user