如何在Bevy中为GLTF场景的SceneBundle应用自定义材质
为Bevy中GLTF场景的实体替换自定义材质
问题描述
我使用Bevy框架开发3D游戏,通过asset_server.load()加载glTF文件并创建了SceneBundle,代码如下:
fn spawn_gltf( mut commands: Commands, ass: Res<AssetServer>, ) { let my_gltf = ass.load("my.glb#Scene0"); commands.spawn(SceneBundle { scene: my_gltf, transform: Transform::from_xyz(2.0, 0.0, -5.0), ..Default::default() }); }
现在希望为该SceneBundle中的实体应用带有自定义顶点/片元着色器的CustomMaterial,但官方示例仅展示了如何为MaterialMeshBundle应用自定义材质,请问如何实现?
解决方案
加载GLTF场景时,Bevy会自动为场景内的网格实体生成带PbrMaterial的组件。要替换为自定义材质,需遍历场景下的相关实体,替换材质组件。
步骤1:标记场景根实体
为加载的SceneBundle添加自定义标签组件,方便后续识别其下的子实体:
// 定义标签组件 #[derive(Component)] struct GltfSceneRoot; fn spawn_gltf( mut commands: Commands, ass: Res<AssetServer>, ) { let my_gltf = ass.load("my.glb#Scene0"); commands.spawn(( SceneBundle { scene: my_gltf, transform: Transform::from_xyz(2.0, 0.0, -5.0), ..Default::default() }, GltfSceneRoot, // 添加标签标记根实体 )); }
步骤2:实现材质替换系统
创建系统遍历场景根实体下的所有带PbrMaterial的网格实体,替换为CustomMaterial:
fn replace_gltf_materials( mut commands: Commands, root_query: Query<Entity, With<GltfSceneRoot>>, mesh_entity_query: Query<(Entity, &Handle<PbrMaterial>), Without<GltfSceneRoot>>, pbr_materials: Res<Assets<PbrMaterial>>, mut custom_materials: ResMut<Assets<CustomMaterial>>, hierarchy: Query<&Parent>, ) { // 获取场景根实体 let Some(root_entity) = root_query.iter().next() else { return; }; // 遍历所有带PbrMaterial的实体,检查是否属于目标场景 for (entity, pbr_mat_handle) in mesh_entity_query.iter() { // 递归验证实体是否为场景根的子实体 let mut current_entity = entity; let mut is_scene_child = false; while let Some(parent) = hierarchy.get(current_entity).ok() { if parent.get() == root_entity { is_scene_child = true; break; } current_entity = parent.get(); } if !is_scene_child { continue; } // 可选:从原有PbrMaterial迁移属性到自定义材质 let custom_mat = match pbr_materials.get(pbr_mat_handle) { Some(pbr_mat) => CustomMaterial { color: pbr_mat.base_color, color_texture: pbr_mat.base_color_texture.clone(), alpha_mode: pbr_mat.alpha_mode, }, None => CustomMaterial { color: Color::WHITE, color_texture: None, alpha_mode: AlphaMode::Opaque, }, }; // 替换材质组件:移除PbrMaterial,添加CustomMaterial let custom_mat_handle = custom_materials.add(custom_mat); commands.entity(entity) .remove::<Handle<PbrMaterial>>() .insert(custom_mat_handle); } }
步骤3:完善CustomMaterial实现
确保CustomMaterial正确实现Material trait,若需要自定义顶点着色器,需额外指定:
use bevy::{ pbr::{Material, MaterialPlugin, AlphaMode}, reflect::TypeUuid, render::render_resource::{AsBindGroup, ShaderRef}, prelude::*, }; #[derive(AsBindGroup, TypeUuid, Debug, Clone)] #[uuid = "f690fdae-d598-45ab-8225-97e2a3f056e0"] pub struct CustomMaterial { #[uniform(0)] color: Color, #[texture(1)] #[sampler(2)] color_texture: Option<Handle<Image>>, alpha_mode: AlphaMode, } impl Material for CustomMaterial { // 若使用自定义顶点着色器,替换为你的shader路径,否则用默认PBR顶点着色器 fn vertex_shader() -> ShaderRef { // "shaders/custom_vertex.wgsl".into() ShaderRef::Default } fn fragment_shader() -> ShaderRef { "shaders/custom_material.wgsl".into() } fn alpha_mode(&self) -> AlphaMode { self.alpha_mode } }
步骤4:注册系统和插件
将自定义材质插件和系统添加到App中,注意材质替换系统要在场景实例化后运行:
fn main() { App::new() .add_plugins(DefaultPlugins) .add_plugin(MaterialPlugin::<CustomMaterial>::default()) // 注册自定义材质插件 .add_startup_system(spawn_gltf) // 放在PostUpdate阶段,确保场景已完成实例化 .add_system(replace_gltf_materials.in_base_set(CoreSet::PostUpdate)) .run(); }
注意事项
- 场景加载是异步操作,替换材质的系统需在
PostUpdate阶段执行,确保场景下的实体已全部生成 - 该方法会替换场景中所有使用
PbrMaterial的实体材质,若需针对性替换,可添加额外标签组件筛选 - 自定义着色器的绑定组需与
CustomMaterial的AsBindGroup属性完全匹配,否则会出现渲染错误
内容的提问来源于stack exchange,提问作者Youdi
相关产品推荐
相关产品推荐

