Path: blob/main/examples/shader_advanced/custom_shader_instancing.rs
6849 views
//! A shader that renders a mesh multiple times in one draw call.1//!2//! Bevy will automatically batch and instance your meshes assuming you use the same3//! `Handle<Material>` and `Handle<Mesh>` for all of your instances.4//!5//! This example is intended for advanced users and shows how to make a custom instancing6//! implementation using bevy's low level rendering api.7//! It's generally recommended to try the built-in instancing before going with this approach.89use bevy::pbr::SetMeshViewBindingArrayBindGroup;10use bevy::{11camera::visibility::NoFrustumCulling,12core_pipeline::core_3d::Transparent3d,13ecs::{14query::QueryItem,15system::{lifetimeless::*, SystemParamItem},16},17mesh::{MeshVertexBufferLayoutRef, VertexBufferLayout},18pbr::{19MeshPipeline, MeshPipelineKey, RenderMeshInstances, SetMeshBindGroup, SetMeshViewBindGroup,20},21prelude::*,22render::{23extract_component::{ExtractComponent, ExtractComponentPlugin},24mesh::{allocator::MeshAllocator, RenderMesh, RenderMeshBufferInfo},25render_asset::RenderAssets,26render_phase::{27AddRenderCommand, DrawFunctions, PhaseItem, PhaseItemExtraIndex, RenderCommand,28RenderCommandResult, SetItemPipeline, TrackedRenderPass, ViewSortedRenderPhases,29},30render_resource::*,31renderer::RenderDevice,32sync_world::MainEntity,33view::{ExtractedView, NoIndirectDrawing},34Render, RenderApp, RenderStartup, RenderSystems,35},36};37use bytemuck::{Pod, Zeroable};3839/// This example uses a shader source file from the assets subdirectory40const SHADER_ASSET_PATH: &str = "shaders/instancing.wgsl";4142fn main() {43App::new()44.add_plugins((DefaultPlugins, CustomMaterialPlugin))45.add_systems(Startup, setup)46.run();47}4849fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {50commands.spawn((51Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),52InstanceMaterialData(53(1..=10)54.flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0)))55.map(|(x, y)| InstanceData {56position: Vec3::new(x * 10.0 - 5.0, y * 10.0 - 5.0, 0.0),57scale: 1.0,58color: LinearRgba::from(Color::hsla(x * 360., y, 0.5, 1.0)).to_f32_array(),59})60.collect(),61),62// NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform.63// As the cube is at the origin, if its Aabb moves outside the view frustum, all the64// instanced cubes will be culled.65// The InstanceMaterialData contains the 'GlobalTransform' information for this custom66// instancing, and that is not taken into account with the built-in frustum culling.67// We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker68// component to avoid incorrect culling.69NoFrustumCulling,70));7172// camera73commands.spawn((74Camera3d::default(),75Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),76// We need this component because we use `draw_indexed` and `draw`77// instead of `draw_indirect_indexed` and `draw_indirect` in78// `DrawMeshInstanced::render`.79NoIndirectDrawing,80));81}8283#[derive(Component, Deref)]84struct InstanceMaterialData(Vec<InstanceData>);8586impl ExtractComponent for InstanceMaterialData {87type QueryData = &'static InstanceMaterialData;88type QueryFilter = ();89type Out = Self;9091fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {92Some(InstanceMaterialData(item.0.clone()))93}94}9596struct CustomMaterialPlugin;9798impl Plugin for CustomMaterialPlugin {99fn build(&self, app: &mut App) {100app.add_plugins(ExtractComponentPlugin::<InstanceMaterialData>::default());101app.sub_app_mut(RenderApp)102.add_render_command::<Transparent3d, DrawCustom>()103.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()104.add_systems(RenderStartup, init_custom_pipeline)105.add_systems(106Render,107(108queue_custom.in_set(RenderSystems::QueueMeshes),109prepare_instance_buffers.in_set(RenderSystems::PrepareResources),110),111);112}113}114115#[derive(Clone, Copy, Pod, Zeroable)]116#[repr(C)]117struct InstanceData {118position: Vec3,119scale: f32,120color: [f32; 4],121}122123fn queue_custom(124transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,125custom_pipeline: Res<CustomPipeline>,126mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,127pipeline_cache: Res<PipelineCache>,128meshes: Res<RenderAssets<RenderMesh>>,129render_mesh_instances: Res<RenderMeshInstances>,130material_meshes: Query<(Entity, &MainEntity), With<InstanceMaterialData>>,131mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,132views: Query<(&ExtractedView, &Msaa)>,133) {134let draw_custom = transparent_3d_draw_functions.read().id::<DrawCustom>();135136for (view, msaa) in &views {137let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)138else {139continue;140};141142let msaa_key = MeshPipelineKey::from_msaa_samples(msaa.samples());143144let view_key = msaa_key | MeshPipelineKey::from_hdr(view.hdr);145let rangefinder = view.rangefinder3d();146for (entity, main_entity) in &material_meshes {147let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity)148else {149continue;150};151let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else {152continue;153};154let key =155view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());156let pipeline = pipelines157.specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout)158.unwrap();159transparent_phase.add(Transparent3d {160entity: (entity, *main_entity),161pipeline,162draw_function: draw_custom,163distance: rangefinder.distance_translation(&mesh_instance.translation),164batch_range: 0..1,165extra_index: PhaseItemExtraIndex::None,166indexed: true,167});168}169}170}171172#[derive(Component)]173struct InstanceBuffer {174buffer: Buffer,175length: usize,176}177178fn prepare_instance_buffers(179mut commands: Commands,180query: Query<(Entity, &InstanceMaterialData)>,181render_device: Res<RenderDevice>,182) {183for (entity, instance_data) in &query {184let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {185label: Some("instance data buffer"),186contents: bytemuck::cast_slice(instance_data.as_slice()),187usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,188});189commands.entity(entity).insert(InstanceBuffer {190buffer,191length: instance_data.len(),192});193}194}195196#[derive(Resource)]197struct CustomPipeline {198shader: Handle<Shader>,199mesh_pipeline: MeshPipeline,200}201202fn init_custom_pipeline(203mut commands: Commands,204asset_server: Res<AssetServer>,205mesh_pipeline: Res<MeshPipeline>,206) {207commands.insert_resource(CustomPipeline {208shader: asset_server.load(SHADER_ASSET_PATH),209mesh_pipeline: mesh_pipeline.clone(),210});211}212213impl SpecializedMeshPipeline for CustomPipeline {214type Key = MeshPipelineKey;215216fn specialize(217&self,218key: Self::Key,219layout: &MeshVertexBufferLayoutRef,220) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {221let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;222223descriptor.vertex.shader = self.shader.clone();224descriptor.vertex.buffers.push(VertexBufferLayout {225array_stride: size_of::<InstanceData>() as u64,226step_mode: VertexStepMode::Instance,227attributes: vec![228VertexAttribute {229format: VertexFormat::Float32x4,230offset: 0,231shader_location: 3, // shader locations 0-2 are taken up by Position, Normal and UV attributes232},233VertexAttribute {234format: VertexFormat::Float32x4,235offset: VertexFormat::Float32x4.size(),236shader_location: 4,237},238],239});240descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();241Ok(descriptor)242}243}244245type DrawCustom = (246SetItemPipeline,247SetMeshViewBindGroup<0>,248SetMeshViewBindingArrayBindGroup<1>,249SetMeshBindGroup<2>,250DrawMeshInstanced,251);252253struct DrawMeshInstanced;254255impl<P: PhaseItem> RenderCommand<P> for DrawMeshInstanced {256type Param = (257SRes<RenderAssets<RenderMesh>>,258SRes<RenderMeshInstances>,259SRes<MeshAllocator>,260);261type ViewQuery = ();262type ItemQuery = Read<InstanceBuffer>;263264#[inline]265fn render<'w>(266item: &P,267_view: (),268instance_buffer: Option<&'w InstanceBuffer>,269(meshes, render_mesh_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>,270pass: &mut TrackedRenderPass<'w>,271) -> RenderCommandResult {272// A borrow check workaround.273let mesh_allocator = mesh_allocator.into_inner();274275let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity())276else {277return RenderCommandResult::Skip;278};279let Some(gpu_mesh) = meshes.into_inner().get(mesh_instance.mesh_asset_id) else {280return RenderCommandResult::Skip;281};282let Some(instance_buffer) = instance_buffer else {283return RenderCommandResult::Skip;284};285let Some(vertex_buffer_slice) =286mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)287else {288return RenderCommandResult::Skip;289};290291pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));292pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..));293294match &gpu_mesh.buffer_info {295RenderMeshBufferInfo::Indexed {296index_format,297count,298} => {299let Some(index_buffer_slice) =300mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id)301else {302return RenderCommandResult::Skip;303};304305pass.set_index_buffer(index_buffer_slice.buffer.slice(..), 0, *index_format);306pass.draw_indexed(307index_buffer_slice.range.start..(index_buffer_slice.range.start + count),308vertex_buffer_slice.range.start as i32,3090..instance_buffer.length as u32,310);311}312RenderMeshBufferInfo::NonIndexed => {313pass.draw(vertex_buffer_slice.range, 0..instance_buffer.length as u32);314}315}316RenderCommandResult::Success317}318}319320321