renderling/light/
shader.rs

1//! Shader functions for the lighting system.
2//!
3//! Directional lights are in lux, spot and point lights are in
4//! candelas. Conversion happens in the [PBR shader during radiance
5//! accumulation](crate::pbr::shader::shade_fragment).
6//!
7//! More info is here
8//! <https://www.realtimerendering.com/blog/physical-units-for-lights>.
9//!
10//! ## Note
11//!
12//! The glTF spec [1] says directional light is in lux, whereas spot and point are
13//! in candelas. The same goes for this library's shaders, but not a ton of work
14//! has gone into verifying that conversion from these units into radiometric units
15//! is accurate _in any way_. The shaders roughly do a conversion by dividing by 683 [2]
16//! or some other constant involving 683 [3].
17//!
18//! More work needs to be done here. PRs would be very appreciated.
19//!
20//! [1]: https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md
21//! [2]: https://depts.washington.edu/mictech/optics/me557/Radiometry.pdf
22//! [3]: https://projects.blender.org/blender/blender-addons/commit/9d903a93f03b
23use crabslab::{Array, Id, Slab, SlabItem};
24use glam::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
25#[cfg(gpu)]
26use spirv_std::num_traits::Float;
27use spirv_std::{spirv, Image};
28
29use crate::{
30    atlas::shader::{AtlasDescriptor, AtlasTextureDescriptor},
31    cubemap::shader::{CubemapDescriptor, CubemapFaceDirection},
32    geometry::shader::GeometryDescriptor,
33    math::{Fetch, IsSampler, IsVector, Sample2dArray},
34    primitive::shader::{PrimitiveDescriptor, VertexInfo},
35    transform::shader::TransformDescriptor,
36};
37
38#[derive(Clone, Copy, Default, SlabItem, core::fmt::Debug)]
39#[offsets]
40pub struct LightingDescriptor {
41    /// List of all analytical lights in the scene.
42    pub analytical_lights_array: Array<Id<LightDescriptor>>,
43    /// Shadow mapping atlas info.
44    pub shadow_map_atlas_descriptor_id: Id<AtlasDescriptor>,
45    /// `Id` of the [`ShadowMapDescriptor`] to use when updating
46    /// a shadow map.
47    ///
48    /// This changes from each run of the `shadow_mapping_vertex`.
49    pub update_shadow_map_id: Id<ShadowMapDescriptor>,
50    /// The index of the shadow map atlas texture to update.
51    pub update_shadow_map_texture_index: u32,
52    /// `Id` of the [`LightTilingDescriptor`] to use when performing
53    /// light tiling.
54    pub light_tiling_descriptor_id: Id<LightTilingDescriptor>,
55}
56
57#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
58pub struct ShadowMapDescriptor {
59    pub light_space_transforms_array: Array<Mat4>,
60    /// Near plane of the projection matrix
61    pub z_near: f32,
62    /// Far plane of the projection matrix
63    pub z_far: f32,
64    /// Pointers to the atlas textures where the shadow map depth
65    /// data is stored.
66    ///
67    /// This will be an array of one `Id` for directional and spot lights,
68    /// and an array of four `Id`s for a point light.
69    pub atlas_textures_array: Array<Id<AtlasTextureDescriptor>>,
70    pub bias_min: f32,
71    pub bias_max: f32,
72    pub pcf_samples: u32,
73}
74
75impl Default for ShadowMapDescriptor {
76    fn default() -> Self {
77        Self {
78            light_space_transforms_array: Default::default(),
79            z_near: Default::default(),
80            z_far: Default::default(),
81            atlas_textures_array: Default::default(),
82            bias_min: 0.0005,
83            bias_max: 0.005,
84            pcf_samples: 4,
85        }
86    }
87}
88
89#[cfg(test)]
90#[derive(Default, Debug, Clone, Copy, PartialEq)]
91pub struct ShadowMappingVertexInfo {
92    pub renderlet_id: Id<PrimitiveDescriptor>,
93    pub vertex_index: u32,
94    pub vertex: crate::geometry::Vertex,
95    pub transform: TransformDescriptor,
96    pub model_matrix: Mat4,
97    pub world_pos: Vec3,
98    pub view_projection: Mat4,
99    pub clip_pos: Vec4,
100}
101
102/// Shadow mapping vertex shader.
103///
104/// It is assumed that a [`LightingDescriptor`] is stored at `Id(0)` of the
105/// `light_slab`.
106///
107/// This shader reads the [`LightingDescriptor`] to find the shadow map to
108/// be updated, then determines the clip positions to emit based on the
109/// shadow map's atlas texture.
110///
111/// It then renders the renderlet into the designated atlas frame.
112// Note:
113// If this is taking too long to render for each renderlet, think about
114// a frustum and occlusion culling pass to generate the list of renderlets.
115#[spirv(vertex)]
116#[allow(clippy::too_many_arguments)]
117pub fn shadow_mapping_vertex(
118    // Points at a `Renderlet`
119    #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
120    // Which vertex within the renderlet are we rendering
121    #[spirv(vertex_index)] vertex_index: u32,
122    // The slab where the renderlet's geometry is staged
123    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
124    // The slab where the scene's lighting data is staged
125    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] light_slab: &[u32],
126
127    #[spirv(position)] out_clip_pos: &mut Vec4,
128    #[cfg(test)] out_comparison_info: &mut ShadowMappingVertexInfo,
129) {
130    let renderlet = geometry_slab.read_unchecked(renderlet_id);
131    if !renderlet.visible {
132        // put it outside the clipping frustum
133        *out_clip_pos = Vec4::new(100.0, 100.0, 100.0, 1.0);
134        return;
135    }
136
137    let VertexInfo {
138        world_pos,
139        vertex: _vertex,
140        transform: _transform,
141        model_matrix: _model_matrix,
142    } = renderlet.get_vertex_info(vertex_index, geometry_slab);
143
144    let lighting_desc = light_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
145    let shadow_desc = light_slab.read_unchecked(lighting_desc.update_shadow_map_id);
146    let light_space_transform_id = shadow_desc
147        .light_space_transforms_array
148        .at(lighting_desc.update_shadow_map_texture_index as usize);
149    let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
150    let clip_pos = light_space_transform * world_pos.extend(1.0);
151    #[cfg(test)]
152    {
153        *out_comparison_info = ShadowMappingVertexInfo {
154            renderlet_id,
155            vertex_index,
156            vertex: _vertex,
157            transform: _transform,
158            model_matrix: _model_matrix,
159            world_pos,
160            view_projection: light_space_transform,
161            clip_pos,
162        };
163    }
164    *out_clip_pos = clip_pos;
165}
166
167#[spirv(fragment)]
168pub fn shadow_mapping_fragment(clip_pos: Vec4, frag_color: &mut Vec4) {
169    *frag_color = (clip_pos.xyz() / clip_pos.w).extend(1.0);
170}
171
172/// Contains values needed to determine the outgoing radiance of a fragment.
173///
174/// For more info, see the **Spotlight** section of the
175/// [learnopengl](https://learnopengl.com/Lighting/Light-casters)
176/// article.
177#[derive(Clone, Copy, Default, core::fmt::Debug)]
178pub struct SpotLightCalculation {
179    /// Position of the light in world space
180    pub light_position: Vec3,
181    /// Position of the fragment in world space
182    pub frag_position: Vec3,
183    /// Unit vector (LightDir) pointing from the fragment to the light
184    pub frag_to_light: Vec3,
185    /// Distance from the fragment to the light
186    pub frag_to_light_distance: f32,
187    /// Unit vector (SpotDir) direction that the light is pointing in
188    pub light_direction: Vec3,
189    /// The cosine of the cutoff angle (Phi ϕ) that specifies the spotlight's radius.
190    ///
191    /// Everything inside this angle is lit by the spotlight.
192    pub cos_inner_cutoff: f32,
193    /// The cosine of the cutoff angle (Gamma γ) that specifies the spotlight's outer radius.
194    ///
195    /// Everything outside this angle is not lit by the spotlight.
196    ///
197    /// Fragments between `inner_cutoff` and `outer_cutoff` have an intensity
198    /// between `1.0` and `0.0`.
199    pub cos_outer_cutoff: f32,
200    /// Whether the fragment is inside the `inner_cutoff` cone.
201    pub fragment_is_inside_inner_cone: bool,
202    /// Whether the fragment is inside the `outer_cutoff` cone.
203    pub fragment_is_inside_outer_cone: bool,
204    /// `outer_cutoff` - `inner_cutoff`
205    pub epsilon: f32,
206    /// Cosine of the angle (Theta θ) between `frag_to_light` (LightDir) vector and the
207    /// `light_direction` (SpotDir) vector.
208    ///
209    /// θ  should be smaller than `outer_cutoff` (Gamma γ) to be
210    /// inside the spotlight, but since these are all cosines of angles, we actually
211    /// compare using `>`.
212    pub cos_theta: f32,
213    pub contribution_unclamped: f32,
214    /// The intensity level between `0.0` and `1.0` that should be used to determine
215    /// outgoing radiance.
216    pub contribution: f32,
217}
218
219impl SpotLightCalculation {
220    /// Calculate the values required to determine outgoing radiance of a spot light.
221    pub fn new(
222        spot_light_descriptor: SpotLightDescriptor,
223        node_transform: Mat4,
224        fragment_world_position: Vec3,
225    ) -> Self {
226        let light_position = node_transform.transform_point3(spot_light_descriptor.position);
227        let frag_position = fragment_world_position;
228        let frag_to_light = light_position - frag_position;
229        let frag_to_light_distance = frag_to_light.length();
230        if frag_to_light_distance == 0.0 {
231            crate::println!("frag_to_light_distance: {frag_to_light_distance}");
232            return Self::default();
233        }
234        let frag_to_light = frag_to_light.alt_norm_or_zero();
235        let light_direction = node_transform
236            .transform_vector3(spot_light_descriptor.direction)
237            .alt_norm_or_zero();
238        let cos_inner_cutoff = spot_light_descriptor.inner_cutoff.cos();
239        let cos_outer_cutoff = spot_light_descriptor.outer_cutoff.cos();
240        let epsilon = cos_inner_cutoff - cos_outer_cutoff;
241        let cos_theta = frag_to_light.dot(-light_direction);
242        let fragment_is_inside_inner_cone = cos_theta > cos_inner_cutoff;
243        let fragment_is_inside_outer_cone = cos_theta > cos_outer_cutoff;
244        let contribution_unclamped = (cos_theta - cos_outer_cutoff) / epsilon;
245        let contribution = contribution_unclamped.clamp(0.0, 1.0);
246        Self {
247            light_position,
248            frag_position,
249            frag_to_light,
250            frag_to_light_distance,
251            light_direction,
252            cos_inner_cutoff,
253            cos_outer_cutoff,
254            fragment_is_inside_inner_cone,
255            fragment_is_inside_outer_cone,
256            epsilon,
257            cos_theta,
258            contribution_unclamped,
259            contribution,
260        }
261    }
262}
263
264/// Description of a spot light.
265///
266/// ## Tips
267///
268/// If your spotlight is not illuminating your scenery, ensure that the
269/// `inner_cutoff` and `outer_cutoff` values are "correct". `outer_cutoff`
270/// should be _greater than_ `inner_cutoff` and the values should be a large
271/// enough to cover at least one pixel at the distance between the light and
272/// the scenery.
273#[repr(C)]
274#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
275#[derive(Copy, Clone, SlabItem)]
276pub struct SpotLightDescriptor {
277    // TODO: add `range` to SpotLightDescriptor
278    // See <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md#light-shared-properties>
279    pub position: Vec3,
280    pub direction: Vec3,
281    pub inner_cutoff: f32,
282    pub outer_cutoff: f32,
283    pub color: Vec4,
284    pub intensity: Candela,
285}
286
287impl Default for SpotLightDescriptor {
288    fn default() -> Self {
289        let white = Vec4::splat(1.0);
290        let inner_cutoff = 0.077143565;
291        let outer_cutoff = 0.09075713;
292        let direction = Vec3::new(0.0, -1.0, 0.0);
293        let color = white;
294        let intensity = Candela::COMMON_WAX_CANDLE;
295
296        Self {
297            position: Default::default(),
298            direction,
299            inner_cutoff,
300            outer_cutoff,
301            color,
302            intensity,
303        }
304    }
305}
306
307impl SpotLightDescriptor {
308    pub fn shadow_mapping_projection_and_view(
309        &self,
310        parent_light_transform: &Mat4,
311        z_near: f32,
312        z_far: f32,
313    ) -> (Mat4, Mat4) {
314        let fovy = 2.0 * self.outer_cutoff;
315        let aspect = 1.0;
316        let projection = Mat4::perspective_rh(fovy, aspect, z_near, z_far);
317        let direction = parent_light_transform
318            .transform_vector3(self.direction)
319            .alt_norm_or_zero();
320        let position = parent_light_transform.transform_point3(self.position);
321        let up = direction.orthonormal_vectors()[0];
322        let view = Mat4::look_to_rh(position, direction, up);
323        (projection, view)
324    }
325}
326
327/// A unit of luminous intensity, lumen per steradian (lm/sr).
328///
329/// Candelas measure the luminous power per unit solid angle emitted in a
330/// particular direction. A common wax candle has a luminous intensity of
331/// roughly 1 candela.
332///
333/// The type provides a collection of const `Candela` lighting levels for use in
334/// constructing analytical lights.
335#[repr(transparent)]
336#[derive(Clone, Copy, Default, Debug, SlabItem)]
337pub struct Candela(pub f32);
338
339impl core::fmt::Display for Candela {
340    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
341        self.0.fmt(f)?;
342        if self.0 == 1.0 {
343            f.write_str(" candela (lm/sr)")
344        } else {
345            f.write_str(" candelas (lm/sr)")
346        }
347    }
348}
349
350impl Candela {
351    pub const COMMON_WAX_CANDLE: Self = Candela(1.0);
352}
353
354/// A unit of illuminance, lumen per meter squared (lm/m^2).
355///
356/// Lux measures the amount of light that falls on a surface, which is
357/// appropriate for directional lights, as they simulate light coming from a
358/// specific direction, like sunlight.
359///
360/// The type provides a collection of const Lux lighting levels for use in
361/// constructing analytical lights.
362#[repr(transparent)]
363#[derive(Clone, Copy, Default, Debug, SlabItem)]
364pub struct Lux(pub f32);
365
366impl core::fmt::Display for Lux {
367    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
368        self.0.fmt(f)?;
369        f.write_str(" lux (lm/m^2)")
370    }
371}
372
373impl Lux {
374    pub const OUTDOOR_TWILIGHT: Self = Lux(1.0);
375    pub const OUTDOOR_STREET_LIGHT_MIN: Self = Lux(5.0);
376    pub const OUTDOOR_SUNSET: Self = Lux(10.0);
377    pub const INDOOR_LOUNGE: Self = Lux(50.0);
378    pub const INDOOR_HALLWAY: Self = Lux(80.0);
379    pub const OUTDOOR_OVERCAST_LOW: Self = Lux(100.0);
380    pub const INDOOR_OFFICE_LOW: Self = Lux(320.0);
381    pub const OUTDOOR_SUNRISE_OR_SUNSET: Self = Lux(400.0);
382    pub const INDOOR_OFFICE_HIGH: Self = Lux(500.0);
383    pub const OUTDOOR_OVERCAST_HIGH: Self = Lux(1000.0);
384    pub const OUTDOOR_FOXS_WEDDING: Self = Lux(3000.0);
385    pub const OUTDOOR_FULL_DAYLIGHT_LOW: Self = Lux(10_000.0);
386    pub const OUTDOOR_FULL_DAYLIGHT_HIGH: Self = Lux(25_000.0);
387    pub const OUTDOOR_DIRECT_SUNLIGHT_LOW: Self = Lux(32_000.0);
388    pub const OUTDOOR_DIRECT_SUNLIGHT_HIGH: Self = Lux(130_000.0);
389}
390
391#[repr(C)]
392#[derive(Copy, Clone, SlabItem, Debug)]
393pub struct DirectionalLightDescriptor {
394    pub direction: Vec3,
395    pub color: Vec4,
396    /// Intensity of the directional light in lux (lm/m^2).
397    pub intensity: Lux,
398}
399
400impl Default for DirectionalLightDescriptor {
401    fn default() -> Self {
402        let direction = Vec3::new(0.0, -1.0, 0.0);
403        let color = Vec4::splat(1.0);
404        let intensity = Lux::OUTDOOR_TWILIGHT;
405
406        Self {
407            direction,
408            color,
409            intensity,
410        }
411    }
412}
413
414impl DirectionalLightDescriptor {
415    pub fn shadow_mapping_projection_and_view(
416        &self,
417        parent_light_transform: &Mat4,
418        // Near limits of the light's reach
419        //
420        // The maximum should be the `Camera`'s `Frustum::depth()`.
421        // TODO: in `DirectionalLightDescriptor::shadow_mapping_projection_and_view`, take Frustum
422        // as a parameter and then figure out the minimal view projection that includes that frustum
423        z_near: f32,
424        // Far limits of the light's reach
425        z_far: f32,
426    ) -> (Mat4, Mat4) {
427        crate::println!("descriptor: {self:#?}");
428        let depth = (z_far - z_near).abs();
429        let hd = depth * 0.5;
430        let projection = Mat4::orthographic_rh(-hd, hd, -hd, hd, z_near, z_far);
431        let direction = parent_light_transform
432            .transform_vector3(self.direction)
433            .alt_norm_or_zero();
434        let position = -direction * depth * 0.5;
435        crate::println!("direction: {direction}");
436        crate::println!("position: {position}");
437        let up = direction.orthonormal_vectors()[0];
438        let view = Mat4::look_to_rh(position, direction, up);
439        (projection, view)
440    }
441}
442
443#[repr(C)]
444#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
445#[derive(Copy, Clone, SlabItem)]
446pub struct PointLightDescriptor {
447    // TODO: add `range` to PointLightDescriptor
448    // See <https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual/README.md#light-shared-properties>
449    pub position: Vec3,
450    pub color: Vec4,
451    /// Intensity as candelas.
452    pub intensity: Candela,
453}
454
455impl Default for PointLightDescriptor {
456    fn default() -> Self {
457        let color = Vec4::splat(1.0);
458        let intensity = Candela::COMMON_WAX_CANDLE;
459
460        Self {
461            position: Default::default(),
462            color,
463            intensity,
464        }
465    }
466}
467
468impl PointLightDescriptor {
469    pub fn shadow_mapping_view_matrix(
470        &self,
471        face_index: usize,
472        parent_light_transform: &Mat4,
473    ) -> Mat4 {
474        let eye = parent_light_transform.transform_point3(self.position);
475        let mut face = CubemapFaceDirection::FACES[face_index];
476        face.eye = eye;
477        face.view()
478    }
479
480    pub fn shadow_mapping_projection_matrix(z_near: f32, z_far: f32) -> Mat4 {
481        Mat4::perspective_lh(core::f32::consts::FRAC_PI_2, 1.0, z_near, z_far)
482    }
483
484    pub fn shadow_mapping_projection_and_view_matrices(
485        &self,
486        parent_light_transform: &Mat4,
487        z_near: f32,
488        z_far: f32,
489    ) -> (Mat4, [Mat4; 6]) {
490        let p = Self::shadow_mapping_projection_matrix(z_near, z_far);
491        let eye = parent_light_transform.transform_point3(self.position);
492        (
493            p,
494            CubemapFaceDirection::FACES.map(|mut face| {
495                face.eye = eye;
496                face.view()
497            }),
498        )
499    }
500}
501
502/// Returns the radius of illumination in meters.
503///
504/// * Moonlight: < 1 lux.
505///   - Full moon on a clear night: 0.25 lux.
506///   - Quarter moon: 0.01 lux
507///   - Starlight overcast moonless night sky: 0.0001 lux.
508/// * General indoor lighting: Around 100 to 300 lux.                                   
509/// * Office lighting: Typically around 300 to 500 lux.                                 
510/// * Reading or task lighting: Around 500 to 750 lux.                                  
511/// * Detailed work (e.g., drafting, surgery): 1000 lux or more.
512pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminance_lux: f32) -> f32 {
513    (intensity_candelas / minimum_illuminance_lux).sqrt()
514}
515
516#[repr(u32)]
517#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
518#[derive(Copy, Clone, PartialEq)]
519pub enum LightStyle {
520    Directional = 0,
521    Point = 1,
522    Spot = 2,
523}
524
525impl core::fmt::Display for LightStyle {
526    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
527        match self {
528            LightStyle::Directional => f.write_str("directional"),
529            LightStyle::Point => f.write_str("point"),
530            LightStyle::Spot => f.write_str("spot"),
531        }
532    }
533}
534
535impl SlabItem for LightStyle {
536    const SLAB_SIZE: usize = { 1 };
537
538    fn read_slab(index: usize, slab: &[u32]) -> Self {
539        let proxy = u32::read_slab(index, slab);
540        match proxy {
541            0 => LightStyle::Directional,
542            1 => LightStyle::Point,
543            2 => LightStyle::Spot,
544            _ => LightStyle::Directional,
545        }
546    }
547
548    fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize {
549        let proxy = *self as u32;
550        proxy.write_slab(index, slab)
551    }
552}
553
554/// A generic light that is used as a slab pointer to a
555/// specific light type.
556#[repr(C)]
557#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
558#[derive(Copy, Clone, PartialEq, SlabItem)]
559pub struct LightDescriptor {
560    /// The type of the light
561    pub light_type: LightStyle,
562    /// The index of the light in the lighting slab
563    pub index: u32,
564    /// The id of a transform to apply to the position and direction of the light.
565    ///
566    /// This `Id` points to a transform on the lighting slab.
567    ///
568    /// The value of this descriptor can be synchronized with that of a node
569    /// transform on the geometry slab from
570    /// [`crate::light::AnalyticalLight::link_node_transform`].
571    pub transform_id: Id<TransformDescriptor>,
572    /// The id of the shadow map in use by this light.
573    pub shadow_map_desc_id: Id<ShadowMapDescriptor>,
574}
575
576impl Default for LightDescriptor {
577    fn default() -> Self {
578        Self {
579            light_type: LightStyle::Directional,
580            index: Id::<()>::NONE.inner(),
581            transform_id: Id::NONE,
582            shadow_map_desc_id: Id::NONE,
583        }
584    }
585}
586
587impl From<Id<DirectionalLightDescriptor>> for LightDescriptor {
588    fn from(id: Id<DirectionalLightDescriptor>) -> Self {
589        Self {
590            light_type: LightStyle::Directional,
591            index: id.inner(),
592            transform_id: Id::NONE,
593            shadow_map_desc_id: Id::NONE,
594        }
595    }
596}
597
598impl From<Id<SpotLightDescriptor>> for LightDescriptor {
599    fn from(id: Id<SpotLightDescriptor>) -> Self {
600        Self {
601            light_type: LightStyle::Spot,
602            index: id.inner(),
603            transform_id: Id::NONE,
604            shadow_map_desc_id: Id::NONE,
605        }
606    }
607}
608
609impl From<Id<PointLightDescriptor>> for LightDescriptor {
610    fn from(id: Id<PointLightDescriptor>) -> Self {
611        Self {
612            light_type: LightStyle::Point,
613            index: id.inner(),
614            transform_id: Id::NONE,
615            shadow_map_desc_id: Id::NONE,
616        }
617    }
618}
619
620impl LightDescriptor {
621    pub fn into_directional_id(self) -> Id<DirectionalLightDescriptor> {
622        Id::from(self.index)
623    }
624
625    pub fn into_spot_id(self) -> Id<SpotLightDescriptor> {
626        Id::from(self.index)
627    }
628
629    pub fn into_point_id(self) -> Id<PointLightDescriptor> {
630        Id::from(self.index)
631    }
632}
633
634/// Parameters to the shadow mapping calculation function.
635///
636/// This is mostly just to appease clippy.
637pub struct ShadowCalculation {
638    pub shadow_map_desc: ShadowMapDescriptor,
639    pub shadow_map_atlas_size: UVec2,
640    pub surface_normal_in_world_space: Vec3,
641    pub frag_pos_in_world_space: Vec3,
642    pub frag_to_light_in_world_space: Vec3,
643    pub bias_min: f32,
644    pub bias_max: f32,
645    pub pcf_samples: u32,
646}
647
648impl ShadowCalculation {
649    /// Reads various required parameters from the slab and creates a `ShadowCalculation`.
650    pub fn new(
651        light_slab: &[u32],
652        light: LightDescriptor,
653        in_pos: Vec3,
654        surface_normal: Vec3,
655        light_direction: Vec3,
656    ) -> Self {
657        let shadow_map_desc = light_slab.read_unchecked(light.shadow_map_desc_id);
658        let atlas_size = {
659            let lighting_desc_id = Id::<LightingDescriptor>::new(0);
660            let atlas_desc_id = light_slab.read_unchecked(
661                lighting_desc_id + LightingDescriptor::OFFSET_OF_SHADOW_MAP_ATLAS_DESCRIPTOR_ID,
662            );
663            let atlas_desc = light_slab.read_unchecked(atlas_desc_id);
664            atlas_desc.size
665        };
666
667        ShadowCalculation {
668            shadow_map_desc,
669            shadow_map_atlas_size: atlas_size.xy(),
670            surface_normal_in_world_space: surface_normal,
671            frag_pos_in_world_space: in_pos,
672            frag_to_light_in_world_space: light_direction,
673            bias_min: shadow_map_desc.bias_min,
674            bias_max: shadow_map_desc.bias_max,
675            pcf_samples: shadow_map_desc.pcf_samples,
676        }
677    }
678
679    fn get_atlas_texture_at(&self, light_slab: &[u32], index: usize) -> AtlasTextureDescriptor {
680        let atlas_texture_id =
681            light_slab.read_unchecked(self.shadow_map_desc.atlas_textures_array.at(index));
682        light_slab.read_unchecked(atlas_texture_id)
683    }
684
685    fn get_frag_pos_in_light_space(&self, light_slab: &[u32], index: usize) -> Vec3 {
686        let light_space_transform_id = self.shadow_map_desc.light_space_transforms_array.at(index);
687        let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
688        light_space_transform.project_point3(self.frag_pos_in_world_space)
689    }
690
691    /// Returns shadow _intensity_ for directional and spot lights.
692    ///
693    /// Returns `0.0` when the fragment is in full light.
694    /// Returns `1.0` when the fragment is in full shadow.
695    pub fn run_directional_or_spot<T, S>(
696        &self,
697        light_slab: &[u32],
698        shadow_map: &T,
699        shadow_map_sampler: &S,
700    ) -> f32
701    where
702        S: IsSampler,
703        T: Sample2dArray<Sampler = S>,
704    {
705        let ShadowCalculation {
706            shadow_map_desc: _,
707            shadow_map_atlas_size,
708            frag_pos_in_world_space: _,
709            surface_normal_in_world_space: surface_normal,
710            frag_to_light_in_world_space: light_direction,
711            bias_min,
712            bias_max,
713            pcf_samples,
714        } = self;
715        let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, 0);
716        crate::println!("frag_pos_in_light_space: {frag_pos_in_light_space}");
717        if !crate::math::is_inside_clip_space(frag_pos_in_light_space.xyz()) {
718            return 0.0;
719        }
720        // The range of coordinates in the light's clip space is -1.0 to 1.0 for x and y,
721        // but the texture space is [0, 1], and Y increases downward, so we do this
722        // conversion to flip Y and also normalize to the range [0.0, 1.0].
723        // Z should already be 0.0 to 1.0.
724        let proj_coords_uv = (frag_pos_in_light_space.xy() * Vec2::new(1.0, -1.0)
725            + Vec2::splat(1.0))
726            * Vec2::splat(0.5);
727        crate::println!("proj_coords_uv: {proj_coords_uv}");
728
729        let shadow_map_atlas_texture = self.get_atlas_texture_at(light_slab, 0);
730        // With these projected coordinates we can sample the depth map as the
731        // resulting [0,1] coordinates from proj_coords directly correspond to
732        // the transformed NDC coordinates from the `ShadowMap::update` render pass.
733        // This gives us the closest depth from the light's point of view:
734        let pcf_samples_2 = *pcf_samples as i32 / 2;
735        let texel_size = 1.0
736            / Vec2::new(
737                shadow_map_atlas_texture.size_px.x as f32,
738                shadow_map_atlas_texture.size_px.y as f32,
739            );
740        let mut shadow = 0.0f32;
741        let mut total = 0.0f32;
742        for x in -pcf_samples_2..=pcf_samples_2 {
743            for y in -pcf_samples_2..=pcf_samples_2 {
744                let proj_coords = shadow_map_atlas_texture.uv(
745                    proj_coords_uv + Vec2::new(x as f32, y as f32) * texel_size,
746                    *shadow_map_atlas_size,
747                );
748                let shadow_map_depth = shadow_map
749                    .sample_by_lod(*shadow_map_sampler, proj_coords, 0.0)
750                    .x;
751                // To get the current depth at this fragment we simply retrieve the projected vector's z
752                // coordinate which equals the depth of this fragment from the light's perspective.
753                let fragment_depth = frag_pos_in_light_space.z;
754
755                // If the `current_depth`, which is the depth of the fragment from the lights POV, is
756                // greater than the `closest_depth` of the shadow map at that fragment, the fragment
757                // is in shadow
758                crate::println!("current_depth: {fragment_depth}");
759                crate::println!("closest_depth: {shadow_map_depth}");
760                let bias = (bias_max * (1.0 - surface_normal.dot(*light_direction))).max(*bias_min);
761
762                if (fragment_depth - bias) >= shadow_map_depth {
763                    shadow += 1.0
764                }
765                total += 1.0;
766            }
767        }
768        shadow / total.max(1.0)
769    }
770
771    pub const POINT_SAMPLE_OFFSET_DIRECTIONS: [Vec3; 21] = [
772        Vec3::ZERO,
773        Vec3::new(1.0, 1.0, 1.0),
774        Vec3::new(1.0, -1.0, 1.0),
775        Vec3::new(-1.0, -1.0, 1.0),
776        Vec3::new(-1.0, 1.0, 1.0),
777        Vec3::new(1.0, 1.0, -1.0),
778        Vec3::new(1.0, -1.0, -1.0),
779        Vec3::new(-1.0, -1.0, -1.0),
780        Vec3::new(-1.0, 1.0, -1.0),
781        Vec3::new(1.0, 1.0, 0.0),
782        Vec3::new(1.0, -1.0, 0.0),
783        Vec3::new(-1.0, -1.0, 0.0),
784        Vec3::new(-1.0, 1.0, 0.0),
785        Vec3::new(1.0, 0.0, 1.0),
786        Vec3::new(-1.0, 0.0, 1.0),
787        Vec3::new(1.0, 0.0, -1.0),
788        Vec3::new(-1.0, 0.0, -1.0),
789        Vec3::new(0.0, 1.0, 1.0),
790        Vec3::new(0.0, -1.0, 1.0),
791        Vec3::new(0.0, -1.0, -1.0),
792        Vec3::new(0.0, 1.0, -1.0),
793    ];
794    /// Returns shadow _intensity_ for point lights.
795    ///
796    /// Returns `0.0` when the fragment is in full light.
797    /// Returns `1.0` when the fragment is in full shadow.
798    pub fn run_point<T, S>(
799        &self,
800        light_slab: &[u32],
801        shadow_map: &T,
802        shadow_map_sampler: &S,
803        light_pos_in_world_space: Vec3,
804    ) -> f32
805    where
806        S: IsSampler,
807        T: Sample2dArray<Sampler = S>,
808    {
809        let ShadowCalculation {
810            shadow_map_desc,
811            shadow_map_atlas_size,
812            frag_pos_in_world_space,
813            surface_normal_in_world_space: surface_normal,
814            frag_to_light_in_world_space: frag_to_light,
815            bias_min,
816            bias_max,
817            pcf_samples,
818        } = self;
819
820        let light_to_frag_dir = frag_pos_in_world_space - light_pos_in_world_space;
821        crate::println!("light_to_frag_dir: {light_to_frag_dir}");
822
823        let pcf_samplesf = (*pcf_samples as f32)
824            .max(1.0)
825            .min(Self::POINT_SAMPLE_OFFSET_DIRECTIONS.len() as f32);
826        let pcf_samples = pcf_samplesf as usize;
827        let view_distance = light_to_frag_dir.length();
828        let disk_radius = (1.0 + view_distance / shadow_map_desc.z_far) / 25.0;
829        let mut shadow = 0.0f32;
830        for i in 0..pcf_samples {
831            let sample_offset = Self::POINT_SAMPLE_OFFSET_DIRECTIONS[i] * disk_radius;
832            crate::println!("sample_offset: {sample_offset}");
833            let sample_dir = (light_to_frag_dir + sample_offset).alt_norm_or_zero();
834            let (face_index, uv) = CubemapDescriptor::get_face_index_and_uv(sample_dir);
835            crate::println!("face_index: {face_index}",);
836            crate::println!("uv: {uv}");
837            let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, face_index);
838            let face_texture = self.get_atlas_texture_at(light_slab, face_index);
839            let uv_tex = face_texture.uv(uv, *shadow_map_atlas_size);
840            let shadow_map_depth = shadow_map.sample_by_lod(*shadow_map_sampler, uv_tex, 0.0).x;
841            let fragment_depth = frag_pos_in_light_space.z;
842            let bias = (bias_max * (1.0 - surface_normal.dot(*frag_to_light))).max(*bias_min);
843            if (fragment_depth - bias) > shadow_map_depth {
844                shadow += 1.0
845            }
846        }
847
848        shadow / pcf_samplesf
849    }
850}
851
852/// Depth pre-pass for the light tiling feature.
853///
854/// This shader writes all staged [`PrimitiveDescriptor`]'s depth into a buffer.
855///
856/// This shader is very much like [`shadow_mapping_vertex`], except that
857/// shader gets its projection+view matrix from the light stored in a
858/// `ShadowMapDescriptor`.
859///
860/// Here we want to render as normal forward pass would, with the `PrimitiveDescriptor`
861/// and the `Camera`'s view projection matrix.
862/// ## Note
863/// This shader will likely be expanded to include parts of occlusion culling and order
864/// independent transparency.
865#[spirv(vertex)]
866pub fn light_tiling_depth_pre_pass(
867    // Points at a `Renderlet`.
868    #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
869    // Which vertex within the renderlet are we rendering?
870    #[spirv(vertex_index)] vertex_index: u32,
871    // The slab where the renderlet's geometry is staged
872    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
873    // Output clip coords
874    #[spirv(position)] out_clip_pos: &mut Vec4,
875) {
876    let renderlet = geometry_slab.read_unchecked(renderlet_id);
877    if !renderlet.visible {
878        // put it outside the clipping frustum
879        *out_clip_pos = Vec3::splat(100.0).extend(1.0);
880        return;
881    }
882
883    let camera_id = geometry_slab
884        .read_unchecked(Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID);
885    let camera = geometry_slab.read_unchecked(camera_id);
886
887    let VertexInfo { world_pos, .. } = renderlet.get_vertex_info(vertex_index, geometry_slab);
888
889    *out_clip_pos = camera.view_projection() * world_pos.extend(1.0);
890}
891
892pub type DepthImage2d = Image!(2D, type=f32, sampled, depth);
893
894pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth, multisampled=true);
895
896/// A tile of screen space used to cull lights.
897#[derive(Clone, Copy, Default, SlabItem)]
898#[offsets]
899pub struct LightTile {
900    /// Minimum depth of objects found within the frustum of the tile.
901    pub depth_min: u32,
902    /// Maximum depth of objects foudn within the frustum of the tile.
903    pub depth_max: u32,
904    /// The count of lights in this tile.
905    ///
906    /// Also, the next available light index.
907    pub next_light_index: u32,
908    /// List of light ids that intersect this tile's frustum.
909    pub lights_array: Array<Id<LightDescriptor>>,
910}
911
912impl core::fmt::Debug for LightTile {
913    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
914        f.debug_struct("LightTile")
915            .field("depth_min", &dequantize_depth_u32_to_f32(self.depth_min))
916            .field("depth_max", &dequantize_depth_u32_to_f32(self.depth_max))
917            .field("next_light_index", &self.next_light_index)
918            .field("lights_array", &self.lights_array)
919            .finish()
920    }
921}
922
923/// Descriptor of the light tiling operation, which culls lights by accumulating
924/// them into lists that illuminate tiles of the screen.
925#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
926pub struct LightTilingDescriptor {
927    /// Size of the [`Stage`](crate::stage::Stage)'s depth texture.
928    pub depth_texture_size: UVec2,
929    /// Configurable tile size.
930    pub tile_size: u32,
931    /// Array pointing to the lighting "tiles".
932    pub tiles_array: Array<LightTile>,
933    /// Minimum illuminance.
934    ///
935    /// Used to determine whether a light illuminates a tile.
936    pub minimum_illuminance_lux: f32,
937}
938
939impl Default for LightTilingDescriptor {
940    fn default() -> Self {
941        Self {
942            depth_texture_size: Default::default(),
943            tile_size: 16,
944            tiles_array: Default::default(),
945            minimum_illuminance_lux: 0.1,
946        }
947    }
948}
949
950impl LightTilingDescriptor {
951    /// Returns the dimensions of the grid of tiles.
952    pub fn tile_grid_size(&self) -> UVec2 {
953        let dims_f32 = self.depth_texture_size.as_vec2() / self.tile_size as f32;
954        dims_f32.ceil().as_uvec2()
955    }
956
957    pub fn tile_coord_for_fragment(&self, frag_coord: Vec2) -> UVec2 {
958        let frag_coord = frag_coord.as_uvec2();
959        frag_coord / self.tile_size
960    }
961
962    pub fn tile_index_for_fragment(&self, frag_coord: Vec2) -> usize {
963        let tile_coord = self.tile_coord_for_fragment(frag_coord);
964        let tile_dimensions = self.tile_grid_size();
965        (tile_coord.y * tile_dimensions.x + tile_coord.x) as usize
966    }
967}
968
969/// Quantizes a fragment depth from `f32` to `u32`.
970pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 {
971    (u32::MAX as f32 * depth).round() as u32
972}
973
974/// Reconstructs a previously quantized depth from a `u32`.
975pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 {
976    depth as f32 / u32::MAX as f32
977}
978
979/// Helper for determining the next light to check during an
980/// invocation of the light list computation.
981pub(crate) struct NextLightIndex {
982    current_step: usize,
983    tile_size: u32,
984    lights: Array<Id<LightDescriptor>>,
985    global_id: UVec3,
986}
987
988impl Iterator for NextLightIndex {
989    type Item = Id<Id<LightDescriptor>>;
990
991    fn next(&mut self) -> Option<Self::Item> {
992        let next_index = self.next_index();
993        self.current_step += 1;
994        if next_index < self.lights.len() {
995            Some(self.lights.at(next_index))
996        } else {
997            None
998        }
999    }
1000}
1001
1002impl NextLightIndex {
1003    pub fn new(
1004        global_id: UVec3,
1005        tile_size: u32,
1006        analytical_lights_array: Array<Id<LightDescriptor>>,
1007    ) -> Self {
1008        Self {
1009            current_step: 0,
1010            tile_size,
1011            lights: analytical_lights_array,
1012            global_id,
1013        }
1014    }
1015
1016    pub fn next_index(&self) -> usize {
1017        // Determine the xy coord of this invocation within the _tile_
1018        let frag_tile_xy = self.global_id.xy() % self.tile_size;
1019        // Determine the index of this invocation within the _tile_
1020        let offset = frag_tile_xy.y * self.tile_size + frag_tile_xy.x;
1021        let stride = (self.tile_size * self.tile_size) as usize;
1022        self.current_step * stride + offset as usize
1023    }
1024}
1025
1026struct LightTilingInvocation {
1027    global_id: UVec3,
1028    descriptor: LightTilingDescriptor,
1029}
1030
1031impl LightTilingInvocation {
1032    fn new(global_id: UVec3, descriptor: LightTilingDescriptor) -> Self {
1033        Self {
1034            global_id,
1035            descriptor,
1036        }
1037    }
1038
1039    /// The fragment's position.
1040    ///
1041    /// X range is 0 to (width - 1), Y range is 0 to (height - 1).
1042    fn frag_pos(&self) -> UVec2 {
1043        self.global_id.xy()
1044    }
1045
1046    /// The number of tiles in X and Y within the depth texture.
1047    fn tile_grid_size(&self) -> UVec2 {
1048        self.descriptor.tile_grid_size()
1049    }
1050
1051    /// The tile's coordinate among all tiles in the tile grid.
1052    ///
1053    /// The units are in tile x y.
1054    fn tile_coord(&self) -> UVec2 {
1055        self.global_id.xy() / self.descriptor.tile_size
1056    }
1057
1058    /// The tile's index in all the [`LightTilingDescriptor`]'s `tile_array`.
1059    fn tile_index(&self) -> usize {
1060        let tile_pos = self.tile_coord();
1061        let tile_dimensions = self.tile_grid_size();
1062        (tile_pos.y * tile_dimensions.x + tile_pos.x) as usize
1063    }
1064
1065    /// The tile's normalized midpoint.
1066    fn tile_ndc_midpoint(&self) -> Vec2 {
1067        let min_coord = self.tile_coord().as_vec2();
1068        let mid_coord = min_coord + 0.5;
1069        crate::math::convert_pixel_to_ndc(mid_coord, self.tile_grid_size())
1070    }
1071
1072    /// Compute the min and max depth of one fragment/invocation for light tiling.
1073    ///
1074    /// The min and max is stored in a tile on lighting slab.
1075    fn compute_min_and_max_depth(
1076        &self,
1077        depth_texture: &impl Fetch<UVec2, Output = Vec4>,
1078        lighting_slab: &mut [u32],
1079    ) {
1080        let frag_pos = self.frag_pos();
1081        let depth_texture_size = self.descriptor.depth_texture_size;
1082        if frag_pos.x >= depth_texture_size.x || frag_pos.y >= depth_texture_size.y {
1083            return;
1084        }
1085        // Depth frag value at the fragment position
1086        let frag_depth: f32 = depth_texture.fetch(frag_pos).x;
1087        // Fragment depth scaled to min/max of u32 values
1088        //
1089        // This is so we can compare with normal atomic ops instead of using the float extension
1090        let frag_depth_u32 = quantize_depth_f32_to_u32(frag_depth);
1091
1092        // The tile's index in all the tiles
1093        let tile_index = self.tile_index();
1094        let lighting_desc = lighting_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
1095        let tiling_desc = lighting_slab.read_unchecked(lighting_desc.light_tiling_descriptor_id);
1096        // index of the tile's min depth atomic value in the lighting slab
1097        let tile_id = tiling_desc.tiles_array.at(tile_index);
1098        let min_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MIN;
1099        // index of the tile's max depth atomic value in the lighting slab
1100        let max_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MAX;
1101
1102        let _prev_min_depth = crate::sync::atomic_u_min::<
1103            { spirv_std::memory::Scope::Workgroup as u32 },
1104            { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1105        >(lighting_slab, min_depth_index, frag_depth_u32);
1106        let _prev_max_depth = crate::sync::atomic_u_max::<
1107            { spirv_std::memory::Scope::Workgroup as u32 },
1108            { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1109        >(lighting_slab, max_depth_index, frag_depth_u32);
1110    }
1111
1112    /// Determine whether this invocation should run.
1113    fn should_invoke(&self) -> bool {
1114        self.global_id.x < self.descriptor.depth_texture_size.x
1115            && self.global_id.y < self.descriptor.depth_texture_size.y
1116    }
1117
1118    /// Clears one tile.
1119    ///
1120    /// ## Note
1121    /// This is only valid to call from the [`light_tiling_clear_tiles`] shader.
1122    fn clear_tile(&self, lighting_slab: &mut [u32]) {
1123        let dimensions = self.tile_grid_size();
1124        let index = (self.global_id.y * dimensions.x + self.global_id.x) as usize;
1125        if index < self.descriptor.tiles_array.len() {
1126            let tile_id = self.descriptor.tiles_array.at(index);
1127            let mut tile = lighting_slab.read(tile_id);
1128            tile.depth_min = u32::MAX;
1129            tile.depth_max = 0;
1130            tile.next_light_index = 0;
1131            lighting_slab.write(tile_id, &tile);
1132            // Zero out the light list and the ratings
1133            for id in tile.lights_array.iter() {
1134                lighting_slab.write(id, &Id::NONE);
1135            }
1136        }
1137    }
1138
1139    // The difficulty here is that in SPIRV we can access `lighting_slab` atomically without wrapping it
1140    // in a type, but on CPU we must pass an array of (something like) `AtomicU32`. I'm not sure how to
1141    // model this interaction to test it on the CPU.
1142    fn compute_light_lists(&self, geometry_slab: &[u32], lighting_slab: &mut [u32]) {
1143        let index = self.tile_index();
1144        let tile_id = self.descriptor.tiles_array.at(index);
1145        // Construct the tile's frustum in clip space.
1146        let depth_min_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MIN);
1147        let depth_max_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MAX);
1148        let depth_min = dequantize_depth_u32_to_f32(depth_min_u32);
1149        let depth_max = dequantize_depth_u32_to_f32(depth_max_u32);
1150
1151        if depth_min == depth_max {
1152            // If we would construct a frustum with zero volume, abort.
1153            //
1154            // See <http://renderling.xyz/articles/live/light_tiling.html#zero-volume-frustum-optimization>
1155            // for more info.
1156            return;
1157        }
1158
1159        let camera_id = geometry_slab.read_unchecked(
1160            Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID,
1161        );
1162        let camera = geometry_slab.read_unchecked(camera_id);
1163
1164        // let (ndc_tile_min, ndc_tile_max) = self.tile_ndc_min_max();
1165        // // This is the AABB frustum, in NDC coords
1166        // let ndc_tile_aabb = Aabb::new(
1167        //     ndc_tile_min.extend(depth_min),
1168        //     ndc_tile_max.extend(depth_max),
1169        // );
1170
1171        // Get the frustum (here simplified to a line) in world coords, since we'll be
1172        // using it to compare against the radius of illumination of each light
1173        let tile_ndc_midpoint = self.tile_ndc_midpoint();
1174        let tile_line_ndc = (
1175            tile_ndc_midpoint.extend(depth_min),
1176            tile_ndc_midpoint.extend(depth_max),
1177        );
1178        let inverse_viewproj = camera.view_projection().inverse();
1179        let tile_line = (
1180            inverse_viewproj.project_point3(tile_line_ndc.0),
1181            inverse_viewproj.project_point3(tile_line_ndc.1),
1182        );
1183
1184        let tile_index = self.tile_index();
1185        let tile_id = self.descriptor.tiles_array.at(tile_index);
1186        let tile_lights_array = lighting_slab.read(tile_id + LightTile::OFFSET_OF_LIGHTS_ARRAY);
1187        let next_light_id = tile_id + LightTile::OFFSET_OF_NEXT_LIGHT_INDEX;
1188
1189        // List of all analytical lights in the scene
1190        let analytical_lights_array = lighting_slab.read_unchecked(
1191            Id::<LightingDescriptor>::new(0)
1192                + LightingDescriptor::OFFSET_OF_ANALYTICAL_LIGHTS_ARRAY,
1193        );
1194
1195        // Each invocation will calculate a few lights' contribution to the tile, until all lights
1196        // have been visited
1197        let next_light = NextLightIndex::new(
1198            self.global_id,
1199            self.descriptor.tile_size,
1200            analytical_lights_array,
1201        );
1202        for id_of_light_id in next_light {
1203            let light_id = lighting_slab.read_unchecked(id_of_light_id);
1204            let light = lighting_slab.read_unchecked(light_id);
1205            let transform = lighting_slab.read(light.transform_id);
1206            // Get the distance to the light in world coords, and the
1207            // intensity of the light.
1208            let (distance, intensity_candelas) = match light.light_type {
1209                LightStyle::Directional => {
1210                    let directional_light = lighting_slab.read(light.into_directional_id());
1211                    // Very hand-wavey conversion
1212                    (0.0, directional_light.intensity.0 / 683.0)
1213                }
1214                LightStyle::Point => {
1215                    let point_light = lighting_slab.read(light.into_point_id());
1216                    let center = Mat4::from(transform).transform_point3(point_light.position);
1217                    let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1218                    // Again, very hand-wavey
1219                    (distance, point_light.intensity.0 / 683.0)
1220                }
1221                LightStyle::Spot => {
1222                    // TODO: take into consideration the direction the spot light is pointing
1223                    let spot_light = lighting_slab.read(light.into_spot_id());
1224                    let center = Mat4::from(transform).transform_point3(spot_light.position);
1225                    let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1226                    // Again, very hand-wavey
1227                    (distance, spot_light.intensity.0 / 683.0)
1228                }
1229            };
1230
1231            let radius =
1232                radius_of_illumination(intensity_candelas, self.descriptor.minimum_illuminance_lux);
1233            let should_add = radius >= distance;
1234            if should_add {
1235                // If the light should be added to the bin, get the next available index in the bin,
1236                // then write the id of the light into that index.
1237                let next_index = crate::sync::atomic_i_increment::<
1238                    { spirv_std::memory::Scope::Workgroup as u32 },
1239                    { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1240                >(lighting_slab, next_light_id);
1241                if next_index as usize >= tile_lights_array.len() {
1242                    // We've already filled the bin, so abort.
1243                    //
1244                    // TODO: Figure out a better way to handle light tile list overrun.
1245                    break;
1246                } else {
1247                    // Get the id that corresponds to the next available index in the ratings bin
1248                    let binned_light_id = tile_lights_array.at(next_index as usize);
1249                    // Write to that location
1250                    lighting_slab.write(binned_light_id, &light_id);
1251                }
1252            }
1253        }
1254    }
1255}
1256
1257#[spirv(compute(threads(16, 16, 1)))]
1258pub fn light_tiling_clear_tiles(
1259    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1260    #[spirv(global_invocation_id)] global_id: UVec3,
1261) {
1262    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1263    let light_tiling_descriptor =
1264        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1265    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1266    invocation.clear_tile(lighting_slab);
1267}
1268
1269/// Compute the min and max depth value for a tile.
1270///
1271/// This shader must be called **once for each fragment in the depth texture**.
1272#[spirv(compute(threads(16, 16, 1)))]
1273pub fn light_tiling_compute_tile_min_and_max_depth(
1274    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1275    #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2d,
1276    #[spirv(global_invocation_id)] global_id: UVec3,
1277) {
1278    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1279    let light_tiling_descriptor =
1280        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1281    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1282    invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1283}
1284
1285/// Compute the min and max depth value for a tile, multisampled.
1286///
1287/// This shader must be called **once for each fragment in the depth texture**.
1288#[spirv(compute(threads(16, 16, 1)))]
1289pub fn light_tiling_compute_tile_min_and_max_depth_multisampled(
1290    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1291    #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2dMultisampled,
1292    #[spirv(global_invocation_id)] global_id: UVec3,
1293) {
1294    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1295    let light_tiling_descriptor =
1296        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1297    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1298    invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1299}
1300
1301#[spirv(compute(threads(16, 16, 1)))]
1302pub fn light_tiling_bin_lights(
1303    #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
1304    #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1305    #[spirv(global_invocation_id)] global_id: UVec3,
1306) {
1307    let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1308    let light_tiling_descriptor =
1309        lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1310    let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1311    if invocation.should_invoke() {
1312        invocation.compute_light_lists(geometry_slab, lighting_slab);
1313    }
1314}