Skip to main content

renderling/camera/
shader.rs

1//! [`CameraDescriptor`] and camera shader utilities.
2
3use crabslab::SlabItem;
4use glam::{Mat4, Vec2, Vec3, Vec4};
5
6use crate::{bvol::Frustum, math::IsVector};
7
8/// GPU descriptor of a camera.
9///
10/// Used for transforming the stage during rendering.
11///
12/// Use [`CameraDescriptor::new`] to create a new camera.
13#[derive(Default, Clone, Copy, PartialEq, SlabItem, core::fmt::Debug)]
14pub struct CameraDescriptor {
15    projection: Mat4,
16    view: Mat4,
17    position: Vec3,
18    frustum: Frustum,
19    /// Nearest center point on the frustum
20    z_near_point: Vec3,
21    /// Furthest center point on the frustum
22    z_far_point: Vec3,
23}
24
25impl CameraDescriptor {
26    pub fn new(projection: Mat4, view: Mat4) -> Self {
27        CameraDescriptor::default().with_projection_and_view(projection, view)
28    }
29
30    pub fn default_perspective(width: f32, height: f32) -> Self {
31        let (projection, view) = super::default_perspective(width, height);
32        CameraDescriptor::new(projection, view)
33    }
34
35    pub fn default_ortho2d(width: f32, height: f32) -> Self {
36        let (projection, view) = super::default_ortho2d(width, height);
37        CameraDescriptor::new(projection, view)
38    }
39
40    pub fn projection(&self) -> Mat4 {
41        self.projection
42    }
43
44    /// Set the projection and view matrices for this camera.
45    ///
46    /// ## Note on depth ordering
47    /// The rendering pipeline uses `CompareFunction::Less` for depth testing.
48    /// This requires that the projection matrix maps closer objects to
49    /// *smaller* z_ndc values. For orthographic projections created with
50    /// `Mat4::orthographic_rh`, this means `near` must be less than `far`.
51    /// If `near > far`, the depth mapping is reversed and back faces will
52    /// be drawn over front faces, producing incorrect (typically black)
53    /// lighting output.
54    pub fn set_projection_and_view(&mut self, projection: Mat4, view: Mat4) {
55        // The z column of the projection matrix determines the depth mapping
56        // direction. A positive z coefficient (projection.z_axis.z > 0)
57        // indicates reversed depth (closer objects get larger z_ndc), which
58        // will cause incorrect results with the standard
59        // CompareFunction::Less depth test. This typically happens when
60        // `near > far` is passed to `Mat4::orthographic_rh`.
61        #[cfg(cpu)]
62        {
63            let z_coeff = projection.z_axis.z;
64            if z_coeff > 0.0 {
65                log::warn!(
66                    "Projection matrix has a positive z coefficient ({z_coeff}), indicating \
67                     reversed depth mapping. This will cause incorrect depth testing (back faces \
68                     drawn over front faces) with the standard CompareFunction::Less. For \
69                     orthographic projections, ensure near < far."
70                );
71            }
72        }
73        self.projection = projection;
74        self.view = view;
75        self.position = view.inverse().transform_point3(Vec3::ZERO);
76        let inverse = (projection * view).inverse();
77        self.z_near_point = inverse.project_point3(Vec3::ZERO);
78        self.z_far_point = inverse.project_point3(Vec2::ZERO.extend(1.0));
79        self.frustum = Frustum::from_camera(self);
80    }
81
82    pub fn with_projection_and_view(mut self, projection: Mat4, view: Mat4) -> Self {
83        self.set_projection_and_view(projection, view);
84        self
85    }
86
87    pub fn set_projection(&mut self, projection: Mat4) {
88        self.set_projection_and_view(projection, self.view);
89    }
90
91    pub fn with_projection(mut self, projection: Mat4) -> Self {
92        self.set_projection(projection);
93        self
94    }
95
96    pub fn view(&self) -> Mat4 {
97        self.view
98    }
99
100    pub fn set_view(&mut self, view: Mat4) {
101        self.set_projection_and_view(self.projection, view);
102    }
103
104    pub fn with_view(mut self, view: Mat4) -> Self {
105        self.set_view(view);
106        self
107    }
108
109    pub fn position(&self) -> Vec3 {
110        self.position
111    }
112
113    pub fn frustum(&self) -> Frustum {
114        self.frustum
115    }
116
117    pub fn view_projection(&self) -> Mat4 {
118        self.projection * self.view
119    }
120
121    pub fn near_plane(&self) -> Vec4 {
122        self.frustum.planes[0]
123    }
124
125    pub fn far_plane(&self) -> Vec4 {
126        self.frustum.planes[5]
127    }
128
129    /// Returns **roughly** the location of the znear plane.
130    pub fn z_near(&self) -> f32 {
131        self.z_near_point.distance(self.position)
132    }
133
134    pub fn z_far(&self) -> f32 {
135        self.z_far_point.distance(self.position)
136    }
137
138    pub fn depth(&self) -> f32 {
139        (self.z_far() - self.z_near()).abs()
140    }
141
142    /// Returns the normalized forward vector which points in the direction the
143    /// camera is looking.
144    pub fn forward(&self) -> Vec3 {
145        (self.z_far_point - self.z_near_point).alt_norm_or_zero()
146    }
147
148    pub fn frustum_near_point(&self) -> Vec3 {
149        self.forward() * self.z_near()
150    }
151
152    pub fn frustum_far_point(&self) -> Vec3 {
153        self.forward() * self.z_far()
154    }
155}