新增PBR实体导致Bevy实例位置继承异常的问题排查
问题:Bevy自定义实例化Shader中添加PBR实体导致实例偏移
我在运行Bevy官方的WebGPU Shader实例化示例时,仅在startup阶段生成Camera3dBundle后添加了以下代码:
commands.spawn(PbrBundle { mesh: meshes.add(Sphere::new(0.2727).mesh().uv(10, 10)), material: materials.add(Color::linear_rgb(1.0, 1.0, 1.0)), transform: Transform::from_translation( Vec3::new(-5.0, 0.0, 0.0), ), ..default() });
执行后发现,原本位于(0,0)中心的实例化彩虹立方体整体偏移到了(-5,0)位置,且调整新增代码的放置顺序也无法解决这个问题。
原因分析
问题出在自定义实例化渲染的逻辑和硬编码的实例索引上:
queue_custom系统中,查询Query<Entity, With<InstanceMaterialData>>会获取所有带InstanceMaterialData的实体,但Bevy的RenderMeshInstances会缓存所有Mesh实体的渲染数据,包括新增的Sphere实体。- 渲染时,Shader中调用
get_world_from_local(0u),这里硬编码传入0u作为实例索引,会获取渲染队列中第一个Mesh实体的全局变换矩阵。新增Sphere实体后,它成为队列中的第一个Mesh实体,其(-5,0,0)的变换被应用到所有实例化立方体上,导致整体偏移。
解决方法
方案一:限定查询范围(快速修复)
给实例化立方体添加专属标签,让自定义渲染逻辑只处理目标实体:
- 定义标签组件:
#[derive(Component)] struct InstancedCubeMarker;
- 在
setup中给实例化立方体添加标签:
commands.spawn(( meshes.add(Cuboid::new(0.5, 0.5, 0.5)), SpatialBundle::INHERITED_IDENTITY, InstanceMaterialData(/* ... */), NoFrustumCulling, InstancedCubeMarker, // 添加标签 ));
- 修改
queue_custom中的查询:
material_meshes: Query<Entity, (With<InstanceMaterialData>, With<InstancedCubeMarker>)>,
这样queue_custom只会处理带有InstancedCubeMarker的实体,新增的Sphere不会被纳入,get_world_from_local(0u)会正确获取实例化立方体的变换。
方案二:修复硬编码索引问题(规范方案)
通过Push Constant传递实体自身的全局变换,彻底摆脱对渲染队列顺序的依赖:
1. 修改DrawMeshInstanced渲染命令,添加Push Constant传递
struct DrawMeshInstanced; impl<P: PhaseItem> RenderCommand<P> for DrawMeshInstanced { type Param = (SRes<RenderAssets<GpuMesh>>, SRes<RenderMeshInstances>); type ViewQuery = (); type ItemQuery = (Read<InstanceBuffer>, Read<ExtractedTransform>); #[inline] fn render<'w>( item: &P, _view: (), (instance_buffer, transform): Option<(&'w InstanceBuffer, &'w ExtractedTransform)>, (meshes, render_mesh_instances): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.entity()) else { return RenderCommandResult::Failure; }; let Some(gpu_mesh) = meshes.into_inner().get(mesh_instance.mesh_asset_id) else { return RenderCommandResult::Failure; }; let Some((instance_buffer, transform)) = (instance_buffer, transform) else { return RenderCommandResult::Failure; }; // 传递实体全局变换作为Push Constant pass.set_push_constants( ShaderStages::VERTEX, 0, bytemuck::cast_slice(&transform.compute_matrix()), ); pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..)); match &gpu_mesh.buffer_info { GpuBufferInfo::Indexed { buffer, index_format, count } => { pass.set_index_buffer(buffer.slice(..), 0, *index_format); pass.draw_indexed(0..*count, 0, 0..instance_buffer.length as u32); } GpuBufferInfo::NonIndexed => { pass.draw(0..gpu_mesh.vertex_count, 0..instance_buffer.length as u32); } } RenderCommandResult::Success } }
2. 修改CustomPipeline,启用Push Constant支持
impl SpecializedMeshPipeline for CustomPipeline { type Key = MeshPipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayoutRef, ) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> { let mut descriptor = self.mesh_pipeline.specialize(key, layout)?; descriptor.vertex.shader = self.shader.clone(); descriptor.vertex.buffers.push(VertexBufferLayout { array_stride: std::mem::size_of::<InstanceData>() as u64, step_mode: VertexStepMode::Instance, attributes: vec![ VertexAttribute { format: VertexFormat::Float32x4, offset: 0, shader_location: 3, }, VertexAttribute { format: VertexFormat::Float32x4, offset: VertexFormat::Float32x4.size(), shader_location: 4, }, ], }); // 添加Push Constant配置 descriptor.push_constants = Some(PushConstantRange { stages: ShaderStages::VERTEX, range: 0..64, // 4x4矩阵占64字节 }); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone(); Ok(descriptor) } }
3. 更新Shader,使用Push Constant中的变换矩阵
修改assets/shaders/instancing.wgsl:
#import bevy_pbr::mesh_functions::mesh_position_local_to_clip struct Vertex { @location(0) position: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) uv: vec2<f32>, @location(3) i_pos_scale: vec4<f32>, @location(4) i_color: vec4<f32>, }; struct VertexOutput { @builtin(position) clip_position: vec4<f32>, @location(0) color: vec4<f32>, }; @push_constant layout(push_constant) uniform PushConstants { mat4 world_from_local; } push_constants; @vertex fn vertex(vertex: Vertex) -> VertexOutput { let position = vertex.position * vertex.i_pos_scale.w + vertex.i_pos_scale.xyz; var out: VertexOutput; out.clip_position = mesh_position_local_to_clip( push_constants.world_from_local, vec4<f32>(position, 1.0) ); out.color = vertex.i_color; return out; } @fragment fn fragment(in: VertexOutput) -> @location(0) vec4<f32> { return in.color; }
内容的提问来源于stack exchange,提问作者Krupip
相关产品推荐
相关产品推荐

