Path: blob/main/examples/shader_advanced/specialized_mesh_pipeline.rs
6849 views
//! Demonstrates how to define and use specialized mesh pipeline1//!2//! This example shows how to use the built-in [`SpecializedMeshPipeline`]3//! functionality with a custom [`RenderCommand`] to allow custom mesh rendering with4//! more flexibility than the material api.5//!6//! [`SpecializedMeshPipeline`] let's you customize the entire pipeline used when rendering a mesh.78use bevy::{9asset::RenderAssetUsages,10camera::visibility::{self, VisibilityClass},11core_pipeline::core_3d::{Opaque3d, Opaque3dBatchSetKey, Opaque3dBinKey, CORE_3D_DEPTH_FORMAT},12ecs::component::Tick,13math::{vec3, vec4},14mesh::{Indices, MeshVertexBufferLayoutRef, PrimitiveTopology},15pbr::{16DrawMesh, MeshPipeline, MeshPipelineKey, MeshPipelineViewLayoutKey, RenderMeshInstances,17SetMeshBindGroup, SetMeshViewBindGroup, SetMeshViewEmptyBindGroup,18},19prelude::*,20render::{21batching::gpu_preprocessing::GpuPreprocessingSupport,22extract_component::{ExtractComponent, ExtractComponentPlugin},23mesh::{allocator::MeshAllocator, RenderMesh},24render_asset::RenderAssets,25render_phase::{26AddRenderCommand, BinnedRenderPhaseType, DrawFunctions, SetItemPipeline,27ViewBinnedRenderPhases,28},29render_resource::{30ColorTargetState, ColorWrites, CompareFunction, DepthStencilState, Face, FragmentState,31FrontFace, MultisampleState, PipelineCache, PolygonMode, PrimitiveState,32RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError,33SpecializedMeshPipelines, TextureFormat, VertexState,34},35view::{ExtractedView, RenderVisibleEntities, ViewTarget},36Render, RenderApp, RenderStartup, RenderSystems,37},38};3940const SHADER_ASSET_PATH: &str = "shaders/specialized_mesh_pipeline.wgsl";4142fn main() {43App::new()44.add_plugins(DefaultPlugins)45.add_plugins(CustomRenderedMeshPipelinePlugin)46.add_systems(Startup, setup)47.run();48}4950/// Spawns the objects in the scene.51fn setup(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>) {52// Build a custom triangle mesh with colors53// We define a custom mesh because the examples only uses a limited54// set of vertex attributes for simplicity55let mesh = Mesh::new(56PrimitiveTopology::TriangleList,57RenderAssetUsages::default(),58)59.with_inserted_indices(Indices::U32(vec![0, 1, 2]))60.with_inserted_attribute(61Mesh::ATTRIBUTE_POSITION,62vec![63vec3(-0.5, -0.5, 0.0),64vec3(0.5, -0.5, 0.0),65vec3(0.0, 0.25, 0.0),66],67)68.with_inserted_attribute(69Mesh::ATTRIBUTE_COLOR,70vec![71vec4(1.0, 0.0, 0.0, 1.0),72vec4(0.0, 1.0, 0.0, 1.0),73vec4(0.0, 0.0, 1.0, 1.0),74],75);7677// spawn 3 triangles to show that batching works78for (x, y) in [-0.5, 0.0, 0.5].into_iter().zip([-0.25, 0.5, -0.25]) {79// Spawn an entity with all the required components for it to be rendered with our custom pipeline80commands.spawn((81// We use a marker component to identify the mesh that will be rendered82// with our specialized pipeline83CustomRenderedEntity,84// We need to add the mesh handle to the entity85Mesh3d(meshes.add(mesh.clone())),86Transform::from_xyz(x, y, 0.0),87));88}8990// Spawn the camera.91commands.spawn((92Camera3d::default(),93// Move the camera back a bit to see all the triangles94Transform::from_xyz(0.0, 0.0, 3.0).looking_at(Vec3::ZERO, Vec3::Y),95));96}9798// When writing custom rendering code it's generally recommended to use a plugin.99// The main reason for this is that it gives you access to the finish() hook100// which is called after rendering resources are initialized.101struct CustomRenderedMeshPipelinePlugin;102impl Plugin for CustomRenderedMeshPipelinePlugin {103fn build(&self, app: &mut App) {104app.add_plugins(ExtractComponentPlugin::<CustomRenderedEntity>::default());105106// We make sure to add these to the render app, not the main app.107let Some(render_app) = app.get_sub_app_mut(RenderApp) else {108return;109};110render_app111// This is needed to tell bevy about your custom pipeline112.init_resource::<SpecializedMeshPipelines<CustomMeshPipeline>>()113// We need to use a custom draw command so we need to register it114.add_render_command::<Opaque3d, DrawSpecializedPipelineCommands>()115.add_systems(RenderStartup, init_custom_mesh_pipeline)116.add_systems(117Render,118queue_custom_mesh_pipeline.in_set(RenderSystems::Queue),119);120}121}122123/// A marker component that represents an entity that is to be rendered using124/// our specialized pipeline.125///126/// Note the [`ExtractComponent`] trait implementation: this is necessary to127/// tell Bevy that this object should be pulled into the render world. Also note128/// the `on_add` hook, which is needed to tell Bevy's `check_visibility` system129/// that entities with this component need to be examined for visibility.130#[derive(Clone, Component, ExtractComponent)]131#[require(VisibilityClass)]132#[component(on_add = visibility::add_visibility_class::<CustomRenderedEntity>)]133struct CustomRenderedEntity;134135/// The custom draw commands that Bevy executes for each entity we enqueue into136/// the render phase.137type DrawSpecializedPipelineCommands = (138// Set the pipeline139SetItemPipeline,140// Set the view uniform at bind group 0141SetMeshViewBindGroup<0>,142// Set an empty material bind group at bind group 1143SetMeshViewEmptyBindGroup<1>,144// Set the mesh uniform at bind group 2145SetMeshBindGroup<2>,146// Draw the mesh147DrawMesh,148);149150// This contains the state needed to specialize a mesh pipeline151#[derive(Resource)]152struct CustomMeshPipeline {153/// The base mesh pipeline defined by bevy154///155/// This isn't required, but if you want to use a bevy `Mesh` it's easier when you156/// have access to the base `MeshPipeline` that bevy already defines157mesh_pipeline: MeshPipeline,158/// Stores the shader used for this pipeline directly on the pipeline.159/// This isn't required, it's only done like this for simplicity.160shader_handle: Handle<Shader>,161}162163fn init_custom_mesh_pipeline(164mut commands: Commands,165asset_server: Res<AssetServer>,166mesh_pipeline: Res<MeshPipeline>,167) {168// Load the shader169let shader_handle: Handle<Shader> = asset_server.load(SHADER_ASSET_PATH);170commands.insert_resource(CustomMeshPipeline {171mesh_pipeline: mesh_pipeline.clone(),172shader_handle,173});174}175176impl SpecializedMeshPipeline for CustomMeshPipeline {177/// Pipeline use keys to determine how to specialize it.178/// The key is also used by the pipeline cache to determine if179/// it needs to create a new pipeline or not180///181/// In this example we just use the base `MeshPipelineKey` defined by bevy, but this could be anything.182/// For example, if you want to make a pipeline with a procedural shader you could add the Handle<Shader> to the key.183type Key = MeshPipelineKey;184185fn specialize(186&self,187mesh_key: Self::Key,188layout: &MeshVertexBufferLayoutRef,189) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {190// Define the vertex attributes based on a standard bevy [`Mesh`]191let mut vertex_attributes = Vec::new();192if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {193// Make sure this matches the shader location194vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));195}196if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {197// Make sure this matches the shader location198vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(1));199}200// This will automatically generate the correct `VertexBufferLayout` based on the vertex attributes201let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;202203let view_layout = self204.mesh_pipeline205.get_view_layout(MeshPipelineViewLayoutKey::from(mesh_key));206207Ok(RenderPipelineDescriptor {208label: Some("Specialized Mesh Pipeline".into()),209layout: vec![210view_layout.main_layout.clone(),211view_layout.empty_layout.clone(),212self.mesh_pipeline.mesh_layouts.model_only.clone(),213],214vertex: VertexState {215shader: self.shader_handle.clone(),216// Customize how to store the meshes' vertex attributes in the vertex buffer217buffers: vec![vertex_buffer_layout],218..default()219},220fragment: Some(FragmentState {221shader: self.shader_handle.clone(),222targets: vec![Some(ColorTargetState {223// This isn't required, but bevy supports HDR and non-HDR rendering224// so it's generally recommended to specialize the pipeline for that225format: if mesh_key.contains(MeshPipelineKey::HDR) {226ViewTarget::TEXTURE_FORMAT_HDR227} else {228TextureFormat::bevy_default()229},230// For this example we only use opaque meshes,231// but if you wanted to use alpha blending you would need to set it here232blend: None,233write_mask: ColorWrites::ALL,234})],235..default()236}),237primitive: PrimitiveState {238topology: mesh_key.primitive_topology(),239front_face: FrontFace::Ccw,240cull_mode: Some(Face::Back),241polygon_mode: PolygonMode::Fill,242..default()243},244// Note that if your view has no depth buffer this will need to be245// changed.246depth_stencil: Some(DepthStencilState {247format: CORE_3D_DEPTH_FORMAT,248depth_write_enabled: true,249depth_compare: CompareFunction::GreaterEqual,250stencil: default(),251bias: default(),252}),253// It's generally recommended to specialize your pipeline for MSAA,254// but it's not always possible255multisample: MultisampleState {256count: mesh_key.msaa_samples(),257..default()258},259..default()260})261}262}263264/// A render-world system that enqueues the entity with custom rendering into265/// the opaque render phases of each view.266fn queue_custom_mesh_pipeline(267pipeline_cache: Res<PipelineCache>,268custom_mesh_pipeline: Res<CustomMeshPipeline>,269(mut opaque_render_phases, opaque_draw_functions): (270ResMut<ViewBinnedRenderPhases<Opaque3d>>,271Res<DrawFunctions<Opaque3d>>,272),273mut specialized_mesh_pipelines: ResMut<SpecializedMeshPipelines<CustomMeshPipeline>>,274views: Query<(&RenderVisibleEntities, &ExtractedView, &Msaa)>,275(render_meshes, render_mesh_instances): (276Res<RenderAssets<RenderMesh>>,277Res<RenderMeshInstances>,278),279mut change_tick: Local<Tick>,280mesh_allocator: Res<MeshAllocator>,281gpu_preprocessing_support: Res<GpuPreprocessingSupport>,282) {283// Get the id for our custom draw function284let draw_function = opaque_draw_functions285.read()286.id::<DrawSpecializedPipelineCommands>();287288// Render phases are per-view, so we need to iterate over all views so that289// the entity appears in them. (In this example, we have only one view, but290// it's good practice to loop over all views anyway.)291for (view_visible_entities, view, msaa) in views.iter() {292let Some(opaque_phase) = opaque_render_phases.get_mut(&view.retained_view_entity) else {293continue;294};295296// Create the key based on the view. In this case we only care about MSAA and HDR297let view_key = MeshPipelineKey::from_msaa_samples(msaa.samples())298| MeshPipelineKey::from_hdr(view.hdr);299300// Find all the custom rendered entities that are visible from this301// view.302for &(render_entity, visible_entity) in303view_visible_entities.get::<CustomRenderedEntity>().iter()304{305// Get the mesh instance306let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(visible_entity)307else {308continue;309};310311// Get the mesh data312let Some(mesh) = render_meshes.get(mesh_instance.mesh_asset_id) else {313continue;314};315316let (vertex_slab, index_slab) = mesh_allocator.mesh_slabs(&mesh_instance.mesh_asset_id);317318// Specialize the key for the current mesh entity319// For this example we only specialize based on the mesh topology320// but you could have more complex keys and that's where you'd need to create those keys321let mut mesh_key = view_key;322mesh_key |= MeshPipelineKey::from_primitive_topology(mesh.primitive_topology());323324// Finally, we can specialize the pipeline based on the key325let pipeline_id = specialized_mesh_pipelines326.specialize(327&pipeline_cache,328&custom_mesh_pipeline,329mesh_key,330&mesh.layout,331)332// This should never happen with this example, but if your pipeline333// specialization can fail you need to handle the error here334.expect("Failed to specialize mesh pipeline");335336// Bump the change tick so that Bevy is forced to rebuild the bin.337let next_change_tick = change_tick.get() + 1;338change_tick.set(next_change_tick);339340// Add the mesh with our specialized pipeline341opaque_phase.add(342Opaque3dBatchSetKey {343draw_function,344pipeline: pipeline_id,345material_bind_group_index: None,346vertex_slab: vertex_slab.unwrap_or_default(),347index_slab,348lightmap_slab: None,349},350// For this example we can use the mesh asset id as the bin key,351// but you can use any asset_id as a key352Opaque3dBinKey {353asset_id: mesh_instance.mesh_asset_id.into(),354},355(render_entity, visible_entity),356mesh_instance.current_uniform_index,357// This example supports batching and multi draw indirect,358// but if your pipeline doesn't support it you can use359// `BinnedRenderPhaseType::UnbatchableMesh`360BinnedRenderPhaseType::mesh(361mesh_instance.should_batch(),362&gpu_preprocessing_support,363),364*change_tick,365);366}367}368}369370371