Add capability to render to a texture (#3412)
# Objective Will fix #3377 and #3254 ## Solution Use an enum to represent either a `WindowId` or `Handle<Image>` in place of `Camera::window`. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
		
							parent
							
								
									ba6b74ba20
								
							
						
					
					
						commit
						81d57e129b
					
				@ -212,6 +212,10 @@ path = "examples/3d/spherical_area_lights.rs"
 | 
			
		||||
name = "texture"
 | 
			
		||||
path = "examples/3d/texture.rs"
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "render_to_texture"
 | 
			
		||||
path = "examples/3d/render_to_texture.rs"
 | 
			
		||||
 | 
			
		||||
[[example]]
 | 
			
		||||
name = "update_gltf_scene"
 | 
			
		||||
path = "examples/3d/update_gltf_scene.rs"
 | 
			
		||||
 | 
			
		||||
@ -19,4 +19,5 @@ bevy_asset = { path = "../bevy_asset", version = "0.6.0" }
 | 
			
		||||
bevy_core = { path = "../bevy_core", version = "0.6.0" }
 | 
			
		||||
bevy_ecs = { path = "../bevy_ecs", version = "0.6.0" }
 | 
			
		||||
bevy_render = { path = "../bevy_render", version = "0.6.0" }
 | 
			
		||||
bevy_utils = { path = "../bevy_utils", version = "0.6.0" }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,11 @@
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
use crate::ClearColor;
 | 
			
		||||
use crate::{ClearColor, RenderTargetClearColors};
 | 
			
		||||
use bevy_ecs::prelude::*;
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    camera::ExtractedCamera,
 | 
			
		||||
    camera::{ExtractedCamera, RenderTarget},
 | 
			
		||||
    prelude::Image,
 | 
			
		||||
    render_asset::RenderAssets,
 | 
			
		||||
    render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo},
 | 
			
		||||
    render_resource::{
 | 
			
		||||
        LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
 | 
			
		||||
@ -47,21 +49,26 @@ impl Node for ClearPassNode {
 | 
			
		||||
        render_context: &mut RenderContext,
 | 
			
		||||
        world: &World,
 | 
			
		||||
    ) -> Result<(), NodeRunError> {
 | 
			
		||||
        let mut cleared_windows = HashSet::new();
 | 
			
		||||
        let mut cleared_targets = HashSet::new();
 | 
			
		||||
        let clear_color = world.get_resource::<ClearColor>().unwrap();
 | 
			
		||||
        let render_target_clear_colors = world.get_resource::<RenderTargetClearColors>().unwrap();
 | 
			
		||||
 | 
			
		||||
        // This gets all ViewTargets and ViewDepthTextures and clears its attachments
 | 
			
		||||
        // TODO: This has the potential to clear the same target multiple times, if there
 | 
			
		||||
        // are multiple views drawing to the same target. This should be fixed when we make
 | 
			
		||||
        // clearing happen on "render targets" instead of "views" (see the TODO below for more context).
 | 
			
		||||
        for (target, depth, camera) in self.query.iter_manual(world) {
 | 
			
		||||
            let mut color = &clear_color.0;
 | 
			
		||||
            if let Some(camera) = camera {
 | 
			
		||||
                cleared_windows.insert(camera.window_id);
 | 
			
		||||
                cleared_targets.insert(&camera.target);
 | 
			
		||||
                if let Some(target_color) = render_target_clear_colors.get(&camera.target) {
 | 
			
		||||
                    color = target_color;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            let pass_descriptor = RenderPassDescriptor {
 | 
			
		||||
                label: Some("clear_pass"),
 | 
			
		||||
                color_attachments: &[target.get_color_attachment(Operations {
 | 
			
		||||
                    load: LoadOp::Clear(clear_color.0.into()),
 | 
			
		||||
                    load: LoadOp::Clear((*color).into()),
 | 
			
		||||
                    store: true,
 | 
			
		||||
                })],
 | 
			
		||||
                depth_stencil_attachment: depth.map(|depth| RenderPassDepthStencilAttachment {
 | 
			
		||||
@ -83,18 +90,28 @@ impl Node for ClearPassNode {
 | 
			
		||||
        // which will cause panics. The real fix here is to clear "render targets" directly
 | 
			
		||||
        // instead of "views". This should be removed once full RenderTargets are implemented.
 | 
			
		||||
        let windows = world.get_resource::<ExtractedWindows>().unwrap();
 | 
			
		||||
        for window in windows.values() {
 | 
			
		||||
        let images = world.get_resource::<RenderAssets<Image>>().unwrap();
 | 
			
		||||
        for target in render_target_clear_colors.colors.keys().cloned().chain(
 | 
			
		||||
            windows
 | 
			
		||||
                .values()
 | 
			
		||||
                .map(|window| RenderTarget::Window(window.id)),
 | 
			
		||||
        ) {
 | 
			
		||||
            // skip windows that have already been cleared
 | 
			
		||||
            if cleared_windows.contains(&window.id) {
 | 
			
		||||
            if cleared_targets.contains(&target) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            let pass_descriptor = RenderPassDescriptor {
 | 
			
		||||
                label: Some("clear_pass"),
 | 
			
		||||
                color_attachments: &[RenderPassColorAttachment {
 | 
			
		||||
                    view: window.swap_chain_texture.as_ref().unwrap(),
 | 
			
		||||
                    view: target.get_texture_view(windows, images).unwrap(),
 | 
			
		||||
                    resolve_target: None,
 | 
			
		||||
                    ops: Operations {
 | 
			
		||||
                        load: LoadOp::Clear(clear_color.0.into()),
 | 
			
		||||
                        load: LoadOp::Clear(
 | 
			
		||||
                            (*render_target_clear_colors
 | 
			
		||||
                                .get(&target)
 | 
			
		||||
                                .unwrap_or(&clear_color.0))
 | 
			
		||||
                            .into(),
 | 
			
		||||
                        ),
 | 
			
		||||
                        store: true,
 | 
			
		||||
                    },
 | 
			
		||||
                }],
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,8 @@ pub mod prelude {
 | 
			
		||||
    pub use crate::ClearColor;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use bevy_utils::HashMap;
 | 
			
		||||
 | 
			
		||||
pub use clear_pass::*;
 | 
			
		||||
pub use clear_pass_driver::*;
 | 
			
		||||
pub use main_pass_2d::*;
 | 
			
		||||
@ -21,7 +23,7 @@ use bevy_app::{App, Plugin};
 | 
			
		||||
use bevy_core::FloatOrd;
 | 
			
		||||
use bevy_ecs::prelude::*;
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    camera::{ActiveCameras, CameraPlugin},
 | 
			
		||||
    camera::{ActiveCameras, CameraPlugin, RenderTarget},
 | 
			
		||||
    color::Color,
 | 
			
		||||
    render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
 | 
			
		||||
    render_phase::{
 | 
			
		||||
@ -48,6 +50,20 @@ impl Default for ClearColor {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Default)]
 | 
			
		||||
pub struct RenderTargetClearColors {
 | 
			
		||||
    colors: HashMap<RenderTarget, Color>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RenderTargetClearColors {
 | 
			
		||||
    pub fn get(&self, target: &RenderTarget) -> Option<&Color> {
 | 
			
		||||
        self.colors.get(target)
 | 
			
		||||
    }
 | 
			
		||||
    pub fn insert(&mut self, target: RenderTarget, color: Color) {
 | 
			
		||||
        self.colors.insert(target, color);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Plugins that contribute to the RenderGraph should use the following label conventions:
 | 
			
		||||
// 1. Graph modules should have a NAME, input module, and node module (where relevant)
 | 
			
		||||
// 2. The "top level" graph is the plugin module root. Just add things like `pub mod node` directly under the plugin module
 | 
			
		||||
@ -96,7 +112,8 @@ pub enum CorePipelineRenderSystems {
 | 
			
		||||
 | 
			
		||||
impl Plugin for CorePipelinePlugin {
 | 
			
		||||
    fn build(&self, app: &mut App) {
 | 
			
		||||
        app.init_resource::<ClearColor>();
 | 
			
		||||
        app.init_resource::<ClearColor>()
 | 
			
		||||
            .init_resource::<RenderTargetClearColors>();
 | 
			
		||||
 | 
			
		||||
        let render_app = match app.get_sub_app_mut(RenderApp) {
 | 
			
		||||
            Ok(render_app) => render_app,
 | 
			
		||||
@ -330,12 +347,22 @@ impl CachedPipelinePhaseItem for Transparent3d {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_clear_color(clear_color: Res<ClearColor>, mut render_world: ResMut<RenderWorld>) {
 | 
			
		||||
pub fn extract_clear_color(
 | 
			
		||||
    clear_color: Res<ClearColor>,
 | 
			
		||||
    clear_colors: Res<RenderTargetClearColors>,
 | 
			
		||||
    mut render_world: ResMut<RenderWorld>,
 | 
			
		||||
) {
 | 
			
		||||
    // If the clear color has changed
 | 
			
		||||
    if clear_color.is_changed() {
 | 
			
		||||
        // Update the clear color resource in the render world
 | 
			
		||||
        render_world.insert_resource(clear_color.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // If the clear color has changed
 | 
			
		||||
    if clear_colors.is_changed() {
 | 
			
		||||
        // Update the clear color resource in the render world
 | 
			
		||||
        render_world.insert_resource(clear_colors.clone());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn extract_core_pipeline_camera_phases(
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,13 @@
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
use bevy_asset::Assets;
 | 
			
		||||
use bevy_ecs::prelude::*;
 | 
			
		||||
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
 | 
			
		||||
use bevy_reflect::Reflect;
 | 
			
		||||
use bevy_render::{
 | 
			
		||||
    camera::{Camera, CameraProjection, OrthographicProjection},
 | 
			
		||||
    color::Color,
 | 
			
		||||
    prelude::Image,
 | 
			
		||||
    primitives::{Aabb, CubemapFrusta, Frustum, Sphere},
 | 
			
		||||
    view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities},
 | 
			
		||||
};
 | 
			
		||||
@ -354,62 +356,62 @@ const Z_SLICES: u32 = 24;
 | 
			
		||||
pub fn add_clusters(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    images: Res<Assets<Image>>,
 | 
			
		||||
    cameras: Query<(Entity, &Camera), Without<Clusters>>,
 | 
			
		||||
) {
 | 
			
		||||
    for (entity, camera) in cameras.iter() {
 | 
			
		||||
        let window = match windows.get(camera.window) {
 | 
			
		||||
            Some(window) => window,
 | 
			
		||||
            None => continue,
 | 
			
		||||
        };
 | 
			
		||||
        let clusters = Clusters::from_screen_size_and_z_slices(
 | 
			
		||||
            UVec2::new(window.physical_width(), window.physical_height()),
 | 
			
		||||
            Z_SLICES,
 | 
			
		||||
        );
 | 
			
		||||
        commands.entity(entity).insert(clusters);
 | 
			
		||||
        if let Some(size) = camera.target.get_physical_size(&windows, &images) {
 | 
			
		||||
            let clusters = Clusters::from_screen_size_and_z_slices(size, Z_SLICES);
 | 
			
		||||
            commands.entity(entity).insert(clusters);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn update_clusters(windows: Res<Windows>, mut views: Query<(&Camera, &mut Clusters)>) {
 | 
			
		||||
pub fn update_clusters(
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    images: Res<Assets<Image>>,
 | 
			
		||||
    mut views: Query<(&Camera, &mut Clusters)>,
 | 
			
		||||
) {
 | 
			
		||||
    for (camera, mut clusters) in views.iter_mut() {
 | 
			
		||||
        let is_orthographic = camera.projection_matrix.w_axis.w == 1.0;
 | 
			
		||||
        let inverse_projection = camera.projection_matrix.inverse();
 | 
			
		||||
        let window = windows.get(camera.window).unwrap();
 | 
			
		||||
        let screen_size_u32 = UVec2::new(window.physical_width(), window.physical_height());
 | 
			
		||||
        // Don't update clusters if screen size is 0.
 | 
			
		||||
        if screen_size_u32.x == 0 || screen_size_u32.y == 0 {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        *clusters =
 | 
			
		||||
            Clusters::from_screen_size_and_z_slices(screen_size_u32, clusters.axis_slices.z);
 | 
			
		||||
        let screen_size = screen_size_u32.as_vec2();
 | 
			
		||||
        let tile_size_u32 = clusters.tile_size;
 | 
			
		||||
        let tile_size = tile_size_u32.as_vec2();
 | 
			
		||||
        if let Some(screen_size_u32) = camera.target.get_physical_size(&windows, &images) {
 | 
			
		||||
            // Don't update clusters if screen size is 0.
 | 
			
		||||
            if screen_size_u32.x == 0 || screen_size_u32.y == 0 {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            *clusters =
 | 
			
		||||
                Clusters::from_screen_size_and_z_slices(screen_size_u32, clusters.axis_slices.z);
 | 
			
		||||
            let screen_size = screen_size_u32.as_vec2();
 | 
			
		||||
            let tile_size_u32 = clusters.tile_size;
 | 
			
		||||
            let tile_size = tile_size_u32.as_vec2();
 | 
			
		||||
 | 
			
		||||
        // Calculate view space AABBs
 | 
			
		||||
        // NOTE: It is important that these are iterated in a specific order
 | 
			
		||||
        // so that we can calculate the cluster index in the fragment shader!
 | 
			
		||||
        // I (Rob Swain) choose to scan along rows of tiles in x,y, and for each tile then scan
 | 
			
		||||
        // along z
 | 
			
		||||
        let mut aabbs = Vec::with_capacity(
 | 
			
		||||
            (clusters.axis_slices.y * clusters.axis_slices.x * clusters.axis_slices.z) as usize,
 | 
			
		||||
        );
 | 
			
		||||
        for y in 0..clusters.axis_slices.y {
 | 
			
		||||
            for x in 0..clusters.axis_slices.x {
 | 
			
		||||
                for z in 0..clusters.axis_slices.z {
 | 
			
		||||
                    aabbs.push(compute_aabb_for_cluster(
 | 
			
		||||
                        clusters.near,
 | 
			
		||||
                        camera.far,
 | 
			
		||||
                        tile_size,
 | 
			
		||||
                        screen_size,
 | 
			
		||||
                        inverse_projection,
 | 
			
		||||
                        is_orthographic,
 | 
			
		||||
                        clusters.axis_slices,
 | 
			
		||||
                        UVec3::new(x, y, z),
 | 
			
		||||
                    ));
 | 
			
		||||
            // Calculate view space AABBs
 | 
			
		||||
            // NOTE: It is important that these are iterated in a specific order
 | 
			
		||||
            // so that we can calculate the cluster index in the fragment shader!
 | 
			
		||||
            // I (Rob Swain) choose to scan along rows of tiles in x,y, and for each tile then scan
 | 
			
		||||
            // along z
 | 
			
		||||
            let mut aabbs = Vec::with_capacity(
 | 
			
		||||
                (clusters.axis_slices.y * clusters.axis_slices.x * clusters.axis_slices.z) as usize,
 | 
			
		||||
            );
 | 
			
		||||
            for y in 0..clusters.axis_slices.y {
 | 
			
		||||
                for x in 0..clusters.axis_slices.x {
 | 
			
		||||
                    for z in 0..clusters.axis_slices.z {
 | 
			
		||||
                        aabbs.push(compute_aabb_for_cluster(
 | 
			
		||||
                            clusters.near,
 | 
			
		||||
                            camera.far,
 | 
			
		||||
                            tile_size,
 | 
			
		||||
                            screen_size,
 | 
			
		||||
                            inverse_projection,
 | 
			
		||||
                            is_orthographic,
 | 
			
		||||
                            clusters.axis_slices,
 | 
			
		||||
                            UVec3::new(x, y, z),
 | 
			
		||||
                        ));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            clusters.aabbs = aabbs;
 | 
			
		||||
        }
 | 
			
		||||
        clusters.aabbs = aabbs;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
use crate::camera::CameraProjection;
 | 
			
		||||
use crate::{
 | 
			
		||||
    camera::CameraProjection, prelude::Image, render_asset::RenderAssets,
 | 
			
		||||
    render_resource::TextureView, view::ExtractedWindows,
 | 
			
		||||
};
 | 
			
		||||
use bevy_asset::{AssetEvent, Assets, Handle};
 | 
			
		||||
use bevy_ecs::{
 | 
			
		||||
    component::Component,
 | 
			
		||||
    entity::Entity,
 | 
			
		||||
@ -8,11 +12,13 @@ use bevy_ecs::{
 | 
			
		||||
    reflect::ReflectComponent,
 | 
			
		||||
    system::{QuerySet, Res},
 | 
			
		||||
};
 | 
			
		||||
use bevy_math::{Mat4, Vec2, Vec3};
 | 
			
		||||
use bevy_math::{Mat4, UVec2, Vec2, Vec3};
 | 
			
		||||
use bevy_reflect::{Reflect, ReflectDeserialize};
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
use bevy_utils::HashSet;
 | 
			
		||||
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use wgpu::Extent3d;
 | 
			
		||||
 | 
			
		||||
#[derive(Component, Default, Debug, Reflect)]
 | 
			
		||||
#[reflect(Component)]
 | 
			
		||||
@ -20,13 +26,77 @@ pub struct Camera {
 | 
			
		||||
    pub projection_matrix: Mat4,
 | 
			
		||||
    pub name: Option<String>,
 | 
			
		||||
    #[reflect(ignore)]
 | 
			
		||||
    pub window: WindowId,
 | 
			
		||||
    pub target: RenderTarget,
 | 
			
		||||
    #[reflect(ignore)]
 | 
			
		||||
    pub depth_calculation: DepthCalculation,
 | 
			
		||||
    pub near: f32,
 | 
			
		||||
    pub far: f32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash)]
 | 
			
		||||
pub enum RenderTarget {
 | 
			
		||||
    /// Window to which the camera's view is rendered.
 | 
			
		||||
    Window(WindowId),
 | 
			
		||||
    /// Image to which the camera's view is rendered.
 | 
			
		||||
    Image(Handle<Image>),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for RenderTarget {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::Window(Default::default())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RenderTarget {
 | 
			
		||||
    pub fn get_texture_view<'a>(
 | 
			
		||||
        &self,
 | 
			
		||||
        windows: &'a ExtractedWindows,
 | 
			
		||||
        images: &'a RenderAssets<Image>,
 | 
			
		||||
    ) -> Option<&'a TextureView> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RenderTarget::Window(window_id) => windows
 | 
			
		||||
                .get(window_id)
 | 
			
		||||
                .and_then(|window| window.swap_chain_texture.as_ref()),
 | 
			
		||||
            RenderTarget::Image(image_handle) => {
 | 
			
		||||
                images.get(image_handle).map(|image| &image.texture_view)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_physical_size(&self, windows: &Windows, images: &Assets<Image>) -> Option<UVec2> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RenderTarget::Window(window_id) => windows
 | 
			
		||||
                .get(*window_id)
 | 
			
		||||
                .map(|window| UVec2::new(window.physical_width(), window.physical_height())),
 | 
			
		||||
            RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| {
 | 
			
		||||
                let Extent3d { width, height, .. } = image.texture_descriptor.size;
 | 
			
		||||
                UVec2::new(width, height)
 | 
			
		||||
            }),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    pub fn get_logical_size(&self, windows: &Windows, images: &Assets<Image>) -> Option<Vec2> {
 | 
			
		||||
        match self {
 | 
			
		||||
            RenderTarget::Window(window_id) => windows
 | 
			
		||||
                .get(*window_id)
 | 
			
		||||
                .map(|window| Vec2::new(window.width(), window.height())),
 | 
			
		||||
            RenderTarget::Image(image_handle) => images.get(image_handle).map(|image| {
 | 
			
		||||
                let Extent3d { width, height, .. } = image.texture_descriptor.size;
 | 
			
		||||
                Vec2::new(width as f32, height as f32)
 | 
			
		||||
            }),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // Check if this render target is contained in the given changed windows or images.
 | 
			
		||||
    fn is_changed(
 | 
			
		||||
        &self,
 | 
			
		||||
        changed_window_ids: &[WindowId],
 | 
			
		||||
        changed_image_handles: &HashSet<&Handle<Image>>,
 | 
			
		||||
    ) -> bool {
 | 
			
		||||
        match self {
 | 
			
		||||
            RenderTarget::Window(window_id) => changed_window_ids.contains(window_id),
 | 
			
		||||
            RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
 | 
			
		||||
#[reflect_value(Serialize, Deserialize)]
 | 
			
		||||
pub enum DepthCalculation {
 | 
			
		||||
@ -47,11 +117,11 @@ impl Camera {
 | 
			
		||||
    pub fn world_to_screen(
 | 
			
		||||
        &self,
 | 
			
		||||
        windows: &Windows,
 | 
			
		||||
        images: &Assets<Image>,
 | 
			
		||||
        camera_transform: &GlobalTransform,
 | 
			
		||||
        world_position: Vec3,
 | 
			
		||||
    ) -> Option<Vec2> {
 | 
			
		||||
        let window = windows.get(self.window)?;
 | 
			
		||||
        let window_size = Vec2::new(window.width(), window.height());
 | 
			
		||||
        let window_size = self.target.get_logical_size(windows, images)?;
 | 
			
		||||
        // Build a transform to convert from world to NDC using camera data
 | 
			
		||||
        let world_to_ndc: Mat4 =
 | 
			
		||||
            self.projection_matrix * camera_transform.compute_matrix().inverse();
 | 
			
		||||
@ -74,7 +144,9 @@ impl Camera {
 | 
			
		||||
pub fn camera_system<T: CameraProjection + Component>(
 | 
			
		||||
    mut window_resized_events: EventReader<WindowResized>,
 | 
			
		||||
    mut window_created_events: EventReader<WindowCreated>,
 | 
			
		||||
    mut image_asset_events: EventReader<AssetEvent<Image>>,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    images: Res<Assets<Image>>,
 | 
			
		||||
    mut queries: QuerySet<(
 | 
			
		||||
        QueryState<(Entity, &mut Camera, &mut T)>,
 | 
			
		||||
        QueryState<Entity, Added<Camera>>,
 | 
			
		||||
@ -101,17 +173,30 @@ pub fn camera_system<T: CameraProjection + Component>(
 | 
			
		||||
        changed_window_ids.push(event.id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let changed_image_handles: HashSet<&Handle<Image>> = image_asset_events
 | 
			
		||||
        .iter()
 | 
			
		||||
        .filter_map(|event| {
 | 
			
		||||
            if let AssetEvent::Modified { handle } = event {
 | 
			
		||||
                Some(handle)
 | 
			
		||||
            } else {
 | 
			
		||||
                None
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
 | 
			
		||||
    let mut added_cameras = vec![];
 | 
			
		||||
    for entity in &mut queries.q1().iter() {
 | 
			
		||||
        added_cameras.push(entity);
 | 
			
		||||
    }
 | 
			
		||||
    for (entity, mut camera, mut camera_projection) in queries.q0().iter_mut() {
 | 
			
		||||
        if let Some(window) = windows.get(camera.window) {
 | 
			
		||||
            if changed_window_ids.contains(&window.id())
 | 
			
		||||
                || added_cameras.contains(&entity)
 | 
			
		||||
                || camera_projection.is_changed()
 | 
			
		||||
            {
 | 
			
		||||
                camera_projection.update(window.width(), window.height());
 | 
			
		||||
        if camera
 | 
			
		||||
            .target
 | 
			
		||||
            .is_changed(&changed_window_ids, &changed_image_handles)
 | 
			
		||||
            || added_cameras.contains(&entity)
 | 
			
		||||
            || camera_projection.is_changed()
 | 
			
		||||
        {
 | 
			
		||||
            if let Some(size) = camera.target.get_logical_size(&windows, &images) {
 | 
			
		||||
                camera_projection.update(size.x, size.y);
 | 
			
		||||
                camera.projection_matrix = camera_projection.get_projection_matrix();
 | 
			
		||||
                camera.depth_calculation = camera_projection.depth_calculation();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -5,14 +5,17 @@ mod camera;
 | 
			
		||||
mod projection;
 | 
			
		||||
 | 
			
		||||
pub use active_cameras::*;
 | 
			
		||||
use bevy_asset::Assets;
 | 
			
		||||
use bevy_math::UVec2;
 | 
			
		||||
use bevy_transform::components::GlobalTransform;
 | 
			
		||||
use bevy_utils::HashMap;
 | 
			
		||||
use bevy_window::{WindowId, Windows};
 | 
			
		||||
use bevy_window::Windows;
 | 
			
		||||
pub use bundle::*;
 | 
			
		||||
pub use camera::*;
 | 
			
		||||
pub use projection::*;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    prelude::Image,
 | 
			
		||||
    primitives::Aabb,
 | 
			
		||||
    view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities},
 | 
			
		||||
    RenderApp, RenderStage,
 | 
			
		||||
@ -68,14 +71,16 @@ pub struct ExtractedCameraNames {
 | 
			
		||||
 | 
			
		||||
#[derive(Component, Debug)]
 | 
			
		||||
pub struct ExtractedCamera {
 | 
			
		||||
    pub window_id: WindowId,
 | 
			
		||||
    pub target: RenderTarget,
 | 
			
		||||
    pub name: Option<String>,
 | 
			
		||||
    pub physical_size: Option<UVec2>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_cameras(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    active_cameras: Res<ActiveCameras>,
 | 
			
		||||
    windows: Res<Windows>,
 | 
			
		||||
    images: Res<Assets<Image>>,
 | 
			
		||||
    query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>,
 | 
			
		||||
) {
 | 
			
		||||
    let mut entities = HashMap::default();
 | 
			
		||||
@ -84,18 +89,19 @@ fn extract_cameras(
 | 
			
		||||
        if let Some((entity, camera, transform, visible_entities)) =
 | 
			
		||||
            camera.entity.and_then(|e| query.get(e).ok())
 | 
			
		||||
        {
 | 
			
		||||
            if let Some(window) = windows.get(camera.window) {
 | 
			
		||||
            if let Some(size) = camera.target.get_physical_size(&windows, &images) {
 | 
			
		||||
                entities.insert(name.clone(), entity);
 | 
			
		||||
                commands.get_or_spawn(entity).insert_bundle((
 | 
			
		||||
                    ExtractedCamera {
 | 
			
		||||
                        window_id: camera.window,
 | 
			
		||||
                        target: camera.target.clone(),
 | 
			
		||||
                        name: camera.name.clone(),
 | 
			
		||||
                        physical_size: camera.target.get_physical_size(&windows, &images),
 | 
			
		||||
                    },
 | 
			
		||||
                    ExtractedView {
 | 
			
		||||
                        projection: camera.projection_matrix,
 | 
			
		||||
                        transform: *transform,
 | 
			
		||||
                        width: window.physical_width().max(1),
 | 
			
		||||
                        height: window.physical_height().max(1),
 | 
			
		||||
                        width: size.x.max(1),
 | 
			
		||||
                        height: size.y.max(1),
 | 
			
		||||
                        near: camera.near,
 | 
			
		||||
                        far: camera.far,
 | 
			
		||||
                    },
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,8 @@ pub use window::*;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    camera::{ExtractedCamera, ExtractedCameraNames},
 | 
			
		||||
    prelude::Image,
 | 
			
		||||
    render_asset::RenderAssets,
 | 
			
		||||
    render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView},
 | 
			
		||||
    renderer::{RenderDevice, RenderQueue},
 | 
			
		||||
    texture::{BevyDefault, TextureCache},
 | 
			
		||||
@ -170,10 +172,12 @@ fn prepare_view_uniforms(
 | 
			
		||||
        .write_buffer(&render_device, &render_queue);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[allow(clippy::too_many_arguments)]
 | 
			
		||||
fn prepare_view_targets(
 | 
			
		||||
    mut commands: Commands,
 | 
			
		||||
    camera_names: Res<ExtractedCameraNames>,
 | 
			
		||||
    windows: Res<ExtractedWindows>,
 | 
			
		||||
    images: Res<RenderAssets<Image>>,
 | 
			
		||||
    msaa: Res<Msaa>,
 | 
			
		||||
    render_device: Res<RenderDevice>,
 | 
			
		||||
    mut texture_cache: ResMut<TextureCache>,
 | 
			
		||||
@ -185,41 +189,34 @@ fn prepare_view_targets(
 | 
			
		||||
        } else {
 | 
			
		||||
            continue;
 | 
			
		||||
        };
 | 
			
		||||
        let window = if let Some(window) = windows.get(&camera.window_id) {
 | 
			
		||||
            window
 | 
			
		||||
        } else {
 | 
			
		||||
            continue;
 | 
			
		||||
        };
 | 
			
		||||
        let swap_chain_texture = if let Some(texture) = &window.swap_chain_texture {
 | 
			
		||||
            texture
 | 
			
		||||
        } else {
 | 
			
		||||
            continue;
 | 
			
		||||
        };
 | 
			
		||||
        let sampled_target = if msaa.samples > 1 {
 | 
			
		||||
            let sampled_texture = texture_cache.get(
 | 
			
		||||
                &render_device,
 | 
			
		||||
                TextureDescriptor {
 | 
			
		||||
                    label: Some("sampled_color_attachment_texture"),
 | 
			
		||||
                    size: Extent3d {
 | 
			
		||||
                        width: window.physical_width,
 | 
			
		||||
                        height: window.physical_height,
 | 
			
		||||
                        depth_or_array_layers: 1,
 | 
			
		||||
                    },
 | 
			
		||||
                    mip_level_count: 1,
 | 
			
		||||
                    sample_count: msaa.samples,
 | 
			
		||||
                    dimension: TextureDimension::D2,
 | 
			
		||||
                    format: TextureFormat::bevy_default(),
 | 
			
		||||
                    usage: TextureUsages::RENDER_ATTACHMENT,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
            Some(sampled_texture.default_view.clone())
 | 
			
		||||
        } else {
 | 
			
		||||
            None
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        commands.entity(entity).insert(ViewTarget {
 | 
			
		||||
            view: swap_chain_texture.clone(),
 | 
			
		||||
            sampled_target,
 | 
			
		||||
        });
 | 
			
		||||
        if let Some(size) = camera.physical_size {
 | 
			
		||||
            if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) {
 | 
			
		||||
                let sampled_target = if msaa.samples > 1 {
 | 
			
		||||
                    let sampled_texture = texture_cache.get(
 | 
			
		||||
                        &render_device,
 | 
			
		||||
                        TextureDescriptor {
 | 
			
		||||
                            label: Some("sampled_color_attachment_texture"),
 | 
			
		||||
                            size: Extent3d {
 | 
			
		||||
                                width: size.x,
 | 
			
		||||
                                height: size.y,
 | 
			
		||||
                                depth_or_array_layers: 1,
 | 
			
		||||
                            },
 | 
			
		||||
                            mip_level_count: 1,
 | 
			
		||||
                            sample_count: msaa.samples,
 | 
			
		||||
                            dimension: TextureDimension::D2,
 | 
			
		||||
                            format: TextureFormat::bevy_default(),
 | 
			
		||||
                            usage: TextureUsages::RENDER_ATTACHMENT,
 | 
			
		||||
                        },
 | 
			
		||||
                    );
 | 
			
		||||
                    Some(sampled_texture.default_view.clone())
 | 
			
		||||
                } else {
 | 
			
		||||
                    None
 | 
			
		||||
                };
 | 
			
		||||
                commands.entity(entity).insert(ViewTarget {
 | 
			
		||||
                    view: texture_view.clone(),
 | 
			
		||||
                    sampled_target,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,17 +49,17 @@ impl RenderLayers {
 | 
			
		||||
    pub const TOTAL_LAYERS: usize = std::mem::size_of::<LayerMask>() * 8;
 | 
			
		||||
 | 
			
		||||
    /// Create a new `RenderLayers` belonging to the given layer.
 | 
			
		||||
    pub fn layer(n: Layer) -> Self {
 | 
			
		||||
    pub const fn layer(n: Layer) -> Self {
 | 
			
		||||
        RenderLayers(0).with(n)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new `RenderLayers` that belongs to all layers.
 | 
			
		||||
    pub fn all() -> Self {
 | 
			
		||||
    pub const fn all() -> Self {
 | 
			
		||||
        RenderLayers(u32::MAX)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new `RenderLayers` that belongs to no layers.
 | 
			
		||||
    pub fn none() -> Self {
 | 
			
		||||
    pub const fn none() -> Self {
 | 
			
		||||
        RenderLayers(0)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -75,9 +75,8 @@ impl RenderLayers {
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn with(mut self, layer: Layer) -> Self {
 | 
			
		||||
        assert!(usize::from(layer) < Self::TOTAL_LAYERS);
 | 
			
		||||
    pub const fn with(mut self, layer: Layer) -> Self {
 | 
			
		||||
        assert!((layer as usize) < Self::TOTAL_LAYERS);
 | 
			
		||||
        self.0 |= 1 << layer;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
@ -86,9 +85,8 @@ impl RenderLayers {
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`.
 | 
			
		||||
    #[must_use]
 | 
			
		||||
    pub fn without(mut self, layer: Layer) -> Self {
 | 
			
		||||
        assert!(usize::from(layer) < Self::TOTAL_LAYERS);
 | 
			
		||||
    pub const fn without(mut self, layer: Layer) -> Self {
 | 
			
		||||
        assert!((layer as usize) < Self::TOTAL_LAYERS);
 | 
			
		||||
        self.0 &= !(1 << layer);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										242
									
								
								examples/3d/render_to_texture.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								examples/3d/render_to_texture.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,242 @@
 | 
			
		||||
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::<RenderGraph>().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<ActiveCameras>) {
 | 
			
		||||
    if let Some(camera) = active_cameras.get(FIRST_PASS_CAMERA) {
 | 
			
		||||
        if let Some(entity) = camera.entity {
 | 
			
		||||
            commands.get_or_spawn(entity).insert_bundle((
 | 
			
		||||
                RenderPhase::<Opaque3d>::default(),
 | 
			
		||||
                RenderPhase::<AlphaMask3d>::default(),
 | 
			
		||||
                RenderPhase::<Transparent3d>::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::<ExtractedCameraNames>().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<Assets<Mesh>>,
 | 
			
		||||
    mut materials: ResMut<Assets<StandardMaterial>>,
 | 
			
		||||
    mut active_cameras: ResMut<ActiveCameras>,
 | 
			
		||||
    mut images: ResMut<Assets<Image>>,
 | 
			
		||||
    mut clear_colors: ResMut<RenderTargetClearColors>,
 | 
			
		||||
) {
 | 
			
		||||
    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 = `<CommandBuffer-(0, 1, Metal)>`
 | 
			
		||||
    //        In a pass parameter
 | 
			
		||||
    //          note: command buffer = `<CommandBuffer-(0, 1, Metal)>`
 | 
			
		||||
    //        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<Time>, mut query: Query<&mut Transform, With<FirstPassCube>>) {
 | 
			
		||||
    for mut transform in query.iter_mut() {
 | 
			
		||||
        transform.rotation *= Quat::from_rotation_x(1.5 * time.delta_seconds());
 | 
			
		||||
        transform.rotation *= Quat::from_rotation_z(1.3 * time.delta_seconds());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Rotates the outer cube (main pass)
 | 
			
		||||
fn cube_rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<MainPassCube>>) {
 | 
			
		||||
    for mut transform in query.iter_mut() {
 | 
			
		||||
        transform.rotation *= Quat::from_rotation_x(1.0 * time.delta_seconds());
 | 
			
		||||
        transform.rotation *= Quat::from_rotation_y(0.7 * time.delta_seconds());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -107,6 +107,7 @@ Example | File | Description
 | 
			
		||||
`orthographic` | [`3d/orthographic.rs`](./3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look games or CAD applications)
 | 
			
		||||
`parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations
 | 
			
		||||
`pbr` | [`3d/pbr.rs`](./3d/pbr.rs) | Demonstrates use of Physically Based Rendering (PBR) properties
 | 
			
		||||
`render_to_texture` | [`3d/render_to_texture.rs`](./3d/render_to_texture.rs) | Shows how to render to a texture, useful for mirrors, UI, or exporting images
 | 
			
		||||
`shadow_caster_receiver` | [`3d/shadow_caster_receiver.rs`](./3d/shadow_caster_receiver.rs) | Demonstrates how to prevent meshes from casting/receiving shadows in a 3d scene
 | 
			
		||||
`shadow_biases` | [`3d/shadow_biases.rs`](./3d/shadow_biases.rs) | Demonstrates how shadow biases affect shadows in a 3d scene
 | 
			
		||||
`spherical_area_lights` | [`3d/spherical_area_lights.rs`](./3d/spherical_area_lights.rs) | Demonstrates how point light radius values affect light behavior.
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ use bevy::{
 | 
			
		||||
    core_pipeline::{draw_3d_graph, node, AlphaMask3d, Opaque3d, Transparent3d},
 | 
			
		||||
    prelude::*,
 | 
			
		||||
    render::{
 | 
			
		||||
        camera::{ActiveCameras, ExtractedCameraNames},
 | 
			
		||||
        camera::{ActiveCameras, ExtractedCameraNames, RenderTarget},
 | 
			
		||||
        render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue},
 | 
			
		||||
        render_phase::RenderPhase,
 | 
			
		||||
        renderer::RenderContext,
 | 
			
		||||
@ -65,7 +65,7 @@ fn create_new_window(
 | 
			
		||||
    // second window camera
 | 
			
		||||
    commands.spawn_bundle(PerspectiveCameraBundle {
 | 
			
		||||
        camera: Camera {
 | 
			
		||||
            window: window_id,
 | 
			
		||||
            target: RenderTarget::Window(window_id),
 | 
			
		||||
            name: Some(SECONDARY_CAMERA_NAME.into()),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user