diff --git a/examples/shader/custom_render_phase.rs b/examples/shader/custom_render_phase.rs index d558d20740fe89..e7a4850802b906 100644 --- a/examples/shader/custom_render_phase.rs +++ b/examples/shader/custom_render_phase.rs @@ -1,4 +1,8 @@ //! An example showcasing how to set up a custom draw command and render phase +//! +//! The example shader is a very basic shader that just outputs a color at the correct position. +//! +//! This is a fairly low level example and assumes some familiarity with rendering concepts and wgpu. use bevy::{ core_pipeline::core_3d::{self, CORE_3D}, @@ -49,12 +53,15 @@ fn main() { } fn setup(mut commands: Commands, mut meshes: ResMut>) { + // camera commands.spawn(Camera3dBundle { transform: Transform::from_xyz(0.0, 1.0, 5.0), ..default() }); // Spawn 3 cubes that use the custom draw command + // Each cube is at a different depth to show that the sorting works correctly + commands.spawn(CustomMaterialMeshBundle { mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })), material: CustomMaterial { @@ -81,8 +88,7 @@ fn setup(mut commands: Commands, mut meshes: ResMut>) { }); } -const CUSTOM_NODE: &str = "custom_node"; - +// Initializing various parts of the render pipeline can be quite complex so it's easier to do it in a separate plugin pub struct CustomRenderPhasePlugin; impl Plugin for CustomRenderPhasePlugin { fn build(&self, app: &mut App) { @@ -90,16 +96,26 @@ impl Plugin for CustomRenderPhasePlugin { // automatically extract and prepare the given uniform component app.add_plugins(UniformComponentPlugin::::default()); + // We need to get the render app from the main app let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app + // The CustomPipeline is a specialized mesh pipeline so we need to initialize it .init_resource::>() // When making a custom phase you need to initialize the DrawFunctions resource for that phase .init_resource::>() // You also need to add the custom render command to that phase - .add_render_command::() + .add_render_command::(); + + // The render world + render_app + // The extract schedule is the only sync point between the main world and the render world + // When you need to send data to the render world you need to extract + // that data and you can only do it in the ExtractSchedule. + // Some common extract scenarios have plugins that will do this automatically. + // For the purpose of the example we will do it manually. .add_systems( ExtractSchedule, (extract_render_phase, extract_custom_material_uniform), @@ -112,14 +128,29 @@ impl Plugin for CustomRenderPhasePlugin { queue_mesh_custom_phase.in_set(RenderSet::Queue), queue_custom_bind_group.in_set(RenderSet::Queue), ), - ) + ); + + // Bevy's renderer uses a render graph which is a collection of nodes in a directed acyclic graph. + // It currently runs on each view/camera and executes each node in the specified order. + // It will make sure that any node that needs a dependency from another node + // only runs when that dependency is done. + // + // Each node can execute arbitrary work, but it generally runs at least one render pass. + // A node only has access to the render world, so if you need data from the main world + // you need to extract it manually or with the plugin like above. + render_app // Add the node that will render the custom phase - .add_render_graph_node::>(CORE_3D, CUSTOM_NODE) + .add_render_graph_node::>(CORE_3D, CustomNode::NAME) // This will schedule the custom node to run after the main opaque pass - .add_render_graph_edge(CORE_3D, core_3d::graph::node::MAIN_OPAQUE_PASS, CUSTOM_NODE); + .add_render_graph_edge( + CORE_3D, + core_3d::graph::node::MAIN_OPAQUE_PASS, + CustomNode::NAME, + ); } fn finish(&self, app: &mut App) { + // We need to get the render app from the main app let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; @@ -129,45 +160,13 @@ impl Plugin for CustomRenderPhasePlugin { } } -#[derive(Resource, Deref)] -pub struct CustomMaterialBindGroup(BindGroup); - -pub struct SetMaterialBindGroup; -impl RenderCommand

for SetMaterialBindGroup { - type Param = SRes; - type ViewWorldQuery = (); - type ItemWorldQuery = Read>; - - #[inline] - fn render<'w>( - _item: &P, - _view: (), - mesh_index: ROQueryItem<'w, Self::ItemWorldQuery>, - bind_group: SystemParamItem<'w, '_, Self::Param>, - pass: &mut TrackedRenderPass<'w>, - ) -> RenderCommandResult { - pass.set_bind_group(I, bind_group.into_inner(), &[mesh_index.index()]); - RenderCommandResult::Success - } -} - -// The custom draw command -type DrawCustom = ( - // Sets the render pipeline for the draw - SetItemPipeline, - // Sets the mesh view bind group at index 0 - SetMeshViewBindGroup<0>, - // Sets the custom material bind group at index 1 - SetMaterialBindGroup<1>, - // Sets the mesh bind group at index 2 - SetMeshBindGroup<2>, - // Draws the mesh with the specified pipeline and bind groups - DrawMesh, -); - // The render node that will render the custom phase #[derive(Default)] pub struct CustomNode; +impl CustomNode { + const NAME: &str = "custom_node"; +} + impl ViewNode for CustomNode { type ViewQuery = ( &'static RenderPhase, @@ -211,6 +210,46 @@ impl ViewNode for CustomNode { } } +// The bind group of the custom material +#[derive(Resource, Deref)] +pub struct CustomMaterialBindGroup(BindGroup); + +// TODO document render command +pub struct SetMaterialBindGroup; +impl RenderCommand

for SetMaterialBindGroup { + type Param = SRes; + type ViewWorldQuery = (); + type ItemWorldQuery = Read>; + + #[inline] + fn render<'w>( + _item: &P, + _view: (), + mesh_index: ROQueryItem<'w, Self::ItemWorldQuery>, + bind_group: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + pass.set_bind_group(I, bind_group.into_inner(), &[mesh_index.index()]); + RenderCommandResult::Success + } +} + +// The custom draw command +// TODO explain a bit more why it's a type alias +type DrawCustom = ( + // Sets the render pipeline for the draw + SetItemPipeline, + // Sets the mesh view bind group at index 0 + SetMeshViewBindGroup<0>, + // Sets the custom material bind group at index 1 + SetMaterialBindGroup<1>, + // Sets the mesh bind group at index 2 + SetMeshBindGroup<2>, + // Draws the mesh with the specified pipeline and bind groups + DrawMesh, +); + +// TODO explain what a PhaseItem is pub struct CustomPhaseItem { pub distance: f32, pub pipeline: CachedRenderPipelineId, @@ -284,9 +323,9 @@ impl FromWorld for CustomPipeline { } } +// TODO explain what SpecializedMeshPipeline are impl SpecializedMeshPipeline for CustomPipeline { type Key = MeshPipelineKey; - fn specialize( &self, key: Self::Key,