//! Shows how to render to a texture. Useful for mirrors, UI, or exporting images. use bevy::{ core_pipeline::{ draw_3d_graph, node, AlphaMask3d, Opaque3d, RenderTargetClearColors, Transparent3d, }, prelude::*, render::{ camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget}, render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, render_phase::RenderPhase, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, }, renderer::RenderContext, view::RenderLayers, RenderApp, RenderStage, }, }; #[derive(Component, Default)] pub struct FirstPassCamera; // The name of the final node of the first pass. pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins) .add_plugin(CameraTypePlugin::::default()) .add_startup_system(setup) .add_system(cube_rotator_system) .add_system(rotator_system); let render_app = app.sub_app_mut(RenderApp); let driver = FirstPassCameraDriver::new(&mut render_app.world); // 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.resource_mut::(); // Add a node for the first pass. graph.add_node(FIRST_PASS_DRIVER, driver); // 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: Res>, ) { if let Some(entity) = active.get() { 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 { query: QueryState>, } impl FirstPassCameraDriver { pub fn new(render_world: &mut World) -> Self { Self { query: QueryState::new(render_world), } } } impl Node for FirstPassCameraDriver { fn update(&mut self, world: &mut World) { self.query.update_archetypes(world); } fn run( &self, graph: &mut RenderGraphContext, _render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { for camera in self.query.iter_manual(world) { graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?; } 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 images: ResMut>, mut clear_colors: ResMut, ) { let size = Extent3d { width: 512, height: 512, ..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() }; // fill image.data with zeroes image.resize(size); let image_handle = images.add(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() }); // 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() }) .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() }); // First pass camera let render_target = RenderTarget::Image(image_handle.clone()); clear_colors.insert(render_target.clone(), Color::WHITE); commands .spawn_bundle(PerspectiveCameraBundle:: { camera: Camera { target: render_target, ..default() }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), ..PerspectiveCameraBundle::new() }) .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(image_handle), reflectance: 0.02, unlit: false, ..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() }) .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() }); } /// Rotates the inner cube (first pass) fn rotator_system(time: Res