Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add optional viewport to camera #3626

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ path = "examples/2d/texture_atlas.rs"
name = "3d_scene"
path = "examples/3d/3d_scene.rs"

[[example]]
name = "split_screen_viewport"
path = "examples/3d/split_screen_viewport.rs"

[[example]]
name = "lighting"
path = "examples/3d/lighting.rs"
Expand Down
3 changes: 2 additions & 1 deletion 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_math = { path = "../bevy_math", version = "0.6.0" }
wgpu = "0.12"
26 changes: 22 additions & 4 deletions crates/bevy_core_pipeline/src/main_pass_2d.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::Transparent2d;
use bevy_ecs::prelude::*;
use bevy_math::Vec2;
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
Expand All @@ -9,8 +10,11 @@ use bevy_render::{
};

pub struct MainPass2dNode {
query:
QueryState<(&'static RenderPhase<Transparent2d>, &'static ViewTarget), With<ExtractedView>>,
query: QueryState<(
&'static RenderPhase<Transparent2d>,
&'static ViewTarget,
&'static ExtractedView,
)>,
}

impl MainPass2dNode {
Expand Down Expand Up @@ -39,7 +43,7 @@ impl Node for MainPass2dNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (transparent_phase, target) = self
let (transparent_phase, target, view) = self
.query
.get_manual(world, view_entity)
.expect("view entity should exist");
Expand All @@ -57,10 +61,24 @@ impl Node for MainPass2dNode {
.get_resource::<DrawFunctions<Transparent2d>>()
.unwrap();

let render_pass = render_context
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);

if let Some(viewport) = &view.viewport {
let target_size = Vec2::new(view.width as f32, view.height as f32);
let pos = viewport.scaled_pos(target_size);
let size = viewport.scaled_size(target_size);
render_pass.set_viewport(
pos.x,
pos.y,
size.x,
size.y,
viewport.min_depth,
viewport.max_depth,
);
}

let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &transparent_phase.items {
Expand Down
46 changes: 32 additions & 14 deletions crates/bevy_core_pipeline/src/main_pass_3d.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_math::Vec2;
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
Expand All @@ -9,16 +10,14 @@ use bevy_render::{
};

pub struct MainPass3dNode {
query: QueryState<
(
&'static RenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
),
With<ExtractedView>,
>,
query: QueryState<(
&'static RenderPhase<Opaque3d>,
&'static RenderPhase<AlphaMask3d>,
&'static RenderPhase<Transparent3d>,
&'static ViewTarget,
&'static ViewDepthTexture,
&'static ExtractedView,
)>,
}

impl MainPass3dNode {
Expand Down Expand Up @@ -47,12 +46,28 @@ impl Node for MainPass3dNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth) =
let (opaque_phase, alpha_mask_phase, transparent_phase, target, depth, view) =
match self.query.get_manual(world, view_entity) {
Ok(query) => query,
Err(_) => return Ok(()), // No window
};

let set_viewport = |render_pass: &mut wgpu::RenderPass| {
if let Some(viewport) = &view.viewport {
let target_size = Vec2::new(view.width as f32, view.height as f32);
let pos = viewport.scaled_pos(target_size);
let size = viewport.scaled_size(target_size);
render_pass.set_viewport(
pos.x,
pos.y,
size.x,
size.y,
viewport.min_depth,
viewport.max_depth,
);
}
};

{
// Run the opaque pass, sorted front-to-back
// NOTE: Scoped to drop the mutable borrow of render_context
Expand All @@ -77,9 +92,10 @@ impl Node for MainPass3dNode {

let draw_functions = world.get_resource::<DrawFunctions<Opaque3d>>().unwrap();

let render_pass = render_context
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
set_viewport(&mut render_pass);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &opaque_phase.items {
Expand Down Expand Up @@ -111,9 +127,10 @@ impl Node for MainPass3dNode {

let draw_functions = world.get_resource::<DrawFunctions<AlphaMask3d>>().unwrap();

let render_pass = render_context
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
set_viewport(&mut render_pass);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &alpha_mask_phase.items {
Expand Down Expand Up @@ -149,9 +166,10 @@ impl Node for MainPass3dNode {
.get_resource::<DrawFunctions<Transparent3d>>()
.unwrap();

let render_pass = render_context
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
set_viewport(&mut render_pass);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &transparent_phase.items {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,7 @@ pub fn prepare_lights(
projection: cube_face_projection,
near: POINT_LIGHT_NEAR_Z,
far: light.range,
viewport: None,
},
RenderPhase::<Shadow>::default(),
LightEntity::Point {
Expand Down Expand Up @@ -867,6 +868,7 @@ pub fn prepare_lights(
projection,
near: light.near,
far: light.far,
viewport: None,
},
RenderPhase::<Shadow>::default(),
LightEntity::Directional { light_entity },
Expand Down
67 changes: 63 additions & 4 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
prelude::{DetectChanges, QueryState},
query::Added,
prelude::{Changed, DetectChanges, QueryState},
reflect::ReflectComponent,
system::{QuerySet, Res},
};
Expand All @@ -25,6 +24,60 @@ pub struct Camera {
pub depth_calculation: DepthCalculation,
pub near: f32,
pub far: f32,
pub viewport: Option<Viewport>,
}

#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub struct Viewport {
pub x: f32,
pub y: f32,
pub w: f32,
pub h: f32,
/// In range `0..=1`
pub min_depth: f32,
/// In range `0..=1`
pub max_depth: f32,

/// Whether `x`, `y`, `w` and `h` should be in range `0..=1` or in pixels
pub scaling_mode: ViewportScalingMode,
}
impl Default for Viewport {
fn default() -> Self {
Self {
x: 0.0,
y: 0.0,
w: 1.0,
h: 1.0,
min_depth: 0.0,
max_depth: 1.0,
scaling_mode: ViewportScalingMode::Normalized,
}
}
}

impl Viewport {
pub fn scaled_pos(&self, target_size: Vec2) -> Vec2 {
let pos = Vec2::new(self.x, self.y);
match self.scaling_mode {
ViewportScalingMode::Normalized => pos * target_size,
ViewportScalingMode::Pixels => pos,
}
}
pub fn scaled_size(&self, target_size: Vec2) -> Vec2 {
let size = Vec2::new(self.w, self.h);
match self.scaling_mode {
ViewportScalingMode::Normalized => size * target_size,
ViewportScalingMode::Pixels => size,
}
}
}

#[derive(Debug, Clone, Reflect, Serialize, Deserialize)]
pub enum ViewportScalingMode {
/// `x`, `y`, `w` and `h` are in `0..=1`
Normalized,
/// Pixel units
Pixels,
}

#[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
Expand Down Expand Up @@ -77,7 +130,7 @@ pub fn camera_system<T: CameraProjection + Component>(
windows: Res<Windows>,
mut queries: QuerySet<(
QueryState<(Entity, &mut Camera, &mut T)>,
QueryState<Entity, Added<Camera>>,
QueryState<Entity, Changed<Camera>>,
)>,
) {
let mut changed_window_ids = Vec::new();
Expand Down Expand Up @@ -111,7 +164,13 @@ pub fn camera_system<T: CameraProjection + Component>(
|| added_cameras.contains(&entity)
|| camera_projection.is_changed()
{
camera_projection.update(window.width(), window.height());
let target_size = Vec2::new(window.width(), window.height());
let size = camera
.viewport
.as_ref()
.map(|viewport| viewport.scaled_size(target_size))
.unwrap_or(target_size);
camera_projection.update(size.x, size.y);
camera.projection_matrix = camera_projection.get_projection_matrix();
camera.depth_calculation = camera_projection.depth_calculation();
}
Expand Down
10 changes: 8 additions & 2 deletions crates/bevy_render/src/camera/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ impl Plugin for CameraPlugin {
.add_system_to_stage(CoreStage::PostUpdate, crate::camera::active_cameras_system)
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<OrthographicProjection>,
crate::camera::camera_system::<OrthographicProjection>
.label(UpdateCameraProjectionSystem),
)
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<PerspectiveProjection>,
crate::camera::camera_system::<PerspectiveProjection>
.label(UpdateCameraProjectionSystem),
);
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
Expand All @@ -61,6 +63,9 @@ impl Plugin for CameraPlugin {
}
}

#[derive(SystemLabel, Debug, PartialEq, Eq, Clone, Hash)]
pub struct UpdateCameraProjectionSystem;

#[derive(Default)]
pub struct ExtractedCameraNames {
pub entities: HashMap<String, Entity>,
Expand Down Expand Up @@ -98,6 +103,7 @@ fn extract_cameras(
height: window.physical_height().max(1),
near: camera.near,
far: camera.far,
viewport: camera.viewport.clone(),
},
visible_entities.clone(),
));
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use wgpu::{
pub use window::*;

use crate::{
camera::{ExtractedCamera, ExtractedCameraNames},
camera::{ExtractedCamera, ExtractedCameraNames, Viewport},
render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView},
renderer::{RenderDevice, RenderQueue},
texture::{BevyDefault, TextureCache},
Expand Down Expand Up @@ -81,6 +81,7 @@ pub struct ExtractedView {
pub height: u32,
pub near: f32,
pub far: f32,
pub viewport: Option<Viewport>,
}

#[derive(Clone, AsStd140)]
Expand Down Expand Up @@ -148,6 +149,7 @@ fn prepare_view_uniforms(
let projection = camera.projection;
let view = camera.transform.compute_matrix();
let inverse_view = view.inverse();

let view_uniforms = ViewUniformOffset {
offset: view_uniforms.uniforms.push(ViewUniform {
view_proj: projection * inverse_view,
Expand Down
26 changes: 22 additions & 4 deletions crates/bevy_ui/src/render/render_pass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::Vec2;
use bevy_render::{
camera::ExtractedCameraNames,
render_graph::*,
Expand Down Expand Up @@ -35,8 +36,11 @@ impl bevy_render::render_graph::Node for UiPassDriverNode {
}

pub struct UiPassNode {
query:
QueryState<(&'static RenderPhase<TransparentUi>, &'static ViewTarget), With<ExtractedView>>,
query: QueryState<(
&'static RenderPhase<TransparentUi>,
&'static ViewTarget,
&'static ExtractedView,
)>,
}

impl UiPassNode {
Expand Down Expand Up @@ -65,7 +69,7 @@ impl bevy_render::render_graph::Node for UiPassNode {
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (transparent_phase, target) = self
let (transparent_phase, target, view) = self
.query
.get_manual(world, view_entity)
.expect("view entity should exist");
Expand All @@ -86,10 +90,24 @@ impl bevy_render::render_graph::Node for UiPassNode {
.get_resource::<DrawFunctions<TransparentUi>>()
.unwrap();

let render_pass = render_context
let mut render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);

if let Some(viewport) = &view.viewport {
let target_size = Vec2::new(view.width as f32, view.height as f32);
let pos = viewport.scaled_pos(target_size);
let size = viewport.scaled_size(target_size);
render_pass.set_viewport(
pos.x,
pos.y,
size.x,
size.y,
viewport.min_depth,
viewport.max_depth,
);
}

let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
for item in &transparent_phase.items {
Expand Down
Loading