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