bevy_camera/camera.rs
1use crate::primitives::Frustum;
2
3use super::{
4 visibility::{Visibility, VisibleEntities},
5 ClearColorConfig, MsaaWriteback,
6};
7use bevy_asset::Handle;
8use bevy_derive::Deref;
9use bevy_ecs::{
10 component::Component, entity::Entity, reflect::ReflectComponent, template::FromTemplate,
11};
12use bevy_image::Image;
13use bevy_math::{ops, Dir3, FloatOrd, Mat4, Ray3d, Rect, URect, UVec2, Vec2, Vec3, Vec3A};
14use bevy_reflect::prelude::*;
15use bevy_transform::components::{GlobalTransform, Transform};
16use bevy_window::{NormalizedWindowRef, WindowRef};
17use core::ops::Range;
18use derive_more::derive::From;
19use thiserror::Error;
20use wgpu_types::{BlendState, TextureUsages};
21
22/// Render viewport configuration for the [`Camera`] component.
23///
24/// The viewport defines the area on the render target to which the camera renders its image.
25/// You can overlay multiple cameras in a single window using viewports to create effects like
26/// split screen, minimaps, and character viewers.
27///
28/// <div class="warning">
29///
30/// Note that the physical position is in actual screen coordinates and not virtual pixels for window targets.
31/// You should use the scaling factor reported by the window, which on some OS's defaults to a value other than 1.
32/// Please see the example code (which assumes a single camera and window)
33///
34/// ```no_run
35/// # use bevy_camera::{Camera, Projection, Viewport};
36/// # use bevy_transform::prelude::Transform;
37/// # use bevy_ecs::prelude::*;
38/// # use bevy_math::UVec2;
39/// # use bevy_window::Window;
40/// # use bevy_utils::default;
41///
42/// fn update_viewport(
43/// mut camera_query: Query<(&mut Camera, &mut Transform, &mut Projection)>,
44/// windows: Query<&Window>
45/// ) {
46/// let Ok((mut camera, _, _)) = camera_query.single_mut() else { return; };
47///
48/// let window = windows.single().expect("Window not found");
49/// let scale = window.resolution.scale_factor();
50///
51/// camera.viewport = Some(Viewport {
52/// physical_position: (UVec2::new(10, 10).as_vec2() * scale).as_uvec2(),
53/// physical_size: (UVec2::new(100, 100).as_vec2() * scale).as_uvec2(),
54/// ..default()
55/// });
56/// }
57/// ```
58///
59/// </div>
60#[derive(Reflect, Debug, Clone)]
61#[reflect(Default, Clone)]
62pub struct Viewport {
63 /// The physical position to render this viewport to within the [`RenderTarget`] of this [`Camera`].
64 /// (0,0) corresponds to the top-left corner
65 pub physical_position: UVec2,
66 /// The physical size of the viewport rectangle to render to within the [`RenderTarget`] of this [`Camera`].
67 /// The origin of the rectangle is in the top-left corner.
68 pub physical_size: UVec2,
69 /// The minimum and maximum depth to render (on a scale from 0.0 to 1.0).
70 pub depth: Range<f32>,
71}
72
73impl Default for Viewport {
74 fn default() -> Self {
75 Self {
76 physical_position: Default::default(),
77 physical_size: UVec2::new(1, 1),
78 depth: 0.0..1.0,
79 }
80 }
81}
82
83impl Viewport {
84 /// Cut the viewport rectangle so that it lies inside a rectangle of the
85 /// given size.
86 ///
87 /// If either of the viewport's position coordinates lies outside the given
88 /// dimensions, it will be moved just inside first. If either of the given
89 /// dimensions is zero, the position and size of the viewport rectangle will
90 /// both be set to zero in that dimension.
91 pub fn clamp_to_size(&mut self, size: UVec2) {
92 // If the origin of the viewport rect is outside, then adjust so that
93 // it's just barely inside. Then, cut off the part that is outside.
94 if self.physical_size.x + self.physical_position.x > size.x {
95 if self.physical_position.x < size.x {
96 self.physical_size.x = size.x - self.physical_position.x;
97 } else if size.x > 0 {
98 self.physical_position.x = size.x - 1;
99 self.physical_size.x = 1;
100 } else {
101 self.physical_position.x = 0;
102 self.physical_size.x = 0;
103 }
104 }
105 if self.physical_size.y + self.physical_position.y > size.y {
106 if self.physical_position.y < size.y {
107 self.physical_size.y = size.y - self.physical_position.y;
108 } else if size.y > 0 {
109 self.physical_position.y = size.y - 1;
110 self.physical_size.y = 1;
111 } else {
112 self.physical_position.y = 0;
113 self.physical_size.y = 0;
114 }
115 }
116 }
117
118 pub fn from_viewport_and_override(
119 viewport: Option<&Self>,
120 main_pass_resolution_override: Option<&MainPassResolutionOverride>,
121 ) -> Option<Self> {
122 if let Some(override_size) = main_pass_resolution_override {
123 let mut vp = viewport.map_or_else(Self::default, Self::clone);
124 vp.physical_size = **override_size;
125 Some(vp)
126 } else {
127 viewport.cloned()
128 }
129 }
130}
131
132/// Override the resolution a 3d camera's main pass is rendered at.
133///
134/// Does not affect post processing.
135///
136/// ## Usage
137///
138/// * Insert this component on a 3d camera entity in the render world.
139/// * The resolution override must be smaller than the camera's viewport size.
140/// * The resolution override is specified in physical pixels.
141/// * In shaders, use `View::main_pass_viewport` instead of `View::viewport`.
142#[derive(Component, Reflect, Deref, Debug)]
143#[reflect(Component)]
144pub struct MainPassResolutionOverride(pub UVec2);
145
146/// Settings to define a camera sub view.
147///
148/// When [`Camera::sub_camera_view`] is `Some`, only the sub-section of the
149/// image defined by `size` and `offset` (relative to the `full_size` of the
150/// whole image) is projected to the cameras viewport.
151///
152/// Take the example of the following multi-monitor setup:
153/// ```css
154/// ┌───┬───┐
155/// │ A │ B │
156/// ├───┼───┤
157/// │ C │ D │
158/// └───┴───┘
159/// ```
160/// If each monitor is 1920x1080, the whole image will have a resolution of
161/// 3840x2160. For each monitor we can use a single camera with a viewport of
162/// the same size as the monitor it corresponds to. To ensure that the image is
163/// cohesive, we can use a different sub view on each camera:
164/// - Camera A: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 0,0
165/// - Camera B: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 1920,0
166/// - Camera C: `full_size` = 3840x2160, `size` = 1920x1080, `offset` = 0,1080
167/// - Camera D: `full_size` = 3840x2160, `size` = 1920x1080, `offset` =
168/// 1920,1080
169///
170/// However since only the ratio between the values is important, they could all
171/// be divided by 120 and still produce the same image. Camera D would for
172/// example have the following values:
173/// `full_size` = 32x18, `size` = 16x9, `offset` = 16,9
174#[derive(Debug, Clone, Copy, Reflect, PartialEq)]
175#[reflect(Clone, PartialEq, Default)]
176pub struct SubCameraView {
177 /// Size of the entire camera view
178 pub full_size: UVec2,
179 /// Offset of the sub camera
180 pub offset: Vec2,
181 /// Size of the sub camera
182 pub size: UVec2,
183}
184
185impl Default for SubCameraView {
186 fn default() -> Self {
187 Self {
188 full_size: UVec2::new(1, 1),
189 offset: Vec2::new(0., 0.),
190 size: UVec2::new(1, 1),
191 }
192 }
193}
194
195/// Information about the current [`RenderTarget`].
196#[derive(Debug, Reflect, Clone)]
197pub struct RenderTargetInfo {
198 /// The physical size of this render target (in physical pixels, ignoring scale factor).
199 pub physical_size: UVec2,
200 /// The scale factor of this render target.
201 ///
202 /// When rendering to a window, typically it is a value greater or equal than 1.0,
203 /// representing the ratio between the size of the window in physical pixels and the logical size of the window.
204 pub scale_factor: f32,
205}
206
207impl Default for RenderTargetInfo {
208 fn default() -> Self {
209 Self {
210 physical_size: Default::default(),
211 scale_factor: 1.,
212 }
213 }
214}
215
216/// Holds internally computed [`Camera`] values.
217#[derive(Default, Debug, Reflect, Clone)]
218pub struct ComputedCameraValues {
219 pub clip_from_view: Mat4,
220 pub target_info: Option<RenderTargetInfo>,
221 // size of the `Viewport`
222 pub old_viewport_size: Option<UVec2>,
223 pub old_sub_camera_view: Option<SubCameraView>,
224}
225
226/// How much energy a [`Camera3d`](crate::Camera3d) absorbs from incoming light.
227///
228/// <https://en.wikipedia.org/wiki/Exposure_(photography)>
229#[derive(Component, Clone, Copy, Reflect)]
230#[reflect(opaque)]
231#[reflect(Component, Default, Clone)]
232pub struct Exposure {
233 /// <https://en.wikipedia.org/wiki/Exposure_value#Tabulated_exposure_values>
234 pub ev100: f32,
235}
236
237impl Exposure {
238 pub const SUNLIGHT: Self = Self {
239 ev100: Self::EV100_SUNLIGHT,
240 };
241 pub const OVERCAST: Self = Self {
242 ev100: Self::EV100_OVERCAST,
243 };
244 pub const INDOOR: Self = Self {
245 ev100: Self::EV100_INDOOR,
246 };
247 /// This value was calibrated to match Blender's implicit/default exposure as closely as possible.
248 /// It also happens to be a reasonable default.
249 ///
250 /// See <https://github.com/bevyengine/bevy/issues/11577> for details.
251 pub const BLENDER: Self = Self {
252 ev100: Self::EV100_BLENDER,
253 };
254
255 pub const EV100_SUNLIGHT: f32 = 15.0;
256 pub const EV100_OVERCAST: f32 = 12.0;
257 pub const EV100_INDOOR: f32 = 7.0;
258
259 /// This value was calibrated to match Blender's implicit/default exposure as closely as possible.
260 /// It also happens to be a reasonable default.
261 ///
262 /// See <https://github.com/bevyengine/bevy/issues/11577> for details.
263 pub const EV100_BLENDER: f32 = 9.7;
264
265 pub fn from_physical_camera(physical_camera_parameters: PhysicalCameraParameters) -> Self {
266 Self {
267 ev100: physical_camera_parameters.ev100(),
268 }
269 }
270
271 /// Converts EV100 values to exposure values.
272 /// <https://google.github.io/filament/Filament.md.html#imagingpipeline/physicallybasedcamera/exposure>
273 #[inline]
274 pub fn exposure(&self) -> f32 {
275 ops::exp2(-self.ev100) / 1.2
276 }
277}
278
279impl Default for Exposure {
280 fn default() -> Self {
281 Self::BLENDER
282 }
283}
284
285/// Parameters based on physical camera characteristics for calculating EV100
286/// values for use with [`Exposure`]. This is also used for depth of field.
287#[derive(Clone, Copy)]
288pub struct PhysicalCameraParameters {
289 /// <https://en.wikipedia.org/wiki/F-number>
290 pub aperture_f_stops: f32,
291 /// <https://en.wikipedia.org/wiki/Shutter_speed>
292 pub shutter_speed_s: f32,
293 /// <https://en.wikipedia.org/wiki/Film_speed>
294 pub sensitivity_iso: f32,
295 /// The height of the [image sensor format] in meters.
296 ///
297 /// Focal length is derived from the FOV and this value. The default is
298 /// 18.66mm, matching the [Super 35] format, which is popular in cinema.
299 ///
300 /// [image sensor format]: https://en.wikipedia.org/wiki/Image_sensor_format
301 ///
302 /// [Super 35]: https://en.wikipedia.org/wiki/Super_35
303 pub sensor_height: f32,
304}
305
306impl PhysicalCameraParameters {
307 /// Calculate the [EV100](https://en.wikipedia.org/wiki/Exposure_value).
308 pub fn ev100(&self) -> f32 {
309 ops::log2(
310 self.aperture_f_stops * self.aperture_f_stops * 100.0
311 / (self.shutter_speed_s * self.sensitivity_iso),
312 )
313 }
314}
315
316impl Default for PhysicalCameraParameters {
317 fn default() -> Self {
318 Self {
319 aperture_f_stops: 1.0,
320 shutter_speed_s: 1.0 / 125.0,
321 sensitivity_iso: 100.0,
322 sensor_height: 0.01866,
323 }
324 }
325}
326
327/// Error returned when a conversion between world-space and viewport-space coordinates fails.
328///
329/// See [`world_to_viewport`][Camera::world_to_viewport] and [`viewport_to_world`][Camera::viewport_to_world].
330#[derive(Debug, Eq, PartialEq, Copy, Clone, Error)]
331pub enum ViewportConversionError {
332 /// The pre-computed size of the viewport was not available.
333 ///
334 /// This may be because the `Camera` was just created and `camera_system` has not been executed
335 /// yet, or because the [`RenderTarget`] is misconfigured in one of the following ways:
336 /// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
337 /// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
338 /// - it references an [`Image`](RenderTarget::Image) that doesn't exist (invalid handle),
339 /// - it references a [`TextureView`](RenderTarget::TextureView) that doesn't exist (invalid handle).
340 #[error("pre-computed size of viewport not available")]
341 NoViewportSize,
342 /// The computed coordinate was beyond the `Camera`'s near plane.
343 ///
344 /// Only applicable when converting from world-space to viewport-space.
345 #[error("computed coordinate beyond `Camera`'s near plane")]
346 PastNearPlane,
347 /// The computed coordinate was beyond the `Camera`'s far plane.
348 ///
349 /// Only applicable when converting from world-space to viewport-space.
350 #[error("computed coordinate beyond `Camera`'s far plane")]
351 PastFarPlane,
352 /// The Normalized Device Coordinates could not be computed because the `camera_transform`, the
353 /// `world_position`, or the projection matrix defined by [`Projection`](super::projection::Projection)
354 /// contained `NAN` (see [`world_to_ndc`][Camera::world_to_ndc] and [`ndc_to_world`][Camera::ndc_to_world]).
355 #[error("found NaN while computing NDC")]
356 InvalidData,
357}
358
359/// The defining [`Component`] for camera entities,
360/// storing information about how and what to render through this camera.
361///
362/// The [`Camera`] component is added to an entity to define the properties of the viewpoint from
363/// which rendering occurs. It defines the position of the view to render, the projection method
364/// to transform the 3D objects into a 2D image, as well as the render target into which that image
365/// is produced.
366///
367/// Note that a [`Camera`] needs a `CameraRenderGraph` to render anything.
368/// This is typically provided by adding a [`Camera2d`] or [`Camera3d`] component,
369/// but custom render graphs can also be defined. Inserting a [`Camera`] with no render
370/// graph will emit an error at runtime.
371///
372/// [`Camera2d`]: crate::Camera2d
373/// [`Camera3d`]: crate::Camera3d
374#[derive(Component, Debug, Reflect, Clone)]
375#[reflect(Component, Default, Debug, Clone)]
376#[require(
377 Frustum,
378 CameraMainTextureUsages,
379 VisibleEntities,
380 Transform,
381 Visibility,
382 RenderTarget
383)]
384pub struct Camera {
385 /// If set, this camera will render to the given [`Viewport`] rectangle within the configured [`RenderTarget`].
386 pub viewport: Option<Viewport>,
387 /// Cameras with a higher order are rendered later, and thus on top of lower order cameras.
388 pub order: isize,
389 /// If this is set to `true`, this camera will be rendered to its specified [`RenderTarget`]. If `false`, this
390 /// camera will not be rendered.
391 pub is_active: bool,
392 /// Computed values for this camera, such as the projection matrix and the render target size.
393 pub computed: ComputedCameraValues,
394 // todo: reflect this when #6042 lands
395 /// The [`CameraOutputMode`] for this camera.
396 pub output_mode: CameraOutputMode,
397 /// Controls when MSAA writeback occurs for this camera.
398 /// See [`MsaaWriteback`] for available options.
399 pub msaa_writeback: MsaaWriteback,
400 /// The clear color operation to perform on the render target.
401 pub clear_color: ClearColorConfig,
402 /// Whether to switch culling mode so that materials that request backface
403 /// culling cull front faces, and vice versa.
404 ///
405 /// This is typically used for cameras that mirror the world that they
406 /// render across a plane, because doing that flips the winding of each
407 /// polygon.
408 ///
409 /// This setting doesn't affect materials that disable backface culling.
410 pub invert_culling: bool,
411 /// If set, this camera will be a sub camera of a large view, defined by a [`SubCameraView`].
412 pub sub_camera_view: Option<SubCameraView>,
413}
414
415impl Default for Camera {
416 fn default() -> Self {
417 Self {
418 is_active: true,
419 order: 0,
420 viewport: None,
421 computed: Default::default(),
422 output_mode: Default::default(),
423 msaa_writeback: MsaaWriteback::default(),
424 clear_color: Default::default(),
425 invert_culling: false,
426 sub_camera_view: None,
427 }
428 }
429}
430
431impl Camera {
432 /// Converts a physical size in this `Camera` to a logical size.
433 #[inline]
434 pub fn to_logical(&self, physical_size: UVec2) -> Option<Vec2> {
435 let scale = self.computed.target_info.as_ref()?.scale_factor;
436 Some(physical_size.as_vec2() / scale)
437 }
438
439 /// The rendered physical bounds [`URect`] of the camera. If the `viewport` field is
440 /// set to [`Some`], this will be the rect of that custom viewport. Otherwise it will default to
441 /// the full physical rect of the current [`RenderTarget`].
442 #[inline]
443 pub fn physical_viewport_rect(&self) -> Option<URect> {
444 let min = self
445 .viewport
446 .as_ref()
447 .map(|v| v.physical_position)
448 .unwrap_or(UVec2::ZERO);
449 let max = min + self.physical_viewport_size()?;
450 Some(URect { min, max })
451 }
452
453 /// The rendered logical bounds [`Rect`] of the camera. If the `viewport` field is set to
454 /// [`Some`], this will be the rect of that custom viewport. Otherwise it will default to the
455 /// full logical rect of the current [`RenderTarget`].
456 #[inline]
457 pub fn logical_viewport_rect(&self) -> Option<Rect> {
458 let URect { min, max } = self.physical_viewport_rect()?;
459 Some(Rect {
460 min: self.to_logical(min)?,
461 max: self.to_logical(max)?,
462 })
463 }
464
465 /// The logical size of this camera's viewport. If the `viewport` field is set to [`Some`], this
466 /// will be the size of that custom viewport. Otherwise it will default to the full logical size
467 /// of the current [`RenderTarget`].
468 /// For logic that requires the full logical size of the
469 /// [`RenderTarget`], prefer [`Camera::logical_target_size`].
470 ///
471 /// Returns `None` if either:
472 /// - the function is called just after the `Camera` is created, before `camera_system` is executed,
473 /// - the [`RenderTarget`] isn't correctly set:
474 /// - it references the [`PrimaryWindow`](RenderTarget::Window) when there is none,
475 /// - it references a [`Window`](RenderTarget::Window) entity that doesn't exist or doesn't actually have a `Window` component,
476 /// - it references an [`Image`](RenderTarget::Image) that doesn't exist (invalid handle),
477 /// - it references a [`TextureView`](RenderTarget::TextureView) that doesn't exist (invalid handle).
478 #[inline]
479 pub fn logical_viewport_size(&self) -> Option<Vec2> {
480 self.viewport
481 .as_ref()
482 .and_then(|v| self.to_logical(v.physical_size))
483 .or_else(|| self.logical_target_size())
484 }
485
486 /// The physical size of this camera's viewport (in physical pixels).
487 /// If the `viewport` field is set to [`Some`], this
488 /// will be the size of that custom viewport. Otherwise it will default to the full physical size of
489 /// the current [`RenderTarget`].
490 /// For logic that requires the full physical size of the [`RenderTarget`], prefer [`Camera::physical_target_size`].
491 #[inline]
492 pub fn physical_viewport_size(&self) -> Option<UVec2> {
493 self.viewport
494 .as_ref()
495 .map(|v| v.physical_size)
496 .or_else(|| self.physical_target_size())
497 }
498
499 /// The full logical size of this camera's [`RenderTarget`], ignoring custom `viewport` configuration.
500 /// Note that if the `viewport` field is [`Some`], this will not represent the size of the rendered area.
501 /// For logic that requires the size of the actually rendered area, prefer [`Camera::logical_viewport_size`].
502 #[inline]
503 pub fn logical_target_size(&self) -> Option<Vec2> {
504 self.computed
505 .target_info
506 .as_ref()
507 .and_then(|t| self.to_logical(t.physical_size))
508 }
509
510 /// The full physical size of this camera's [`RenderTarget`] (in physical pixels),
511 /// ignoring custom `viewport` configuration.
512 /// Note that if the `viewport` field is [`Some`], this will not represent the size of the rendered area.
513 /// For logic that requires the size of the actually rendered area, prefer [`Camera::physical_viewport_size`].
514 #[inline]
515 pub fn physical_target_size(&self) -> Option<UVec2> {
516 self.computed.target_info.as_ref().map(|t| t.physical_size)
517 }
518
519 #[inline]
520 pub fn target_scaling_factor(&self) -> Option<f32> {
521 self.computed
522 .target_info
523 .as_ref()
524 .map(|t: &RenderTargetInfo| t.scale_factor)
525 }
526
527 /// The projection matrix computed using this camera's [`Projection`](super::projection::Projection).
528 #[inline]
529 pub fn clip_from_view(&self) -> Mat4 {
530 self.computed.clip_from_view
531 }
532
533 /// Core conversion logic to compute viewport coordinates
534 ///
535 /// This function is shared by `world_to_viewport` and `world_to_viewport_with_depth`
536 /// to avoid code duplication.
537 ///
538 /// Returns a tuple `(viewport_position, depth)`.
539 fn world_to_viewport_core(
540 &self,
541 camera_transform: &GlobalTransform,
542 world_position: Vec3,
543 ) -> Result<(Vec2, f32), ViewportConversionError> {
544 let target_rect = self
545 .logical_viewport_rect()
546 .ok_or(ViewportConversionError::NoViewportSize)?;
547 let mut ndc_space_coords = self
548 .world_to_ndc(camera_transform, world_position)
549 .ok_or(ViewportConversionError::InvalidData)?;
550 // NDC z-values outside of 0 < z < 1 are outside the (implicit) camera frustum and are thus not in viewport-space
551 if ndc_space_coords.z < 0.0 {
552 return Err(ViewportConversionError::PastFarPlane);
553 }
554 if ndc_space_coords.z > 1.0 {
555 return Err(ViewportConversionError::PastNearPlane);
556 }
557
558 let depth = ndc_space_coords.z;
559
560 // Flip the Y co-ordinate origin from the bottom to the top.
561 ndc_space_coords.y = -ndc_space_coords.y;
562
563 // Once in NDC space, we can discard the z element and map x/y to the viewport rect
564 let viewport_position =
565 (ndc_space_coords.truncate() + Vec2::ONE) / 2.0 * target_rect.size() + target_rect.min;
566 Ok((viewport_position, depth))
567 }
568
569 /// Given a position in world space, use the camera to compute the viewport-space coordinates.
570 ///
571 /// To get the coordinates in Normalized Device Coordinates, you should use
572 /// [`world_to_ndc`](Self::world_to_ndc).
573 ///
574 /// # Panics
575 ///
576 /// Will panic if `glam_assert` is enabled and the `camera_transform` contains `NAN`
577 /// (see [`world_to_ndc`][Self::world_to_ndc]).
578 #[doc(alias = "world_to_screen")]
579 pub fn world_to_viewport(
580 &self,
581 camera_transform: &GlobalTransform,
582 world_position: Vec3,
583 ) -> Result<Vec2, ViewportConversionError> {
584 Ok(self
585 .world_to_viewport_core(camera_transform, world_position)?
586 .0)
587 }
588
589 /// Given a position in world space, use the camera to compute the viewport-space coordinates and depth.
590 ///
591 /// To get the coordinates in Normalized Device Coordinates, you should use
592 /// [`world_to_ndc`](Self::world_to_ndc).
593 ///
594 /// # Panics
595 ///
596 /// Will panic if `glam_assert` is enabled and the `camera_transform` contains `NAN`
597 /// (see [`world_to_ndc`][Self::world_to_ndc]).
598 #[doc(alias = "world_to_screen_with_depth")]
599 pub fn world_to_viewport_with_depth(
600 &self,
601 camera_transform: &GlobalTransform,
602 world_position: Vec3,
603 ) -> Result<Vec3, ViewportConversionError> {
604 let result = self.world_to_viewport_core(camera_transform, world_position)?;
605 // Stretching ndc depth to value via near plane and negating result to be in positive room again.
606 let depth = -self.depth_ndc_to_view_z(result.1);
607 Ok(result.0.extend(depth))
608 }
609
610 /// Returns a ray originating from the camera, that passes through everything beyond `viewport_position`.
611 ///
612 /// The resulting ray starts on the near plane of the camera.
613 ///
614 /// If the camera's projection is orthographic the direction of the ray is always equal to `camera_transform.forward()`.
615 ///
616 /// To get the world space coordinates with Normalized Device Coordinates, you should use
617 /// [`ndc_to_world`](Self::ndc_to_world).
618 ///
619 /// # Example
620 /// ```no_run
621 /// # use bevy_window::Window;
622 /// # use bevy_ecs::prelude::{Single, IntoScheduleConfigs};
623 /// # use bevy_transform::prelude::{GlobalTransform, TransformSystems};
624 /// # use bevy_camera::Camera;
625 /// # use bevy_app::{App, PostUpdate};
626 /// #
627 /// fn system(camera_query: Single<(&Camera, &GlobalTransform)>, window: Single<&Window>) {
628 /// let (camera, camera_transform) = *camera_query;
629 ///
630 /// if let Some(cursor_position) = window.cursor_position()
631 /// // Calculate a ray pointing from the camera into the world based on the cursor's position.
632 /// && let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position)
633 /// {
634 /// println!("{ray:?}");
635 /// }
636 /// }
637 ///
638 /// # let mut app = App::new();
639 /// // Run the system after transform propagation so the camera's global transform is up-to-date.
640 /// app.add_systems(PostUpdate, system.after(TransformSystems::Propagate));
641 /// ```
642 ///
643 /// # Panics
644 ///
645 /// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
646 /// `glam_assert` is enabled (see [`ndc_to_world`](Self::ndc_to_world).
647 pub fn viewport_to_world(
648 &self,
649 camera_transform: &GlobalTransform,
650 viewport_position: Vec2,
651 ) -> Result<Ray3d, ViewportConversionError> {
652 let ndc_xy = self.viewport_to_ndc(viewport_position)?;
653
654 let ndc_point_near = ndc_xy.extend(1.0).into();
655 // Using EPSILON because an ndc with Z = 0 returns NaNs.
656 let ndc_point_far = ndc_xy.extend(f32::EPSILON).into();
657 let view_from_clip = self.computed.clip_from_view.inverse();
658 let world_from_view = camera_transform.affine();
659 // We multiply the point by `view_from_clip` and then `world_from_view` in sequence to avoid the precision loss
660 // (and performance penalty) incurred by pre-composing an affine transform with a projective transform.
661 // Additionally, we avoid adding and subtracting translation to the direction component to maintain precision.
662 let view_point_near = view_from_clip.project_point3a(ndc_point_near);
663 let view_point_far = view_from_clip.project_point3a(ndc_point_far);
664 let view_dir = view_point_far - view_point_near;
665 let origin = world_from_view.transform_point3a(view_point_near).into();
666 let direction = world_from_view.transform_vector3a(view_dir).into();
667
668 // The fallible direction constructor ensures that direction isn't NaN.
669 Dir3::new(direction)
670 .map_err(|_| ViewportConversionError::InvalidData)
671 .map(|direction| Ray3d { origin, direction })
672 }
673
674 /// Returns a 2D world position computed from a position on this [`Camera`]'s viewport.
675 ///
676 /// Useful for 2D cameras and other cameras with an orthographic projection pointing along the Z axis.
677 ///
678 /// To get the world space coordinates with Normalized Device Coordinates, you should use
679 /// [`ndc_to_world`](Self::ndc_to_world).
680 ///
681 /// # Example
682 /// ```no_run
683 /// # use bevy_window::Window;
684 /// # use bevy_ecs::prelude::*;
685 /// # use bevy_transform::prelude::{GlobalTransform, TransformSystems};
686 /// # use bevy_camera::Camera;
687 /// # use bevy_app::{App, PostUpdate};
688 /// #
689 /// fn system(camera_query: Single<(&Camera, &GlobalTransform)>, window: Single<&Window>) {
690 /// let (camera, camera_transform) = *camera_query;
691 ///
692 /// if let Some(cursor_position) = window.cursor_position()
693 /// // Calculate a world position based on the cursor's position.
694 /// && let Ok(world_pos) = camera.viewport_to_world_2d(camera_transform, cursor_position)
695 /// {
696 /// println!("World position: {world_pos:.2}");
697 /// }
698 /// }
699 ///
700 /// # let mut app = App::new();
701 /// // Run the system after transform propagation so the camera's global transform is up-to-date.
702 /// app.add_systems(PostUpdate, system.after(TransformSystems::Propagate));
703 /// ```
704 ///
705 /// # Panics
706 ///
707 /// Will panic if the camera's projection matrix is invalid (has a determinant of 0) and
708 /// `glam_assert` is enabled (see [`ndc_to_world`](Self::ndc_to_world).
709 pub fn viewport_to_world_2d(
710 &self,
711 camera_transform: &GlobalTransform,
712 viewport_position: Vec2,
713 ) -> Result<Vec2, ViewportConversionError> {
714 let ndc = self.viewport_to_ndc(viewport_position)?;
715
716 let world_near_plane = self
717 .ndc_to_world(camera_transform, ndc.extend(1.))
718 .ok_or(ViewportConversionError::InvalidData)?;
719
720 Ok(world_near_plane.truncate())
721 }
722
723 /// Given a point in world space, use the camera's viewport to compute the Normalized Device Coordinates of the point.
724 ///
725 /// When the point is within the viewport the values returned will be between -1.0 (bottom left) and 1.0 (top right)
726 /// on the X and Y axes, and between 0.0 (far) and 1.0 (near) on the Z axis.
727 /// To get the coordinates in the render target's viewport dimensions, you should use
728 /// [`world_to_viewport`](Self::world_to_viewport).
729 ///
730 /// Returns `None` if the `camera_transform`, the `world_position`, or the projection matrix defined by
731 /// [`Projection`](super::projection::Projection) contain `NAN`.
732 ///
733 /// # Panics
734 ///
735 /// Will panic if the `camera_transform` contains `NAN` and the `glam_assert` feature is enabled.
736 pub fn world_to_ndc<V: Into<Vec3A> + From<Vec3A>>(
737 &self,
738 camera_transform: &GlobalTransform,
739 world_point: V,
740 ) -> Option<V> {
741 let view_from_world = camera_transform.affine().inverse();
742 let view_point = view_from_world.transform_point3a(world_point.into());
743 let ndc_point = self.computed.clip_from_view.project_point3a(view_point);
744
745 (!ndc_point.is_nan()).then_some(ndc_point.into())
746 }
747
748 /// Given a position in Normalized Device Coordinates,
749 /// use the camera's viewport to compute the world space position.
750 ///
751 /// The input is expected to be in NDC: `x` and `y` in the range `[-1.0, 1.0]`, and `z` in `[0.0, 1.0]`
752 /// (with `z = 0.0` at the far plane and `z = 1.0` at the near plane).
753 /// The returned value is a position in world space (your game's world units) and is not limited to `[-1.0, 1.0]`.
754 /// To convert from a viewport position to world space, you should use
755 /// [`viewport_to_world`](Self::viewport_to_world).
756 ///
757 /// Returns `None` if the `camera_transform`, the `ndc_point`, or the projection matrix defined by
758 /// [`Projection`](super::projection::Projection) contain `NAN`.
759 ///
760 /// # Panics
761 ///
762 /// Will panic if the projection matrix is invalid (has a determinant of 0) and `glam_assert` is enabled.
763 pub fn ndc_to_world<V: Into<Vec3A> + From<Vec3A>>(
764 &self,
765 camera_transform: &GlobalTransform,
766 ndc_point: V,
767 ) -> Option<V> {
768 // We multiply the point by `view_from_clip` and then `world_from_view` in sequence to avoid the precision loss
769 // (and performance penalty) incurred by pre-composing an affine transform with a projective transform.
770 let view_point = self
771 .computed
772 .clip_from_view
773 .inverse()
774 .project_point3a(ndc_point.into());
775 let world_point = camera_transform.affine().transform_point3a(view_point);
776
777 (!world_point.is_nan()).then_some(world_point.into())
778 }
779
780 /// Converts the depth in Normalized Device Coordinates
781 /// to linear view z for perspective projections.
782 ///
783 /// Note: Depth values in front of the camera will be negative as -z is forward
784 pub fn depth_ndc_to_view_z(&self, ndc_depth: f32) -> f32 {
785 let near = self.clip_from_view().w_axis.z; // [3][2]
786 -near / ndc_depth
787 }
788
789 /// Converts the depth in Normalized Device Coordinates
790 /// to linear view z for orthographic projections.
791 ///
792 /// Note: Depth values in front of the camera will be negative as -z is forward
793 pub fn depth_ndc_to_view_z_2d(&self, ndc_depth: f32) -> f32 {
794 -(self.clip_from_view().w_axis.z - ndc_depth) / self.clip_from_view().z_axis.z
795 // [3][2] [2][2]
796 }
797
798 /// Converts a position in viewport coordinates to NDC.
799 pub fn viewport_to_ndc(
800 &self,
801 viewport_position: Vec2,
802 ) -> Result<Vec2, ViewportConversionError> {
803 let target_rect = self
804 .logical_viewport_rect()
805 .ok_or(ViewportConversionError::NoViewportSize)?;
806 let rect_relative = (viewport_position - target_rect.min) / target_rect.size();
807 let mut ndc = rect_relative * 2. - Vec2::ONE;
808 // Flip the Y co-ordinate from the top to the bottom to enter NDC.
809 ndc.y = -ndc.y;
810 Ok(ndc)
811 }
812}
813
814/// The entity that Bevy uses to resolve visibility ranges when no specific
815/// camera is applicable.
816///
817/// For efficiency, Bevy currently renders point and spot light shadow maps only
818/// once per frame, regardless of the number of cameras in use, rather than
819/// rendering the shadow maps anew for each camera each frame. Most of the time,
820/// this optimization doesn't change the result relative to a rendering that
821/// rendered such shadow maps separately for each camera. However, there's one
822/// exception: visibility ranges. Visibility ranges cause meshes to be visible
823/// or invisible depending on the distance from the mesh to the camera. When
824/// rendering a shadow map for a point or spot light, Bevy must therefore select
825/// an entity to use as the reference point for the purposes of visibility
826/// ranges. This entity is called the *LOD origin*.
827///
828/// Placing this component on an entity makes that entity the origin from which
829/// LOD distances are computed for the purposes of shadow mapping of point and
830/// spot lights. Typically, you place this component on a camera, but you may
831/// place it on another entity if you wish.
832///
833/// The exact algorithm that Bevy uses to determine the LOD origin is as
834/// follows. Once the LOD origin is determined, all further steps are skipped.
835///
836/// 1. If an entity has this [`ShadowLodOrigin`] component, then it's the LOD
837/// origin. If there's more than one entity with the [`ShadowLodOrigin`]
838/// component, one is chosen arbitrarily in a manner that's stable from frame to
839/// frame.
840///
841/// 2. If a camera renders to a window (that is, the camera's [`RenderTarget`]
842/// is [`RenderTarget::Window`]), then that camera is the shadow LOD origin. If
843/// there's more than one such camera that renders to a window, then one is
844/// chosen arbitrarily in a manner that's stable from frame to frame.
845///
846/// 3. A camera is chosen to be the LOD origin arbitrarily from all cameras in
847/// the scene in a manner that's stable from frame to frame.
848///
849/// This algorithm means that, in most cases, you don't need to add this
850/// [`ShadowLodOrigin`] component explicitly to the scene; usually, Bevy chooses
851/// the right origin automatically. You only need to use this component
852/// explicitly if you have multiple cameras rendering to the window: e.g. in a
853/// split-screen game.
854#[derive(Clone, Copy, Default, Component, Debug, Reflect)]
855#[reflect(Clone, Default, Component)]
856#[require(Transform)]
857pub struct ShadowLodOrigin;
858
859/// Control how this [`Camera`] outputs once rendering is completed.
860#[derive(Debug, Clone, Copy, Reflect)]
861pub enum CameraOutputMode {
862 /// Writes the camera output to configured render target.
863 Write {
864 /// The blend state that will be used by the pipeline that writes the intermediate render textures to the final render target texture.
865 /// If not set, the output will be written as-is, ignoring `clear_color` and the existing data in the final render target texture.
866 blend_state: Option<BlendState>,
867 /// The clear color operation to perform on the final render target texture.
868 clear_color: ClearColorConfig,
869 },
870 /// Skips writing the camera output to the configured render target. The output will remain in the
871 /// Render Target's "intermediate" textures, which a camera with a higher order should write to the render target
872 /// using [`CameraOutputMode::Write`]. The "skip" mode can easily prevent render results from being displayed, or cause
873 /// them to be lost. Only use this if you know what you are doing!
874 /// In camera setups with multiple active cameras rendering to the same [`RenderTarget`], the Skip mode can be used to remove
875 /// unnecessary / redundant writes to the final output texture, removing unnecessary render passes.
876 Skip,
877}
878
879impl Default for CameraOutputMode {
880 fn default() -> Self {
881 CameraOutputMode::Write {
882 blend_state: None,
883 clear_color: ClearColorConfig::Default,
884 }
885 }
886}
887
888/// The "target" that a [`Camera`] will render to. For example, this could be a `Window`
889/// swapchain or an [`Image`].
890#[derive(Component, Debug, Clone, Reflect, From)]
891#[reflect(Clone, Component)]
892pub enum RenderTarget {
893 /// Window to which the camera's view is rendered.
894 Window(WindowRef),
895 /// Image to which the camera's view is rendered.
896 Image(ImageRenderTarget),
897 /// Texture View to which the camera's view is rendered.
898 /// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
899 TextureView(ManualTextureViewHandle),
900 /// The camera won't render to any color target.
901 ///
902 /// This is useful when you want a camera that *only* renders prepasses, for
903 /// example a depth prepass. See the `render_depth_to_texture` example.
904 None {
905 /// The physical size of the viewport.
906 size: UVec2,
907 },
908}
909
910impl RenderTarget {
911 /// Get a handle to the render target's image,
912 /// or `None` if the render target is another variant.
913 pub fn as_image(&self) -> Option<&Handle<Image>> {
914 if let Self::Image(image_target) = self {
915 Some(&image_target.handle)
916 } else {
917 None
918 }
919 }
920
921 /// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
922 pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
923 match self {
924 RenderTarget::Window(window_ref) => window_ref
925 .normalize(primary_window)
926 .map(NormalizedRenderTarget::Window),
927 RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
928 RenderTarget::TextureView(id) => Some(NormalizedRenderTarget::TextureView(*id)),
929 RenderTarget::None { size } => Some(NormalizedRenderTarget::None {
930 width: size.x,
931 height: size.y,
932 }),
933 }
934 }
935}
936
937/// Normalized version of the render target.
938///
939/// Once we have this we shouldn't need to resolve it down anymore.
940#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord, From)]
941#[reflect(Clone, PartialEq, Hash)]
942pub enum NormalizedRenderTarget {
943 /// Window to which the camera's view is rendered.
944 Window(NormalizedWindowRef),
945 /// Image to which the camera's view is rendered.
946 Image(ImageRenderTarget),
947 /// Texture View to which the camera's view is rendered.
948 /// Useful when the texture view needs to be created outside of Bevy, for example OpenXR.
949 TextureView(ManualTextureViewHandle),
950 /// The camera won't render to any color target.
951 ///
952 /// This is useful when you want a camera that *only* renders prepasses, for
953 /// example a depth prepass. See the `render_depth_to_texture` example.
954 None {
955 /// The physical width of the viewport.
956 width: u32,
957 /// The physical height of the viewport.
958 height: u32,
959 },
960}
961
962/// A unique id that corresponds to a specific `ManualTextureView` in the `ManualTextureViews` collection.
963///
964/// See `ManualTextureViews` in `bevy_camera` for more details.
965#[derive(
966 Default,
967 Debug,
968 Clone,
969 Copy,
970 PartialEq,
971 Eq,
972 Hash,
973 PartialOrd,
974 Ord,
975 Component,
976 Reflect,
977 FromTemplate,
978)]
979#[reflect(Component, Default, Debug, PartialEq, Hash, Clone)]
980pub struct ManualTextureViewHandle(pub u32);
981
982/// A render target that renders to an [`Image`].
983#[derive(Debug, Clone, Reflect)]
984#[reflect(Clone, PartialEq, Hash)]
985pub struct ImageRenderTarget {
986 /// The image to render to.
987 pub handle: Handle<Image>,
988 /// The scale factor of the render target image, corresponding to the scale
989 /// factor for a window target. This should almost always be 1.0.
990 pub scale_factor: f32,
991}
992
993impl Eq for ImageRenderTarget {}
994
995impl PartialEq for ImageRenderTarget {
996 fn eq(&self, other: &Self) -> bool {
997 self.handle == other.handle && FloatOrd(self.scale_factor) == FloatOrd(other.scale_factor)
998 }
999}
1000
1001impl core::hash::Hash for ImageRenderTarget {
1002 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
1003 self.handle.hash(state);
1004 FloatOrd(self.scale_factor).hash(state);
1005 }
1006}
1007
1008impl PartialOrd for ImageRenderTarget {
1009 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1010 Some(self.cmp(other))
1011 }
1012}
1013
1014impl Ord for ImageRenderTarget {
1015 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1016 self.handle
1017 .cmp(&other.handle)
1018 .then_with(|| FloatOrd(self.scale_factor).cmp(&FloatOrd(other.scale_factor)))
1019 }
1020}
1021
1022impl From<Handle<Image>> for RenderTarget {
1023 fn from(handle: Handle<Image>) -> Self {
1024 Self::Image(handle.into())
1025 }
1026}
1027
1028impl From<Handle<Image>> for ImageRenderTarget {
1029 fn from(handle: Handle<Image>) -> Self {
1030 Self {
1031 handle,
1032 scale_factor: 1.0,
1033 }
1034 }
1035}
1036
1037impl Default for RenderTarget {
1038 fn default() -> Self {
1039 Self::Window(Default::default())
1040 }
1041}
1042
1043/// This component lets you control the [`TextureUsages`] field of the main texture generated for the camera
1044#[derive(Component, Clone, Copy, Reflect)]
1045#[reflect(opaque)]
1046#[reflect(Component, Default, Clone)]
1047pub struct CameraMainTextureUsages(pub TextureUsages);
1048
1049impl Default for CameraMainTextureUsages {
1050 fn default() -> Self {
1051 Self(
1052 TextureUsages::RENDER_ATTACHMENT
1053 | TextureUsages::TEXTURE_BINDING
1054 | TextureUsages::COPY_SRC,
1055 )
1056 }
1057}
1058
1059impl CameraMainTextureUsages {
1060 pub fn with(mut self, usages: TextureUsages) -> Self {
1061 self.0 |= usages;
1062 self
1063 }
1064}
1065
1066#[cfg(test)]
1067mod test {
1068 use bevy_math::{Vec2, Vec3};
1069 use bevy_transform::components::GlobalTransform;
1070
1071 use crate::{
1072 Camera, OrthographicProjection, PerspectiveProjection, Projection, RenderTargetInfo,
1073 Viewport,
1074 };
1075
1076 fn make_camera(mut projection: Projection, physical_size: Vec2) -> Camera {
1077 let viewport = Viewport {
1078 physical_size: physical_size.as_uvec2(),
1079 ..Default::default()
1080 };
1081 let mut camera = Camera {
1082 viewport: Some(viewport.clone()),
1083 ..Default::default()
1084 };
1085 camera.computed.target_info = Some(RenderTargetInfo {
1086 physical_size: viewport.physical_size,
1087 scale_factor: 1.0,
1088 });
1089 projection.update(
1090 viewport.physical_size.x as f32,
1091 viewport.physical_size.y as f32,
1092 );
1093 camera.computed.clip_from_view = projection.get_clip_from_view();
1094 camera
1095 }
1096
1097 #[test]
1098 fn viewport_to_world_orthographic_3d_returns_forward() {
1099 let transform = GlobalTransform::default();
1100 let size = Vec2::new(1600.0, 900.0);
1101 let camera = make_camera(
1102 Projection::Orthographic(OrthographicProjection::default_3d()),
1103 size,
1104 );
1105 let ray = camera.viewport_to_world(&transform, Vec2::ZERO).unwrap();
1106 assert_eq!(ray.direction, transform.forward());
1107 assert!(ray
1108 .origin
1109 .abs_diff_eq(Vec3::new(-size.x * 0.5, size.y * 0.5, 0.0), 1e-4));
1110 let ray = camera.viewport_to_world(&transform, size).unwrap();
1111 assert_eq!(ray.direction, transform.forward());
1112 assert!(ray
1113 .origin
1114 .abs_diff_eq(Vec3::new(size.x * 0.5, -size.y * 0.5, 0.0), 1e-4));
1115 }
1116
1117 #[test]
1118 fn viewport_to_world_orthographic_2d_returns_forward() {
1119 let transform = GlobalTransform::default();
1120 let size = Vec2::new(1600.0, 900.0);
1121 let camera = make_camera(
1122 Projection::Orthographic(OrthographicProjection::default_2d()),
1123 size,
1124 );
1125 let ray = camera.viewport_to_world(&transform, Vec2::ZERO).unwrap();
1126 assert_eq!(ray.direction, transform.forward());
1127 assert!(ray
1128 .origin
1129 .abs_diff_eq(Vec3::new(-size.x * 0.5, size.y * 0.5, 1000.0), 1e-4));
1130 let ray = camera.viewport_to_world(&transform, size).unwrap();
1131 assert_eq!(ray.direction, transform.forward());
1132 assert!(ray
1133 .origin
1134 .abs_diff_eq(Vec3::new(size.x * 0.5, -size.y * 0.5, 1000.0), 1e-4));
1135 }
1136
1137 #[test]
1138 fn viewport_to_world_perspective_center_returns_forward() {
1139 let transform = GlobalTransform::default();
1140 let size = Vec2::new(1600.0, 900.0);
1141 let camera = make_camera(
1142 Projection::Perspective(PerspectiveProjection::default()),
1143 size,
1144 );
1145 let ray = camera.viewport_to_world(&transform, size * 0.5).unwrap();
1146 assert_eq!(ray.direction, transform.forward());
1147 assert_eq!(ray.origin, transform.forward() * 0.1);
1148 }
1149}