mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Add sub_camera_view
, enabling sheared projection (#15537)
# Objective - This PR fixes #12488 ## Solution - This PR adds a new property to `Camera` that emulates the functionality of the [setViewOffset()](https://threejs.org/docs/#api/en/cameras/PerspectiveCamera.setViewOffset) API in three.js. - When set, the perspective and orthographic projections will restrict the visible area of the camera to a part of the view frustum defined by `offset` and `size`. ## Testing - In the new `camera_sub_view` example, a fixed, moving and control sub view is created for both perspective and orthographic projection - Run the example with `cargo run --example camera_sub_view` - The code can be tested by adding a `SubCameraView` to a camera --- ## Showcase ![image](https://github.com/user-attachments/assets/75ac45fc-d75d-4664-8ef6-ff7865297c25) - Left Half: Perspective Projection - Right Half: Orthographic Projection - Small boxes in order: - Sub view of the left half of the full image - Sub view moving from the top left to the bottom right of the full image - Sub view of the full image (acting as a control) - Large box: No sub view <details> <summary>Shortened camera setup of `camera_sub_view` example</summary> ```rust // Main perspective Camera commands.spawn(Camera3dBundle { transform, ..default() }); // Perspective camera left half commands.spawn(Camera3dBundle { camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view camera to the left half of the full image full_size: uvec2(500, 500), offset: ivec2(0, 0), size: uvec2(250, 500), }), order: 1, ..default() }, transform, ..default() }); // Perspective camera moving commands.spawn(( Camera3dBundle { camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view camera to a fifth of the full view and // move it in another system full_size: uvec2(500, 500), offset: ivec2(0, 0), size: uvec2(100, 100), }), order: 2, ..default() }, transform, ..default() }, MovingCameraMarker, )); // Perspective camera control commands.spawn(Camera3dBundle { camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view to the full image, to ensure that it matches // the projection without sub view full_size: uvec2(450, 450), offset: ivec2(0, 0), size: uvec2(450, 450), }), order: 3, ..default() }, transform, ..default() }); // Main orthographic camera commands.spawn(Camera3dBundle { projection: OrthographicProjection { ... } .into(), camera: Camera { order: 4, ..default() }, transform, ..default() }); // Orthographic camera left half commands.spawn(Camera3dBundle { projection: OrthographicProjection { ... } .into(), camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view camera to the left half of the full image full_size: uvec2(500, 500), offset: ivec2(0, 0), size: uvec2(250, 500), }), order: 5, ..default() }, transform, ..default() }); // Orthographic camera moving commands.spawn(( Camera3dBundle { projection: OrthographicProjection { ... } .into(), camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view camera to a fifth of the full view and // move it in another system full_size: uvec2(500, 500), offset: ivec2(0, 0), size: uvec2(100, 100), }), order: 6, ..default() }, transform, ..default() }, MovingCameraMarker, )); // Orthographic camera control commands.spawn(Camera3dBundle { projection: OrthographicProjection { ... } .into(), camera: Camera { sub_camera_view: Some(SubCameraView { // Set the sub view to the full image, to ensure that it matches // the projection without sub view full_size: uvec2(450, 450), offset: ivec2(0, 0), size: uvec2(450, 450), }), order: 7, ..default() }, transform, ..default() }); ``` </details>
This commit is contained in:
parent
956d9ccbb1
commit
c323db02e0
5 changed files with 439 additions and 2 deletions
11
Cargo.toml
11
Cargo.toml
|
@ -3430,6 +3430,17 @@ description = "Demonstrates screen space reflections with water ripples"
|
|||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "camera_sub_view"
|
||||
path = "examples/3d/camera_sub_view.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.camera_sub_view]
|
||||
name = "Camera sub view"
|
||||
description = "Demonstrates using different sub view effects on a camera"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "color_grading"
|
||||
path = "examples/3d/color_grading.rs"
|
||||
|
|
|
@ -67,6 +67,54 @@ impl Default for Viewport {
|
|||
}
|
||||
}
|
||||
|
||||
/// Settings to define a camera sub view.
|
||||
///
|
||||
/// When [`Camera::sub_camera_view`] is `Some`, only the sub-section of the
|
||||
/// image defined by `size` and `offset` (relative to the `full_size` of the
|
||||
/// whole image) is projected to the cameras viewport.
|
||||
///
|
||||
/// Take the example of the following multi-monitor setup:
|
||||
/// ```css
|
||||
/// ┌───┬───┐
|
||||
/// │ A │ B │
|
||||
/// ├───┼───┤
|
||||
/// │ C │ D │
|
||||
/// └───┴───┘
|
||||
/// ```
|
||||
/// If each monitor is 1920x1080, the whole image will have a resolution of
|
||||
/// 3840x2160. For each monitor we can use a single camera with a viewport of
|
||||
/// the same size as the monitor it corresponds to. To ensure that the image is
|
||||
/// cohesive, we can use a different sub view on each camera:
|
||||
/// - Camera A: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 0,0
|
||||
/// - Camera B: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 1920,0
|
||||
/// - Camera C: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 0,1080
|
||||
/// - Camera D: `full_size` = 3840x2160, `size` = 1920x1080, `offset` =
|
||||
/// 1920,1080
|
||||
///
|
||||
/// However since only the ratio between the values is important, they could all
|
||||
/// be divided by 120 and still produce the same image. Camera D would for
|
||||
/// example have the following values:
|
||||
/// `full_size` = 32x18, `size` = 16x9, `offset` = 16,9
|
||||
#[derive(Debug, Clone, Copy, Reflect, PartialEq)]
|
||||
pub struct SubCameraView {
|
||||
/// Size of the entire camera view
|
||||
pub full_size: UVec2,
|
||||
/// Offset of the sub camera
|
||||
pub offset: Vec2,
|
||||
/// Size of the sub camera
|
||||
pub size: UVec2,
|
||||
}
|
||||
|
||||
impl Default for SubCameraView {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
full_size: UVec2::new(1, 1),
|
||||
offset: Vec2::new(0., 0.),
|
||||
size: UVec2::new(1, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the current [`RenderTarget`].
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct RenderTargetInfo {
|
||||
|
@ -86,6 +134,7 @@ pub struct ComputedCameraValues {
|
|||
target_info: Option<RenderTargetInfo>,
|
||||
// size of the `Viewport`
|
||||
old_viewport_size: Option<UVec2>,
|
||||
old_sub_camera_view: Option<SubCameraView>,
|
||||
}
|
||||
|
||||
/// How much energy a `Camera3d` absorbs from incoming light.
|
||||
|
@ -256,6 +305,8 @@ pub struct Camera {
|
|||
pub msaa_writeback: bool,
|
||||
/// The clear color operation to perform on the render target.
|
||||
pub clear_color: ClearColorConfig,
|
||||
/// If set, this camera will be a sub camera of a large view, defined by a [`SubCameraView`].
|
||||
pub sub_camera_view: Option<SubCameraView>,
|
||||
}
|
||||
|
||||
impl Default for Camera {
|
||||
|
@ -270,6 +321,7 @@ impl Default for Camera {
|
|||
hdr: false,
|
||||
msaa_writeback: true,
|
||||
clear_color: Default::default(),
|
||||
sub_camera_view: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -843,6 +895,7 @@ pub fn camera_system<T: CameraProjection + Component>(
|
|||
|| camera.is_added()
|
||||
|| camera_projection.is_changed()
|
||||
|| camera.computed.old_viewport_size != viewport_size
|
||||
|| camera.computed.old_sub_camera_view != camera.sub_camera_view
|
||||
{
|
||||
let new_computed_target_info = normalized_target.get_render_target_info(
|
||||
&windows,
|
||||
|
@ -890,7 +943,10 @@ pub fn camera_system<T: CameraProjection + Component>(
|
|||
camera.computed.target_info = new_computed_target_info;
|
||||
if let Some(size) = camera.logical_viewport_size() {
|
||||
camera_projection.update(size.x, size.y);
|
||||
camera.computed.clip_from_view = camera_projection.get_clip_from_view();
|
||||
camera.computed.clip_from_view = match &camera.sub_camera_view {
|
||||
Some(sub_view) => camera_projection.get_clip_from_view_for_sub(sub_view),
|
||||
None => camera_projection.get_clip_from_view(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -898,6 +954,10 @@ pub fn camera_system<T: CameraProjection + Component>(
|
|||
if camera.computed.old_viewport_size != viewport_size {
|
||||
camera.computed.old_viewport_size = viewport_size;
|
||||
}
|
||||
|
||||
if camera.computed.old_sub_camera_view != camera.sub_camera_view {
|
||||
camera.computed.old_sub_camera_view = camera.sub_camera_view;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use core::{
|
|||
use crate::{primitives::Frustum, view::VisibilitySystems};
|
||||
use bevy_app::{App, Plugin, PostStartup, PostUpdate};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A};
|
||||
use bevy_math::{ops, AspectRatio, Mat4, Rect, Vec2, Vec3A, Vec4};
|
||||
use bevy_reflect::{
|
||||
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
|
||||
};
|
||||
|
@ -76,6 +76,7 @@ pub struct CameraUpdateSystem;
|
|||
/// [`Camera`]: crate::camera::Camera
|
||||
pub trait CameraProjection {
|
||||
fn get_clip_from_view(&self) -> Mat4;
|
||||
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4;
|
||||
fn update(&mut self, width: f32, height: f32);
|
||||
fn far(&self) -> f32;
|
||||
fn get_frustum_corners(&self, z_near: f32, z_far: f32) -> [Vec3A; 8];
|
||||
|
@ -124,6 +125,13 @@ impl CameraProjection for Projection {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
|
||||
match self {
|
||||
Projection::Perspective(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
||||
Projection::Orthographic(projection) => projection.get_clip_from_view_for_sub(sub_view),
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, width: f32, height: f32) {
|
||||
match self {
|
||||
Projection::Perspective(projection) => projection.update(width, height),
|
||||
|
@ -189,6 +197,45 @@ impl CameraProjection for PerspectiveProjection {
|
|||
Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near)
|
||||
}
|
||||
|
||||
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
|
||||
let full_width = sub_view.full_size.x as f32;
|
||||
let full_height = sub_view.full_size.y as f32;
|
||||
let sub_width = sub_view.size.x as f32;
|
||||
let sub_height = sub_view.size.y as f32;
|
||||
let offset_x = sub_view.offset.x;
|
||||
// Y-axis increases from top to bottom
|
||||
let offset_y = full_height - (sub_view.offset.y + sub_height);
|
||||
|
||||
// Original frustum parameters
|
||||
let top = self.near * ops::tan(0.5 * self.fov);
|
||||
let bottom = -top;
|
||||
let right = top * self.aspect_ratio;
|
||||
let left = -right;
|
||||
|
||||
// Calculate scaling factors
|
||||
let width = right - left;
|
||||
let height = top - bottom;
|
||||
|
||||
// Calculate the new frustum parameters
|
||||
let left_prime = left + (width * offset_x) / full_width;
|
||||
let right_prime = left + (width * (offset_x + sub_width)) / full_width;
|
||||
let bottom_prime = bottom + (height * offset_y) / full_height;
|
||||
let top_prime = bottom + (height * (offset_y + sub_height)) / full_height;
|
||||
|
||||
// Compute the new projection matrix
|
||||
let x = (2.0 * self.near) / (right_prime - left_prime);
|
||||
let y = (2.0 * self.near) / (top_prime - bottom_prime);
|
||||
let a = (right_prime + left_prime) / (right_prime - left_prime);
|
||||
let b = (top_prime + bottom_prime) / (top_prime - bottom_prime);
|
||||
|
||||
Mat4::from_cols(
|
||||
Vec4::new(x, 0.0, 0.0, 0.0),
|
||||
Vec4::new(0.0, y, 0.0, 0.0),
|
||||
Vec4::new(a, b, 0.0, -1.0),
|
||||
Vec4::new(0.0, 0.0, self.near, 0.0),
|
||||
)
|
||||
}
|
||||
|
||||
fn update(&mut self, width: f32, height: f32) {
|
||||
self.aspect_ratio = AspectRatio::try_new(width, height)
|
||||
.expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values")
|
||||
|
@ -395,6 +442,42 @@ impl CameraProjection for OrthographicProjection {
|
|||
)
|
||||
}
|
||||
|
||||
fn get_clip_from_view_for_sub(&self, sub_view: &super::SubCameraView) -> Mat4 {
|
||||
let full_width = sub_view.full_size.x as f32;
|
||||
let full_height = sub_view.full_size.y as f32;
|
||||
let offset_x = sub_view.offset.x;
|
||||
let offset_y = sub_view.offset.y;
|
||||
let sub_width = sub_view.size.x as f32;
|
||||
let sub_height = sub_view.size.y as f32;
|
||||
|
||||
// Orthographic projection parameters
|
||||
let top = self.area.max.y;
|
||||
let bottom = self.area.min.y;
|
||||
let right = self.area.max.x;
|
||||
let left = self.area.min.x;
|
||||
|
||||
// Calculate scaling factors
|
||||
let scale_w = (right - left) / full_width;
|
||||
let scale_h = (top - bottom) / full_height;
|
||||
|
||||
// Calculate the new orthographic bounds
|
||||
let left_prime = left + scale_w * offset_x;
|
||||
let right_prime = left_prime + scale_w * sub_width;
|
||||
let top_prime = top - scale_h * offset_y;
|
||||
let bottom_prime = top_prime - scale_h * sub_height;
|
||||
|
||||
Mat4::orthographic_rh(
|
||||
left_prime,
|
||||
right_prime,
|
||||
bottom_prime,
|
||||
top_prime,
|
||||
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
|
||||
// This is for interoperability with pipelines using infinite reverse perspective projections.
|
||||
self.far,
|
||||
self.near,
|
||||
)
|
||||
}
|
||||
|
||||
fn update(&mut self, width: f32, height: f32) {
|
||||
let (projection_width, projection_height) = match self.scaling_mode {
|
||||
ScalingMode::WindowSize(pixel_scale) => (width / pixel_scale, height / pixel_scale),
|
||||
|
|
282
examples/3d/camera_sub_view.rs
Normal file
282
examples/3d/camera_sub_view.rs
Normal file
|
@ -0,0 +1,282 @@
|
|||
//! Demonstrates different sub view effects.
|
||||
//!
|
||||
//! A sub view is essentially a smaller section of a larger viewport. Some use
|
||||
//! cases include:
|
||||
//! - Split one image across multiple cameras, for use in a multimonitor setups
|
||||
//! - Magnify a section of the image, by rendering a small sub view in another
|
||||
//! camera
|
||||
//! - Rapidly change the sub view offset to get a screen shake effect
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::camera::{ScalingMode, SubCameraView, Viewport},
|
||||
};
|
||||
|
||||
const PADDING: u32 = 10;
|
||||
const SMALL_SIZE: u32 = 100;
|
||||
const LARGE_SIZE: u32 = 450;
|
||||
|
||||
const WINDOW_HEIGHT: f32 = (LARGE_SIZE + PADDING * 3 + SMALL_SIZE) as f32;
|
||||
const WINDOW_WIDTH: f32 = (LARGE_SIZE * 2 + PADDING * 3) as f32;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
// Fix window size to avoid issues with viewports on resizing
|
||||
resize_constraints: WindowResizeConstraints {
|
||||
min_width: WINDOW_WIDTH,
|
||||
min_height: WINDOW_HEIGHT,
|
||||
max_width: WINDOW_WIDTH,
|
||||
max_height: WINDOW_HEIGHT,
|
||||
},
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, move_camera_view)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Debug, Component)]
|
||||
struct MovingCameraMarker;
|
||||
|
||||
/// Set up a simple 3D scene
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let transform = Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y);
|
||||
|
||||
// Plane
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)),
|
||||
material: materials.add(Color::srgb(0.3, 0.5, 0.3)),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Cube
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Cuboid::default()),
|
||||
material: materials.add(Color::srgb(0.8, 0.7, 0.6)),
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Light
|
||||
commands.spawn((
|
||||
PointLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
));
|
||||
|
||||
// Main perspective Camera
|
||||
commands.spawn(Camera3dBundle {
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(LARGE_SIZE, LARGE_SIZE),
|
||||
physical_position: UVec2::new(PADDING, PADDING * 2 + SMALL_SIZE),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Perspective camera left half
|
||||
commands.spawn(Camera3dBundle {
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view camera to the right half of the full image
|
||||
//
|
||||
// The values of `full_size` and `size` do not have to be the
|
||||
// exact values of your physical viewport. The important part is
|
||||
// the ratio between them.
|
||||
full_size: UVec2::new(10, 10),
|
||||
// The `offset` is also relative to the values in `full_size`
|
||||
// and `size`
|
||||
offset: Vec2::new(5.0, 0.0),
|
||||
size: UVec2::new(5, 10),
|
||||
}),
|
||||
order: 1,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Perspective camera moving
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 2 + SMALL_SIZE, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view camera to a fifth of the full view and
|
||||
// move it in another system
|
||||
full_size: UVec2::new(500, 500),
|
||||
offset: Vec2::ZERO,
|
||||
size: UVec2::new(100, 100),
|
||||
}),
|
||||
order: 2,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
},
|
||||
MovingCameraMarker,
|
||||
));
|
||||
|
||||
// Perspective camera control
|
||||
commands.spawn(Camera3dBundle {
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 3 + SMALL_SIZE * 2, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view to the full image, to ensure that it matches
|
||||
// the projection without sub view
|
||||
full_size: UVec2::new(450, 450),
|
||||
offset: Vec2::ZERO,
|
||||
size: UVec2::new(450, 450),
|
||||
}),
|
||||
order: 3,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Main orthographic camera
|
||||
commands.spawn(Camera3dBundle {
|
||||
projection: OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedVertical(6.0),
|
||||
..OrthographicProjection::default_3d()
|
||||
}
|
||||
.into(),
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(LARGE_SIZE, LARGE_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 2 + LARGE_SIZE, PADDING * 2 + SMALL_SIZE),
|
||||
..default()
|
||||
}),
|
||||
order: 4,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Orthographic camera left half
|
||||
commands.spawn(Camera3dBundle {
|
||||
projection: OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedVertical(6.0),
|
||||
..OrthographicProjection::default_3d()
|
||||
}
|
||||
.into(),
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 5 + SMALL_SIZE * 4, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view camera to the left half of the full image.
|
||||
//
|
||||
// The values of `full_size` and `size` do not have to be the
|
||||
// exact values of your physical viewport. The important part is
|
||||
// the ratio between them.
|
||||
full_size: UVec2::new(2, 2),
|
||||
offset: Vec2::ZERO,
|
||||
size: UVec2::new(1, 2),
|
||||
}),
|
||||
order: 5,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Orthographic camera moving
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
projection: OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedVertical(6.0),
|
||||
..OrthographicProjection::default_3d()
|
||||
}
|
||||
.into(),
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 6 + SMALL_SIZE * 5, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view camera to a fifth of the full view and
|
||||
// move it in another system
|
||||
full_size: UVec2::new(500, 500),
|
||||
offset: Vec2::ZERO,
|
||||
size: UVec2::new(100, 100),
|
||||
}),
|
||||
order: 6,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
},
|
||||
MovingCameraMarker,
|
||||
));
|
||||
|
||||
// Orthographic camera control
|
||||
commands.spawn(Camera3dBundle {
|
||||
projection: OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedVertical(6.0),
|
||||
..OrthographicProjection::default_3d()
|
||||
}
|
||||
.into(),
|
||||
camera: Camera {
|
||||
viewport: Option::from(Viewport {
|
||||
physical_size: UVec2::new(SMALL_SIZE, SMALL_SIZE),
|
||||
physical_position: UVec2::new(PADDING * 7 + SMALL_SIZE * 6, PADDING),
|
||||
..default()
|
||||
}),
|
||||
sub_camera_view: Some(SubCameraView {
|
||||
// Set the sub view to the full image, to ensure that it matches
|
||||
// the projection without sub view
|
||||
full_size: UVec2::new(450, 450),
|
||||
offset: Vec2::ZERO,
|
||||
size: UVec2::new(450, 450),
|
||||
}),
|
||||
order: 7,
|
||||
..default()
|
||||
},
|
||||
transform,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
fn move_camera_view(
|
||||
mut movable_camera_query: Query<&mut Camera, With<MovingCameraMarker>>,
|
||||
time: Res<Time>,
|
||||
) {
|
||||
for mut camera in movable_camera_query.iter_mut() {
|
||||
if let Some(sub_view) = &mut camera.sub_camera_view {
|
||||
sub_view.offset.x = (time.elapsed_seconds() * 150.) % 450.0 - 50.0;
|
||||
sub_view.offset.y = sub_view.offset.x;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -139,6 +139,7 @@ Example | Description
|
|||
[Auto Exposure](../examples/3d/auto_exposure.rs) | A scene showcasing auto exposure
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[Built-in postprocessing](../examples/3d/post_processing.rs) | Demonstrates the built-in postprocessing features
|
||||
[Camera sub view](../examples/3d/camera_sub_view.rs) | Demonstrates using different sub view effects on a camera
|
||||
[Clearcoat](../examples/3d/clearcoat.rs) | Demonstrates the clearcoat PBR feature
|
||||
[Color grading](../examples/3d/color_grading.rs) | Demonstrates color grading
|
||||
[Deferred Rendering](../examples/3d/deferred_rendering.rs) | Renders meshes with both forward and deferred pipelines
|
||||
|
|
Loading…
Reference in a new issue