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  - 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
					
				
							
								
								
									
										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 New Issue
	
	Block a user
	 m-edlund
						m-edlund