You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

新增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)的变换被应用到所有实例化立方体上,导致整体偏移。

解决方法

方案一:限定查询范围(快速修复)

给实例化立方体添加专属标签,让自定义渲染逻辑只处理目标实体:

  1. 定义标签组件:
#[derive(Component)]
struct InstancedCubeMarker;
  1. 在setup中给实例化立方体添加标签:
commands.spawn((
    meshes.add(Cuboid::new(0.5, 0.5, 0.5)),
    SpatialBundle::INHERITED_IDENTITY,
    InstanceMaterialData(/* ... */),
    NoFrustumCulling,
    InstancedCubeMarker, // 添加标签
));
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 17:05:53