docs/bevy/examples/gltf/edit_material_on_gltf.rs 3.9 K · 103 lines · raw · history

1 //! Showcases how to change the material of a `Scene` spawned from a Gltf
2
3 use bevy::{
4 audio::AudioPlugin, color::palettes, gltf::GltfMaterialName, prelude::*,
5 world_serialization::WorldInstanceReady,
6 };
7
8 fn main() {
9 App::new()
10 .add_plugins(DefaultPlugins.build().disable::<AudioPlugin>())
11 .add_systems(Startup, setup_scene)
12 .add_observer(change_material)
13 .run();
14 }
15
16 /// This is added to a [`WorldAssetRoot`] and will cause the [`StandardMaterial::base_color`]
17 /// of materials with [`GltfMaterialName`] equal to `LeatherPartsMat`.
18 #[derive(Component)]
19 struct ColorOverride(Color);
20
21 fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
22 commands.spawn((
23 Camera3d::default(),
24 Transform::from_xyz(0., 1., 2.5).looking_at(Vec3::new(0., 0.25, 0.), Dir3::Y),
25 ));
26
27 commands.spawn((
28 DirectionalLight::default(),
29 Transform::from_xyz(0., 1., 0.25).looking_at(Vec3::ZERO, Dir3::Y),
30 ));
31
32 // FlightHelmet handle
33 let flight_helmet = asset_server
34 .load(GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"));
35 // This model will keep its original materials
36 commands.spawn(WorldAssetRoot(flight_helmet.clone()));
37 // This model will be tinted red
38 commands.spawn((
39 WorldAssetRoot(flight_helmet.clone()),
40 Transform::from_xyz(-1.25, 0., 0.),
41 ColorOverride(palettes::tailwind::RED_300.into()),
42 ));
43 // This model will be tinted green
44 commands.spawn((
45 WorldAssetRoot(flight_helmet),
46 Transform::from_xyz(1.25, 0., 0.),
47 ColorOverride(palettes::tailwind::GREEN_300.into()),
48 ));
49 }
50
51 /// On [`WorldInstanceReady`], iterates over all descendants of the scene
52 /// and modifies the tint of the material for the materials named `LeatherPartsMat`.
53 ///
54 /// If the [`WorldAssetRoot`] does not have a [`ColorOverride`], it is skipped.
55 fn change_material(
56 scene_ready: On<WorldInstanceReady>,
57 mut commands: Commands,
58 children: Query<&Children>,
59 color_override: Query<&ColorOverride>,
60 mesh_materials: Query<(&MeshMaterial3d<StandardMaterial>, &GltfMaterialName)>,
61 mut asset_materials: ResMut<Assets<StandardMaterial>>,
62 ) {
63 info!("processing Scene Entity: {}", scene_ready.entity);
64
65 // Get the `ColorOverride` of the entity, if it does not have a color override, return
66 let Ok(color_override) = color_override.get(scene_ready.entity) else {
67 info!("{} does not have a color override", scene_ready.entity);
68 return;
69 };
70
71 // Iterate over all children recursively
72 for descendant in children.iter_descendants(scene_ready.entity) {
73 // Get the material id and name which were created from the glTF file information
74 let Ok((id, material_name)) = mesh_materials.get(descendant) else {
75 continue;
76 };
77 // Get the material of the descendant
78 let Some(material) = asset_materials.get(id.id()) else {
79 continue;
80 };
81
82 // match on the material name, modifying the materials as necessary
83 match material_name.0.as_str() {
84 "LeatherPartsMat" => {
85 info!("editing LeatherPartsMat to use ColorOverride tint");
86 // Create a copy of the material and override base color
87 // If you intend on creating multiple models with the same tint, it
88 // is best to cache the handle somewhere, as having multiple materials
89 // that are identical is expensive
90 let mut new_material = material.clone();
91 new_material.base_color = color_override.0;
92
93 // Override `MeshMaterial3d` with new material
94 commands
95 .entity(descendant)
96 .insert(MeshMaterial3d(asset_materials.add(new_material)));
97 }
98 name => {
99 info!("not replacing: {name}");
100 }
101 }
102 }
103 }