use bevy::{ core_pipeline::{ draw_3d_graph, node, AlphaMask3d, Opaque3d, RenderTargetClearColors, Transparent3d, }, prelude::*, reflect::TypeUuid, render::{ camera::{ActiveCameras, Camera, ExtractedCameraNames, RenderTarget}, render_graph::{NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, render_phase::RenderPhase, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, renderer::RenderContext, view::RenderLayers, RenderApp, RenderStage, }, }; // This handle will point at the texture to which we will render in the first pass. pub const RENDER_IMAGE_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13378939762009864029); // The name of the final node of the first pass. pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; // The name of the camera that determines the view rendered in the first pass. pub const FIRST_PASS_CAMERA: &str = "first_pass_camera"; fn main() { let mut app = App::new(); app.insert_resource(Msaa { samples: 4 }) // Use 4x MSAA .add_plugins(DefaultPlugins) .add_startup_system(setup) .add_system(cube_rotator_system) .add_system(rotator_system); let render_app = app.sub_app_mut(RenderApp); // This will add 3D render phases for the new camera. render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases); let mut graph = render_app.world.get_resource_mut::().unwrap(); // Add a node for the first pass. graph.add_node(FIRST_PASS_DRIVER, FirstPassCameraDriver); // The first pass's dependencies include those of the main pass. graph .add_node_edge(node::MAIN_PASS_DEPENDENCIES, FIRST_PASS_DRIVER) .unwrap(); // Insert the first pass node: CLEAR_PASS_DRIVER -> FIRST_PASS_DRIVER -> MAIN_PASS_DRIVER graph .add_node_edge(node::CLEAR_PASS_DRIVER, FIRST_PASS_DRIVER) .unwrap(); graph .add_node_edge(FIRST_PASS_DRIVER, node::MAIN_PASS_DRIVER) .unwrap(); app.run(); } // Add 3D render phases for FIRST_PASS_CAMERA. fn extract_first_pass_camera_phases(mut commands: Commands, active_cameras: Res) { if let Some(camera) = active_cameras.get(FIRST_PASS_CAMERA) { if let Some(entity) = camera.entity { commands.get_or_spawn(entity).insert_bundle(( RenderPhase::::default(), RenderPhase::::default(), RenderPhase::::default(), )); } } } // A node for the first pass camera that runs draw_3d_graph with this camera. struct FirstPassCameraDriver; impl bevy::render::render_graph::Node for FirstPassCameraDriver { fn run( &self, graph: &mut RenderGraphContext, _render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { let extracted_cameras = world.get_resource::().unwrap(); if let Some(camera_3d) = extracted_cameras.entities.get(FIRST_PASS_CAMERA) { graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(*camera_3d)])?; } Ok(()) } } // Marks the first pass cube (rendered to a texture.) #[derive(Component)] struct FirstPassCube; // Marks the main pass cube, to which the texture is applied. #[derive(Component)] struct MainPassCube; fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut active_cameras: ResMut, mut images: ResMut>, mut clear_colors: ResMut, ) { let size = Extent3d { width: 512, height: 512, ..Default::default() }; // This is the texture that will be rendered to. let mut image = Image { texture_descriptor: TextureDescriptor { label: None, size, dimension: TextureDimension::D2, format: TextureFormat::Bgra8UnormSrgb, mip_level_count: 1, sample_count: 1, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST | TextureUsages::RENDER_ATTACHMENT, }, ..Default::default() }; // fill image.data with zeroes image.resize(size); let image_handle = images.set(RENDER_IMAGE_HANDLE, image); let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 })); let cube_material_handle = materials.add(StandardMaterial { base_color: Color::rgb(0.8, 0.7, 0.6), reflectance: 0.02, unlit: false, ..Default::default() }); // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube. let first_pass_layer = RenderLayers::layer(1); // The cube that will be rendered to the texture. commands .spawn_bundle(PbrBundle { mesh: cube_handle, material: cube_material_handle, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), ..Default::default() }) .insert(FirstPassCube) .insert(first_pass_layer); // Light // NOTE: Currently lights are shared between passes - see https://github.com/bevyengine/bevy/issues/3462 commands.spawn_bundle(PointLightBundle { transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), ..Default::default() }); // First pass camera let render_target = RenderTarget::Image(image_handle); clear_colors.insert(render_target.clone(), Color::WHITE); active_cameras.add(FIRST_PASS_CAMERA); commands .spawn_bundle(PerspectiveCameraBundle { camera: Camera { name: Some(FIRST_PASS_CAMERA.to_string()), target: render_target, ..Default::default() }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), ..Default::default() }) .insert(first_pass_layer); // NOTE: omitting the RenderLayers component for this camera may cause a validation error: // // thread 'main' panicked at 'wgpu error: Validation Error // // Caused by: // In a RenderPass // note: encoder = `` // In a pass parameter // note: command buffer = `` // Attempted to use texture (5, 1, Metal) mips 0..1 layers 0..1 as a combination of COLOR_TARGET within a usage scope. // // This happens because the texture would be written and read in the same frame, which is not allowed. // So either render layers must be used to avoid this, or the texture must be double buffered. let cube_size = 4.0; let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); // This material has the texture that has been rendered. let material_handle = materials.add(StandardMaterial { base_color_texture: Some(RENDER_IMAGE_HANDLE.typed()), reflectance: 0.02, unlit: false, ..Default::default() }); // Main pass cube, with material containing the rendered first pass texture. commands .spawn_bundle(PbrBundle { mesh: cube_handle, material: material_handle, transform: Transform { translation: Vec3::new(0.0, 0.0, 1.5), rotation: Quat::from_rotation_x(-std::f32::consts::PI / 5.0), ..Default::default() }, ..Default::default() }) .insert(MainPassCube); // The main pass camera. commands.spawn_bundle(PerspectiveCameraBundle { transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), ..Default::default() }); } /// Rotates the inner cube (first pass) fn rotator_system(time: Res