feat(gltf): add name component to gltf mesh primitive (#13912)

# Objective

- fixes https://github.com/bevyengine/bevy/issues/13473

## Solution

- When a single mesh is assigned multiple materials, it is divided into
several primitive nodes, with each primitive assigned a unique material.
Presently, these primitives are named using the format Mesh.index, which
complicates querying. To improve this, we can assign a specific name to
each primitive based on the material’s name, since each primitive
corresponds to one material exclusively.

## Testing

- I have included a simple example which shows how to query a material
and mesh part based on the new name component.

## Changelog
- adds `GltfMaterialName` component to the mesh entity of the gltf
primitive node.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
Sou1gh0st 2024-10-01 00:51:52 +08:00 committed by GitHub
parent 5289e18e0b
commit 78a3aae81b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 121 additions and 1 deletions

View file

@ -910,6 +910,17 @@ description = "Loads and renders a glTF file as a scene, including the gltf extr
category = "3D Rendering"
wasm = true
[[example]]
name = "query_gltf_primitives"
path = "examples/3d/query_gltf_primitives.rs"
doc-scrape-examples = true
[package.metadata.example.query_gltf_primitives]
name = "Query glTF primitives"
description = "Query primitives in a glTF scene"
category = "3D Rendering"
wasm = true
[[example]]
name = "motion_blur"
path = "examples/3d/motion_blur.rs"

Binary file not shown.

View file

@ -153,6 +153,7 @@ impl Plugin for GltfPlugin {
.register_type::<GltfSceneExtras>()
.register_type::<GltfMeshExtras>()
.register_type::<GltfMaterialExtras>()
.register_type::<GltfMaterialName>()
.init_asset::<Gltf>()
.init_asset::<GltfNode>()
.init_asset::<GltfPrimitive>()
@ -460,6 +461,13 @@ pub struct GltfMaterialExtras {
pub value: String,
}
/// The material name of a glTF primitive.
///
/// See [the relevant glTF specification section](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-material).
#[derive(Clone, Debug, Reflect, Default, Component)]
#[reflect(Component)]
pub struct GltfMaterialName(pub String);
/// Labels that can be used to load part of a glTF
///
/// You can use [`GltfAssetLabel::from_asset`] to add it to an asset path

View file

@ -1,6 +1,6 @@
use crate::{
vertex_attributes::convert_attribute, Gltf, GltfAssetLabel, GltfExtras, GltfMaterialExtras,
GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin,
GltfMaterialName, GltfMeshExtras, GltfNode, GltfSceneExtras, GltfSkin,
};
use alloc::collections::VecDeque;
@ -1380,6 +1380,10 @@ fn load_node(
});
}
if let Some(name) = material.name() {
mesh_entity.insert(GltfMaterialName(String::from(name)));
}
mesh_entity.insert(Name::new(primitive_name(&mesh, &primitive)));
// Mark for adding skinned mesh
if let Some(skin) = gltf_node.skin() {

View file

@ -0,0 +1,96 @@
//! This example demonstrates how to query a [`StandardMaterial`] within a glTF scene.
//! It is particularly useful for glTF scenes with a mesh that consists of multiple primitives.
use std::f32::consts::PI;
use bevy::{
gltf::GltfMaterialName,
pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap},
prelude::*,
render::mesh::VertexAttributeValues,
};
fn main() {
App::new()
.insert_resource(DirectionalLightShadowMap { size: 4096 })
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, find_top_material_and_mesh)
.run();
}
fn find_top_material_and_mesh(
mut materials: ResMut<Assets<StandardMaterial>>,
mut meshes: ResMut<Assets<Mesh>>,
time: Res<Time>,
mat_query: Query<(&Handle<StandardMaterial>, &Handle<Mesh>, &GltfMaterialName)>,
) {
for (mat_handle, mesh_handle, name) in mat_query.iter() {
// locate a material by material name
if name.0 == "Top" {
if let Some(material) = materials.get_mut(mat_handle) {
if let Color::Hsla(ref mut hsla) = material.base_color {
*hsla = hsla.rotate_hue(time.delta_seconds() * 100.0);
} else {
material.base_color = Color::from(Hsla::hsl(0.0, 0.8, 0.5));
}
}
if let Some(mesh) = meshes.get_mut(mesh_handle) {
if let Some(VertexAttributeValues::Float32x3(positions)) =
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
{
positions[0] = (
ops::sin(2.0 * PI * time.elapsed_seconds()),
positions[0][1],
positions[0][2],
)
.into();
}
}
}
}
}
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn((
Camera3dBundle {
transform: Transform::from_xyz(0.6, 1.6, 11.3)
.looking_at(Vec3::new(0.0, 0.0, 3.0), Vec3::Y),
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 500.0,
rotation: Quat::IDENTITY,
},
));
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
shadows_enabled: true,
..default()
},
// This is a relatively small scene, so use tighter shadow
// cascade bounds than the default for better quality.
// We also adjusted the shadow map to be larger since we're
// only using a single cascade.
cascade_shadow_config: CascadeShadowConfigBuilder {
num_cascades: 1,
maximum_distance: 1.6,
..default()
}
.into(),
..default()
});
commands.spawn(SceneBundle {
scene: asset_server
.load(GltfAssetLabel::Scene(0).from_asset("models/GltfPrimitives/gltf_primitives.glb")),
transform: Transform {
rotation: Quat::from_rotation_y(-90.0 / 180.0 * PI),
..default()
},
..default()
});
}

View file

@ -159,6 +159,7 @@ Example | Description
[Parenting](../examples/3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
[Percentage-closer soft shadows](../examples/3d/pcss.rs) | Demonstrates percentage-closer soft shadows (PCSS)
[Physically Based Rendering](../examples/3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
[Query glTF primitives](../examples/3d/query_gltf_primitives.rs) | Query primitives in a glTF scene
[Reflection Probes](../examples/3d/reflection_probes.rs) | Demonstrates reflection probes
[Render to Texture](../examples/3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
[Rotate Environment Map](../examples/3d/rotate_environment_map.rs) | Demonstrates how to rotate the skybox and the environment map simultaneously