Skip to content

Commit

Permalink
Add capability to render to a texture (bevyengine#3412)
Browse files Browse the repository at this point in the history
# Objective

Will fix bevyengine#3377 and bevyengine#3254

## Solution

Use an enum to represent either a `WindowId` or `Handle<Image>` in place of `Camera::window`.


Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
HackerFoo and cart committed Feb 24, 2022
1 parent ba6b74b commit 81d57e1
Show file tree
Hide file tree
Showing 12 changed files with 500 additions and 120 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_core_pipeline/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

35 changes: 26 additions & 9 deletions crates/bevy_core_pipeline/src/clear_pass.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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,
},
}],
Expand Down
33 changes: 30 additions & 3 deletions crates/bevy_core_pipeline/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand All @@ -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::{
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
90 changes: 46 additions & 44 deletions crates/bevy_pbr/src/light.rs
Original file line number Diff line number Diff line change
@@ -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},
};
Expand Down Expand Up @@ -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();

// 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),
));
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),
));
}
}
}
clusters.aabbs = aabbs;
}
clusters.aabbs = aabbs;
}
}

Expand Down
Loading

0 comments on commit 81d57e1

Please sign in to comment.