Path: blob/main/examples/shader_advanced/custom_shader_instancing.rs
9334 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, ViewKeyCache};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_component::SyncComponent,33sync_world::MainEntity,34view::{ExtractedView, NoIndirectDrawing},35Render, RenderApp, RenderStartup, RenderSystems,36},37};38use bytemuck::{Pod, Zeroable};3940/// This example uses a shader source file from the assets subdirectory41const SHADER_ASSET_PATH: &str = "shaders/instancing.wgsl";4243fn main() {44App::new()45.add_plugins((DefaultPlugins, CustomMaterialPlugin))46.add_systems(Startup, setup)47.run();48}4950fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {51commands.spawn((52Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),53InstanceMaterialData(54(1..=10)55.flat_map(|x| (1..=10).map(move |y| (x as f32 / 10.0, y as f32 / 10.0)))56.map(|(x, y)| InstanceData {57position: Vec3::new(x * 10.0 - 5.0, y * 10.0 - 5.0, 0.0),58scale: 1.0,59color: LinearRgba::from(Color::hsla(x * 360., y, 0.5, 1.0)).to_f32_array(),60})61.collect(),62),63// NOTE: Frustum culling is done based on the Aabb of the Mesh and the GlobalTransform.64// As the cube is at the origin, if its Aabb moves outside the view frustum, all the65// instanced cubes will be culled.66// The InstanceMaterialData contains the 'GlobalTransform' information for this custom67// instancing, and that is not taken into account with the built-in frustum culling.68// We must disable the built-in frustum culling by adding the `NoFrustumCulling` marker69// component to avoid incorrect culling.70NoFrustumCulling,71));7273// camera74commands.spawn((75Camera3d::default(),76Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y),77// We need this component because we use `draw_indexed` and `draw`78// instead of `draw_indirect_indexed` and `draw_indirect` in79// `DrawMeshInstanced::render`.80NoIndirectDrawing,81));82}8384#[derive(Component, Deref)]85struct InstanceMaterialData(Vec<InstanceData>);8687impl SyncComponent for InstanceMaterialData {88type Out = Self;89}9091impl ExtractComponent for InstanceMaterialData {92type QueryData = &'static InstanceMaterialData;93type QueryFilter = ();9495fn extract_component(item: QueryItem<'_, '_, Self::QueryData>) -> Option<Self> {96Some(InstanceMaterialData(item.0.clone()))97}98}99100struct CustomMaterialPlugin;101102impl Plugin for CustomMaterialPlugin {103fn build(&self, app: &mut App) {104app.add_plugins(ExtractComponentPlugin::<InstanceMaterialData>::default());105app.sub_app_mut(RenderApp)106.add_render_command::<Transparent3d, DrawCustom>()107.init_resource::<SpecializedMeshPipelines<CustomPipeline>>()108.add_systems(RenderStartup, init_custom_pipeline)109.add_systems(110Render,111(112queue_custom.in_set(RenderSystems::QueueMeshes),113prepare_instance_buffers.in_set(RenderSystems::PrepareResources),114),115);116}117}118119#[derive(Clone, Copy, Pod, Zeroable)]120#[repr(C)]121struct InstanceData {122position: Vec3,123scale: f32,124color: [f32; 4],125}126127fn queue_custom(128transparent_3d_draw_functions: Res<DrawFunctions<Transparent3d>>,129custom_pipeline: Res<CustomPipeline>,130mut pipelines: ResMut<SpecializedMeshPipelines<CustomPipeline>>,131pipeline_cache: Res<PipelineCache>,132meshes: Res<RenderAssets<RenderMesh>>,133render_mesh_instances: Res<RenderMeshInstances>,134material_meshes: Query<(Entity, &MainEntity), With<InstanceMaterialData>>,135mut transparent_render_phases: ResMut<ViewSortedRenderPhases<Transparent3d>>,136views: Query<&ExtractedView>,137view_key_cache: Res<ViewKeyCache>,138) {139let draw_custom = transparent_3d_draw_functions.read().id::<DrawCustom>();140141for view in &views {142let Some(transparent_phase) = transparent_render_phases.get_mut(&view.retained_view_entity)143else {144continue;145};146147let Some(&view_key) = view_key_cache.get(&view.retained_view_entity) else {148continue;149};150151let rangefinder = view.rangefinder3d();152for (entity, main_entity) in &material_meshes {153let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(*main_entity)154else {155continue;156};157let Some(mesh) = meshes.get(mesh_instance.mesh_asset_id) else {158continue;159};160let key =161view_key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());162let pipeline = pipelines163.specialize(&pipeline_cache, &custom_pipeline, key, &mesh.layout)164.unwrap();165transparent_phase.add(Transparent3d {166entity: (entity, *main_entity),167pipeline,168draw_function: draw_custom,169distance: rangefinder.distance(&mesh_instance.center),170batch_range: 0..1,171extra_index: PhaseItemExtraIndex::None,172indexed: true,173});174}175}176}177178#[derive(Component)]179struct InstanceBuffer {180buffer: Buffer,181length: usize,182}183184fn prepare_instance_buffers(185mut commands: Commands,186query: Query<(Entity, &InstanceMaterialData)>,187render_device: Res<RenderDevice>,188) {189for (entity, instance_data) in &query {190let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {191label: Some("instance data buffer"),192contents: bytemuck::cast_slice(instance_data.as_slice()),193usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,194});195commands.entity(entity).insert(InstanceBuffer {196buffer,197length: instance_data.len(),198});199}200}201202#[derive(Resource)]203struct CustomPipeline {204shader: Handle<Shader>,205mesh_pipeline: MeshPipeline,206}207208fn init_custom_pipeline(209mut commands: Commands,210asset_server: Res<AssetServer>,211mesh_pipeline: Res<MeshPipeline>,212) {213commands.insert_resource(CustomPipeline {214shader: asset_server.load(SHADER_ASSET_PATH),215mesh_pipeline: mesh_pipeline.clone(),216});217}218219impl SpecializedMeshPipeline for CustomPipeline {220type Key = MeshPipelineKey;221222fn specialize(223&self,224key: Self::Key,225layout: &MeshVertexBufferLayoutRef,226) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {227let mut descriptor = self.mesh_pipeline.specialize(key, layout)?;228229descriptor.vertex.shader = self.shader.clone();230descriptor.vertex.buffers.push(VertexBufferLayout {231array_stride: size_of::<InstanceData>() as u64,232step_mode: VertexStepMode::Instance,233attributes: vec![234VertexAttribute {235format: VertexFormat::Float32x4,236offset: 0,237shader_location: 3, // shader locations 0-2 are taken up by Position, Normal and UV attributes238},239VertexAttribute {240format: VertexFormat::Float32x4,241offset: VertexFormat::Float32x4.size(),242shader_location: 4,243},244],245});246descriptor.fragment.as_mut().unwrap().shader = self.shader.clone();247Ok(descriptor)248}249}250251type DrawCustom = (252SetItemPipeline,253SetMeshViewBindGroup<0>,254SetMeshViewBindingArrayBindGroup<1>,255SetMeshBindGroup<2>,256DrawMeshInstanced,257);258259struct DrawMeshInstanced;260261impl<P: PhaseItem> RenderCommand<P> for DrawMeshInstanced {262type Param = (263SRes<RenderAssets<RenderMesh>>,264SRes<RenderMeshInstances>,265SRes<MeshAllocator>,266);267type ViewQuery = ();268type ItemQuery = Read<InstanceBuffer>;269270#[inline]271fn render<'w>(272item: &P,273_view: (),274instance_buffer: Option<&'w InstanceBuffer>,275(meshes, render_mesh_instances, mesh_allocator): SystemParamItem<'w, '_, Self::Param>,276pass: &mut TrackedRenderPass<'w>,277) -> RenderCommandResult {278// A borrow check workaround.279let mesh_allocator = mesh_allocator.into_inner();280281let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(item.main_entity())282else {283return RenderCommandResult::Skip;284};285let Some(gpu_mesh) = meshes.into_inner().get(mesh_instance.mesh_asset_id) else {286return RenderCommandResult::Skip;287};288let Some(instance_buffer) = instance_buffer else {289return RenderCommandResult::Skip;290};291let Some(vertex_buffer_slice) =292mesh_allocator.mesh_vertex_slice(&mesh_instance.mesh_asset_id)293else {294return RenderCommandResult::Skip;295};296297pass.set_vertex_buffer(0, vertex_buffer_slice.buffer.slice(..));298pass.set_vertex_buffer(1, instance_buffer.buffer.slice(..));299300match &gpu_mesh.buffer_info {301RenderMeshBufferInfo::Indexed {302index_format,303count,304} => {305let Some(index_buffer_slice) =306mesh_allocator.mesh_index_slice(&mesh_instance.mesh_asset_id)307else {308return RenderCommandResult::Skip;309};310311pass.set_index_buffer(index_buffer_slice.buffer.slice(..), *index_format);312pass.draw_indexed(313index_buffer_slice.range.start..(index_buffer_slice.range.start + count),314vertex_buffer_slice.range.start as i32,3150..instance_buffer.length as u32,316);317}318RenderMeshBufferInfo::NonIndexed => {319pass.draw(vertex_buffer_slice.range, 0..instance_buffer.length as u32);320}321}322RenderCommandResult::Success323}324}325326327