1use 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 pub analytical_lights_array: Array<Id<LightDescriptor>>,
43 pub shadow_map_atlas_descriptor_id: Id<AtlasDescriptor>,
45 pub update_shadow_map_id: Id<ShadowMapDescriptor>,
50 pub update_shadow_map_texture_index: u32,
52 pub light_tiling_descriptor_id: Id<LightTilingDescriptor>,
55 pub ambient_color: Vec4,
63}
64
65#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
66pub struct ShadowMapDescriptor {
67 pub light_space_transforms_array: Array<Mat4>,
68 pub z_near: f32,
70 pub z_far: f32,
72 pub atlas_textures_array: Array<Id<AtlasTextureDescriptor>>,
78 pub bias_min: f32,
79 pub bias_max: f32,
80 pub pcf_samples: u32,
81}
82
83impl Default for ShadowMapDescriptor {
84 fn default() -> Self {
85 Self {
86 light_space_transforms_array: Default::default(),
87 z_near: Default::default(),
88 z_far: Default::default(),
89 atlas_textures_array: Default::default(),
90 bias_min: 0.0005,
91 bias_max: 0.005,
92 pcf_samples: 4,
93 }
94 }
95}
96
97#[cfg(test)]
98#[derive(Default, Debug, Clone, Copy, PartialEq)]
99pub struct ShadowMappingVertexInfo {
100 pub renderlet_id: Id<PrimitiveDescriptor>,
101 pub vertex_index: u32,
102 pub vertex: crate::geometry::Vertex,
103 pub transform: TransformDescriptor,
104 pub model_matrix: Mat4,
105 pub world_pos: Vec3,
106 pub view_projection: Mat4,
107 pub clip_pos: Vec4,
108}
109
110#[spirv(vertex)]
124#[allow(clippy::too_many_arguments)]
125pub fn shadow_mapping_vertex(
126 #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
128 #[spirv(vertex_index)] vertex_index: u32,
130 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
132 #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] light_slab: &[u32],
134
135 #[spirv(position)] out_clip_pos: &mut Vec4,
136 #[cfg(test)] out_comparison_info: &mut ShadowMappingVertexInfo,
137) {
138 let renderlet = geometry_slab.read_unchecked(renderlet_id);
139 if !renderlet.visible {
140 *out_clip_pos = Vec4::new(100.0, 100.0, 100.0, 1.0);
142 return;
143 }
144
145 let VertexInfo {
146 world_pos,
147 vertex: _vertex,
148 transform: _transform,
149 model_matrix: _model_matrix,
150 } = renderlet.get_vertex_info(vertex_index, geometry_slab);
151
152 let lighting_desc = light_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
153 let shadow_desc = light_slab.read_unchecked(lighting_desc.update_shadow_map_id);
154 let light_space_transform_id = shadow_desc
155 .light_space_transforms_array
156 .at(lighting_desc.update_shadow_map_texture_index as usize);
157 let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
158 let clip_pos = light_space_transform * world_pos.extend(1.0);
159 #[cfg(test)]
160 {
161 *out_comparison_info = ShadowMappingVertexInfo {
162 renderlet_id,
163 vertex_index,
164 vertex: _vertex,
165 transform: _transform,
166 model_matrix: _model_matrix,
167 world_pos,
168 view_projection: light_space_transform,
169 clip_pos,
170 };
171 }
172 *out_clip_pos = clip_pos;
173}
174
175#[spirv(fragment)]
176pub fn shadow_mapping_fragment(clip_pos: Vec4, frag_color: &mut Vec4) {
177 *frag_color = (clip_pos.xyz() / clip_pos.w).extend(1.0);
178}
179
180#[derive(Clone, Copy, Default, core::fmt::Debug)]
186pub struct SpotLightCalculation {
187 pub light_position: Vec3,
189 pub frag_position: Vec3,
191 pub frag_to_light: Vec3,
193 pub frag_to_light_distance: f32,
195 pub light_direction: Vec3,
197 pub cos_inner_cutoff: f32,
202 pub cos_outer_cutoff: f32,
210 pub fragment_is_inside_inner_cone: bool,
212 pub fragment_is_inside_outer_cone: bool,
214 pub epsilon: f32,
216 pub cos_theta: f32,
223 pub contribution_unclamped: f32,
224 pub contribution: f32,
227}
228
229impl SpotLightCalculation {
230 pub fn new(
233 spot_light_descriptor: SpotLightDescriptor,
234 node_transform: Mat4,
235 fragment_world_position: Vec3,
236 ) -> Self {
237 let light_position = node_transform.transform_point3(spot_light_descriptor.position);
238 let frag_position = fragment_world_position;
239 let frag_to_light = light_position - frag_position;
240 let frag_to_light_distance = frag_to_light.length();
241 if frag_to_light_distance == 0.0 {
242 crate::println!("frag_to_light_distance: {frag_to_light_distance}");
243 return Self::default();
244 }
245 let frag_to_light = frag_to_light.alt_norm_or_zero();
246 let light_direction = node_transform
247 .transform_vector3(spot_light_descriptor.direction)
248 .alt_norm_or_zero();
249 let cos_inner_cutoff = spot_light_descriptor.inner_cutoff.cos();
250 let cos_outer_cutoff = spot_light_descriptor.outer_cutoff.cos();
251 let epsilon = cos_inner_cutoff - cos_outer_cutoff;
252 let cos_theta = frag_to_light.dot(-light_direction);
253 let fragment_is_inside_inner_cone = cos_theta > cos_inner_cutoff;
254 let fragment_is_inside_outer_cone = cos_theta > cos_outer_cutoff;
255 let contribution_unclamped = (cos_theta - cos_outer_cutoff) / epsilon;
256 let contribution = contribution_unclamped.clamp(0.0, 1.0);
257 Self {
258 light_position,
259 frag_position,
260 frag_to_light,
261 frag_to_light_distance,
262 light_direction,
263 cos_inner_cutoff,
264 cos_outer_cutoff,
265 fragment_is_inside_inner_cone,
266 fragment_is_inside_outer_cone,
267 epsilon,
268 cos_theta,
269 contribution_unclamped,
270 contribution,
271 }
272 }
273}
274
275#[repr(C)]
285#[derive(Copy, Clone, SlabItem, core::fmt::Debug)]
286pub struct SpotLightDescriptor {
287 pub position: Vec3,
290 pub direction: Vec3,
291 pub inner_cutoff: f32,
292 pub outer_cutoff: f32,
293 pub color: Vec4,
294 pub intensity: Candela,
295}
296
297impl Default for SpotLightDescriptor {
298 fn default() -> Self {
299 let white = Vec4::splat(1.0);
300 let inner_cutoff = 0.077143565;
301 let outer_cutoff = 0.09075713;
302 let direction = Vec3::new(0.0, -1.0, 0.0);
303 let color = white;
304 let intensity = Candela::COMMON_WAX_CANDLE;
305
306 Self {
307 position: Default::default(),
308 direction,
309 inner_cutoff,
310 outer_cutoff,
311 color,
312 intensity,
313 }
314 }
315}
316
317impl SpotLightDescriptor {
318 pub fn shadow_mapping_projection_and_view(
319 &self,
320 parent_light_transform: &Mat4,
321 z_near: f32,
322 z_far: f32,
323 ) -> (Mat4, Mat4) {
324 let fovy = 2.0 * self.outer_cutoff;
325 let aspect = 1.0;
326 let projection = Mat4::perspective_rh(fovy, aspect, z_near, z_far);
327 let direction = parent_light_transform
328 .transform_vector3(self.direction)
329 .alt_norm_or_zero();
330 let position = parent_light_transform.transform_point3(self.position);
331 let up = direction.orthonormal_vectors()[0];
332 let view = Mat4::look_to_rh(position, direction, up);
333 (projection, view)
334 }
335}
336
337#[repr(transparent)]
346#[derive(Clone, Copy, Default, Debug, SlabItem)]
347pub struct Candela(pub f32);
348
349impl core::fmt::Display for Candela {
350 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
351 self.0.fmt(f)?;
352 if self.0 == 1.0 {
353 f.write_str(" candela (lm/sr)")
354 } else {
355 f.write_str(" candelas (lm/sr)")
356 }
357 }
358}
359
360impl Candela {
361 pub const COMMON_WAX_CANDLE: Self = Candela(1.0);
362}
363
364#[repr(transparent)]
373#[derive(Clone, Copy, Default, Debug, SlabItem)]
374pub struct Lux(pub f32);
375
376impl core::fmt::Display for Lux {
377 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
378 self.0.fmt(f)?;
379 f.write_str(" lux (lm/m^2)")
380 }
381}
382
383impl Lux {
384 pub const OUTDOOR_TWILIGHT: Self = Lux(1.0);
385 pub const OUTDOOR_STREET_LIGHT_MIN: Self = Lux(5.0);
386 pub const OUTDOOR_SUNSET: Self = Lux(10.0);
387 pub const INDOOR_LOUNGE: Self = Lux(50.0);
388 pub const INDOOR_HALLWAY: Self = Lux(80.0);
389 pub const OUTDOOR_OVERCAST_LOW: Self = Lux(100.0);
390 pub const INDOOR_OFFICE_LOW: Self = Lux(320.0);
391 pub const OUTDOOR_SUNRISE_OR_SUNSET: Self = Lux(400.0);
392 pub const INDOOR_OFFICE_HIGH: Self = Lux(500.0);
393 pub const OUTDOOR_OVERCAST_HIGH: Self = Lux(1000.0);
394 pub const OUTDOOR_FOXS_WEDDING: Self = Lux(3000.0);
395 pub const OUTDOOR_FULL_DAYLIGHT_LOW: Self = Lux(10_000.0);
396 pub const OUTDOOR_FULL_DAYLIGHT_HIGH: Self = Lux(25_000.0);
397 pub const OUTDOOR_DIRECT_SUNLIGHT_LOW: Self = Lux(32_000.0);
398 pub const OUTDOOR_DIRECT_SUNLIGHT_HIGH: Self = Lux(130_000.0);
399}
400
401#[repr(C)]
402#[derive(Copy, Clone, SlabItem, Debug)]
403pub struct DirectionalLightDescriptor {
404 pub direction: Vec3,
405 pub color: Vec4,
406 pub intensity: Lux,
408}
409
410impl Default for DirectionalLightDescriptor {
411 fn default() -> Self {
412 let direction = Vec3::new(0.0, -1.0, 0.0);
413 let color = Vec4::splat(1.0);
414 let intensity = Lux::OUTDOOR_TWILIGHT;
415
416 Self {
417 direction,
418 color,
419 intensity,
420 }
421 }
422}
423
424impl DirectionalLightDescriptor {
425 pub fn shadow_mapping_projection_and_view(
426 &self,
427 parent_light_transform: &Mat4,
428 z_near: f32,
435 z_far: f32,
437 ) -> (Mat4, Mat4) {
438 crate::println!("descriptor: {self:#?}");
439 let depth = (z_far - z_near).abs();
440 let hd = depth * 0.5;
441 let projection = Mat4::orthographic_rh(-hd, hd, -hd, hd, z_near, z_far);
442 let direction = parent_light_transform
443 .transform_vector3(self.direction)
444 .alt_norm_or_zero();
445 let position = -direction * depth * 0.5;
446 crate::println!("direction: {direction}");
447 crate::println!("position: {position}");
448 let up = direction.orthonormal_vectors()[0];
449 let view = Mat4::look_to_rh(position, direction, up);
450 (projection, view)
451 }
452}
453
454#[repr(C)]
455#[derive(Copy, Clone, SlabItem, core::fmt::Debug)]
456pub struct PointLightDescriptor {
457 pub position: Vec3,
460 pub color: Vec4,
461 pub intensity: Candela,
463}
464
465impl Default for PointLightDescriptor {
466 fn default() -> Self {
467 let color = Vec4::splat(1.0);
468 let intensity = Candela::COMMON_WAX_CANDLE;
469
470 Self {
471 position: Default::default(),
472 color,
473 intensity,
474 }
475 }
476}
477
478impl PointLightDescriptor {
479 pub fn shadow_mapping_view_matrix(
480 &self,
481 face_index: usize,
482 parent_light_transform: &Mat4,
483 ) -> Mat4 {
484 let eye = parent_light_transform.transform_point3(self.position);
485 let mut face = CubemapFaceDirection::FACES[face_index];
486 face.eye = eye;
487 face.view()
488 }
489
490 pub fn shadow_mapping_projection_matrix(z_near: f32, z_far: f32) -> Mat4 {
491 Mat4::perspective_lh(core::f32::consts::FRAC_PI_2, 1.0, z_near, z_far)
492 }
493
494 pub fn shadow_mapping_projection_and_view_matrices(
495 &self,
496 parent_light_transform: &Mat4,
497 z_near: f32,
498 z_far: f32,
499 ) -> (Mat4, [Mat4; 6]) {
500 let p = Self::shadow_mapping_projection_matrix(z_near, z_far);
501 let eye = parent_light_transform.transform_point3(self.position);
502 (
503 p,
504 CubemapFaceDirection::FACES.map(|mut face| {
505 face.eye = eye;
506 face.view()
507 }),
508 )
509 }
510}
511
512pub fn radius_of_illumination(intensity_candelas: f32, minimum_illuminance_lux: f32) -> f32 {
523 (intensity_candelas / minimum_illuminance_lux).sqrt()
524}
525
526#[repr(u32)]
527#[derive(Copy, Clone, PartialEq, core::fmt::Debug)]
528pub enum LightStyle {
529 Directional = 0,
530 Point = 1,
531 Spot = 2,
532}
533
534impl core::fmt::Display for LightStyle {
535 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
536 match self {
537 LightStyle::Directional => f.write_str("directional"),
538 LightStyle::Point => f.write_str("point"),
539 LightStyle::Spot => f.write_str("spot"),
540 }
541 }
542}
543
544impl SlabItem for LightStyle {
545 const SLAB_SIZE: usize = { 1 };
546
547 fn read_slab(index: usize, slab: &[u32]) -> Self {
548 let proxy = u32::read_slab(index, slab);
549 match proxy {
550 0 => LightStyle::Directional,
551 1 => LightStyle::Point,
552 2 => LightStyle::Spot,
553 _ => LightStyle::Directional,
554 }
555 }
556
557 fn write_slab(&self, index: usize, slab: &mut [u32]) -> usize {
558 let proxy = *self as u32;
559 proxy.write_slab(index, slab)
560 }
561}
562
563#[repr(C)]
566#[derive(Copy, Clone, PartialEq, SlabItem, core::fmt::Debug)]
567pub struct LightDescriptor {
568 pub light_type: LightStyle,
570 pub index: u32,
572 pub transform_id: Id<TransformDescriptor>,
581 pub shadow_map_desc_id: Id<ShadowMapDescriptor>,
583}
584
585impl Default for LightDescriptor {
586 fn default() -> Self {
587 Self {
588 light_type: LightStyle::Directional,
589 index: Id::<()>::NONE.inner(),
590 transform_id: Id::NONE,
591 shadow_map_desc_id: Id::NONE,
592 }
593 }
594}
595
596impl From<Id<DirectionalLightDescriptor>> for LightDescriptor {
597 fn from(id: Id<DirectionalLightDescriptor>) -> Self {
598 Self {
599 light_type: LightStyle::Directional,
600 index: id.inner(),
601 transform_id: Id::NONE,
602 shadow_map_desc_id: Id::NONE,
603 }
604 }
605}
606
607impl From<Id<SpotLightDescriptor>> for LightDescriptor {
608 fn from(id: Id<SpotLightDescriptor>) -> Self {
609 Self {
610 light_type: LightStyle::Spot,
611 index: id.inner(),
612 transform_id: Id::NONE,
613 shadow_map_desc_id: Id::NONE,
614 }
615 }
616}
617
618impl From<Id<PointLightDescriptor>> for LightDescriptor {
619 fn from(id: Id<PointLightDescriptor>) -> Self {
620 Self {
621 light_type: LightStyle::Point,
622 index: id.inner(),
623 transform_id: Id::NONE,
624 shadow_map_desc_id: Id::NONE,
625 }
626 }
627}
628
629impl LightDescriptor {
630 pub fn into_directional_id(self) -> Id<DirectionalLightDescriptor> {
631 Id::from(self.index)
632 }
633
634 pub fn into_spot_id(self) -> Id<SpotLightDescriptor> {
635 Id::from(self.index)
636 }
637
638 pub fn into_point_id(self) -> Id<PointLightDescriptor> {
639 Id::from(self.index)
640 }
641}
642
643pub struct ShadowCalculation {
647 pub shadow_map_desc: ShadowMapDescriptor,
648 pub shadow_map_atlas_size: UVec2,
649 pub surface_normal_in_world_space: Vec3,
650 pub frag_pos_in_world_space: Vec3,
651 pub frag_to_light_in_world_space: Vec3,
652 pub bias_min: f32,
653 pub bias_max: f32,
654 pub pcf_samples: u32,
655}
656
657impl ShadowCalculation {
658 pub fn new(
661 light_slab: &[u32],
662 light: LightDescriptor,
663 in_pos: Vec3,
664 surface_normal: Vec3,
665 light_direction: Vec3,
666 ) -> Self {
667 let shadow_map_desc = light_slab.read_unchecked(light.shadow_map_desc_id);
668 let atlas_size = {
669 let lighting_desc_id = Id::<LightingDescriptor>::new(0);
670 let atlas_desc_id = light_slab.read_unchecked(
671 lighting_desc_id + LightingDescriptor::OFFSET_OF_SHADOW_MAP_ATLAS_DESCRIPTOR_ID,
672 );
673 let atlas_desc = light_slab.read_unchecked(atlas_desc_id);
674 atlas_desc.size
675 };
676
677 ShadowCalculation {
678 shadow_map_desc,
679 shadow_map_atlas_size: atlas_size.xy(),
680 surface_normal_in_world_space: surface_normal,
681 frag_pos_in_world_space: in_pos,
682 frag_to_light_in_world_space: light_direction,
683 bias_min: shadow_map_desc.bias_min,
684 bias_max: shadow_map_desc.bias_max,
685 pcf_samples: shadow_map_desc.pcf_samples,
686 }
687 }
688
689 fn get_atlas_texture_at(&self, light_slab: &[u32], index: usize) -> AtlasTextureDescriptor {
690 let atlas_texture_id =
691 light_slab.read_unchecked(self.shadow_map_desc.atlas_textures_array.at(index));
692 light_slab.read_unchecked(atlas_texture_id)
693 }
694
695 fn get_frag_pos_in_light_space(&self, light_slab: &[u32], index: usize) -> Vec3 {
696 let light_space_transform_id = self.shadow_map_desc.light_space_transforms_array.at(index);
697 let light_space_transform = light_slab.read_unchecked(light_space_transform_id);
698 light_space_transform.project_point3(self.frag_pos_in_world_space)
699 }
700
701 pub fn run_directional_or_spot<T, S>(
706 &self,
707 light_slab: &[u32],
708 shadow_map: &T,
709 shadow_map_sampler: &S,
710 ) -> f32
711 where
712 S: IsSampler,
713 T: Sample2dArray<Sampler = S>,
714 {
715 let ShadowCalculation {
716 shadow_map_desc: _,
717 shadow_map_atlas_size,
718 frag_pos_in_world_space: _,
719 surface_normal_in_world_space: surface_normal,
720 frag_to_light_in_world_space: light_direction,
721 bias_min,
722 bias_max,
723 pcf_samples,
724 } = self;
725 let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, 0);
726 crate::println!("frag_pos_in_light_space: {frag_pos_in_light_space}");
727 if !crate::math::is_inside_clip_space(frag_pos_in_light_space.xyz()) {
728 return 0.0;
729 }
730 let proj_coords_uv = (frag_pos_in_light_space.xy() * Vec2::new(1.0, -1.0)
736 + Vec2::splat(1.0))
737 * Vec2::splat(0.5);
738 crate::println!("proj_coords_uv: {proj_coords_uv}");
739
740 let shadow_map_atlas_texture = self.get_atlas_texture_at(light_slab, 0);
741 let pcf_samples_2 = *pcf_samples as i32 / 2;
747 let texel_size = 1.0
748 / Vec2::new(
749 shadow_map_atlas_texture.size_px.x as f32,
750 shadow_map_atlas_texture.size_px.y as f32,
751 );
752 let mut shadow = 0.0f32;
753 let mut total = 0.0f32;
754 for x in -pcf_samples_2..=pcf_samples_2 {
755 for y in -pcf_samples_2..=pcf_samples_2 {
756 let proj_coords = shadow_map_atlas_texture.uv(
757 proj_coords_uv + Vec2::new(x as f32, y as f32) * texel_size,
758 *shadow_map_atlas_size,
759 );
760 let shadow_map_depth = shadow_map
761 .sample_by_lod(*shadow_map_sampler, proj_coords, 0.0)
762 .x;
763 let fragment_depth = frag_pos_in_light_space.z;
768
769 crate::println!("current_depth: {fragment_depth}");
774 crate::println!("closest_depth: {shadow_map_depth}");
775 let bias = (bias_max * (1.0 - surface_normal.dot(*light_direction))).max(*bias_min);
776
777 if (fragment_depth - bias) >= shadow_map_depth {
778 shadow += 1.0
779 }
780 total += 1.0;
781 }
782 }
783 shadow / total.max(1.0)
784 }
785
786 pub const POINT_SAMPLE_OFFSET_DIRECTIONS: [Vec3; 21] = [
787 Vec3::ZERO,
788 Vec3::new(1.0, 1.0, 1.0),
789 Vec3::new(1.0, -1.0, 1.0),
790 Vec3::new(-1.0, -1.0, 1.0),
791 Vec3::new(-1.0, 1.0, 1.0),
792 Vec3::new(1.0, 1.0, -1.0),
793 Vec3::new(1.0, -1.0, -1.0),
794 Vec3::new(-1.0, -1.0, -1.0),
795 Vec3::new(-1.0, 1.0, -1.0),
796 Vec3::new(1.0, 1.0, 0.0),
797 Vec3::new(1.0, -1.0, 0.0),
798 Vec3::new(-1.0, -1.0, 0.0),
799 Vec3::new(-1.0, 1.0, 0.0),
800 Vec3::new(1.0, 0.0, 1.0),
801 Vec3::new(-1.0, 0.0, 1.0),
802 Vec3::new(1.0, 0.0, -1.0),
803 Vec3::new(-1.0, 0.0, -1.0),
804 Vec3::new(0.0, 1.0, 1.0),
805 Vec3::new(0.0, -1.0, 1.0),
806 Vec3::new(0.0, -1.0, -1.0),
807 Vec3::new(0.0, 1.0, -1.0),
808 ];
809 pub fn run_point<T, S>(
814 &self,
815 light_slab: &[u32],
816 shadow_map: &T,
817 shadow_map_sampler: &S,
818 light_pos_in_world_space: Vec3,
819 ) -> f32
820 where
821 S: IsSampler,
822 T: Sample2dArray<Sampler = S>,
823 {
824 let ShadowCalculation {
825 shadow_map_desc,
826 shadow_map_atlas_size,
827 frag_pos_in_world_space,
828 surface_normal_in_world_space: surface_normal,
829 frag_to_light_in_world_space: frag_to_light,
830 bias_min,
831 bias_max,
832 pcf_samples,
833 } = self;
834
835 let light_to_frag_dir = frag_pos_in_world_space - light_pos_in_world_space;
836 crate::println!("light_to_frag_dir: {light_to_frag_dir}");
837
838 let pcf_samplesf = (*pcf_samples as f32)
839 .max(1.0)
840 .min(Self::POINT_SAMPLE_OFFSET_DIRECTIONS.len() as f32);
841 let pcf_samples = pcf_samplesf as usize;
842 let view_distance = light_to_frag_dir.length();
843 let disk_radius = (1.0 + view_distance / shadow_map_desc.z_far) / 25.0;
844 let mut shadow = 0.0f32;
845 for i in 0..pcf_samples {
846 let sample_offset = Self::POINT_SAMPLE_OFFSET_DIRECTIONS[i] * disk_radius;
847 crate::println!("sample_offset: {sample_offset}");
848 let sample_dir = (light_to_frag_dir + sample_offset).alt_norm_or_zero();
849 let (face_index, uv) = CubemapDescriptor::get_face_index_and_uv(sample_dir);
850 crate::println!("face_index: {face_index}",);
851 crate::println!("uv: {uv}");
852 let frag_pos_in_light_space = self.get_frag_pos_in_light_space(light_slab, face_index);
853 let face_texture = self.get_atlas_texture_at(light_slab, face_index);
854 let uv_tex = face_texture.uv(uv, *shadow_map_atlas_size);
855 let shadow_map_depth = shadow_map.sample_by_lod(*shadow_map_sampler, uv_tex, 0.0).x;
856 let fragment_depth = frag_pos_in_light_space.z;
857 let bias = (bias_max * (1.0 - surface_normal.dot(*frag_to_light))).max(*bias_min);
858 if (fragment_depth - bias) > shadow_map_depth {
859 shadow += 1.0
860 }
861 }
862
863 shadow / pcf_samplesf
864 }
865}
866
867#[spirv(vertex)]
881pub fn light_tiling_depth_pre_pass(
882 #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
884 #[spirv(vertex_index)] vertex_index: u32,
886 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
888 #[spirv(position)] out_clip_pos: &mut Vec4,
890) {
891 let renderlet = geometry_slab.read_unchecked(renderlet_id);
892 if !renderlet.visible {
893 *out_clip_pos = Vec3::splat(100.0).extend(1.0);
895 return;
896 }
897
898 let camera_id = geometry_slab
899 .read_unchecked(Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID);
900 let camera = geometry_slab.read_unchecked(camera_id);
901
902 let VertexInfo { world_pos, .. } = renderlet.get_vertex_info(vertex_index, geometry_slab);
903
904 *out_clip_pos = camera.view_projection() * world_pos.extend(1.0);
905}
906
907pub type DepthImage2d = Image!(2D, type=f32, sampled, depth);
908
909pub type DepthImage2dMultisampled = Image!(2D, type=f32, sampled, depth, multisampled=true);
910
911#[derive(Clone, Copy, Default, SlabItem)]
913#[offsets]
914pub struct LightTile {
915 pub depth_min: u32,
917 pub depth_max: u32,
919 pub next_light_index: u32,
923 pub lights_array: Array<Id<LightDescriptor>>,
925}
926
927impl core::fmt::Debug for LightTile {
928 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
929 f.debug_struct("LightTile")
930 .field("depth_min", &dequantize_depth_u32_to_f32(self.depth_min))
931 .field("depth_max", &dequantize_depth_u32_to_f32(self.depth_max))
932 .field("next_light_index", &self.next_light_index)
933 .field("lights_array", &self.lights_array)
934 .finish()
935 }
936}
937
938#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
941pub struct LightTilingDescriptor {
942 pub depth_texture_size: UVec2,
944 pub tile_size: u32,
946 pub tiles_array: Array<LightTile>,
948 pub minimum_illuminance_lux: f32,
952}
953
954impl Default for LightTilingDescriptor {
955 fn default() -> Self {
956 Self {
957 depth_texture_size: Default::default(),
958 tile_size: 16,
959 tiles_array: Default::default(),
960 minimum_illuminance_lux: 0.1,
961 }
962 }
963}
964
965impl LightTilingDescriptor {
966 pub fn tile_grid_size(&self) -> UVec2 {
968 let dims_f32 = self.depth_texture_size.as_vec2() / self.tile_size as f32;
969 dims_f32.ceil().as_uvec2()
970 }
971
972 pub fn tile_coord_for_fragment(&self, frag_coord: Vec2) -> UVec2 {
973 let frag_coord = frag_coord.as_uvec2();
974 frag_coord / self.tile_size
975 }
976
977 pub fn tile_index_for_fragment(&self, frag_coord: Vec2) -> usize {
978 let tile_coord = self.tile_coord_for_fragment(frag_coord);
979 let tile_dimensions = self.tile_grid_size();
980 (tile_coord.y * tile_dimensions.x + tile_coord.x) as usize
981 }
982}
983
984pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 {
986 (u32::MAX as f32 * depth).round() as u32
987}
988
989pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 {
991 depth as f32 / u32::MAX as f32
992}
993
994pub(crate) struct NextLightIndex {
997 current_step: usize,
998 tile_size: u32,
999 lights: Array<Id<LightDescriptor>>,
1000 global_id: UVec3,
1001}
1002
1003impl Iterator for NextLightIndex {
1004 type Item = Id<Id<LightDescriptor>>;
1005
1006 fn next(&mut self) -> Option<Self::Item> {
1007 let next_index = self.next_index();
1008 self.current_step += 1;
1009 if next_index < self.lights.len() {
1010 Some(self.lights.at(next_index))
1011 } else {
1012 None
1013 }
1014 }
1015}
1016
1017impl NextLightIndex {
1018 pub fn new(
1019 global_id: UVec3,
1020 tile_size: u32,
1021 analytical_lights_array: Array<Id<LightDescriptor>>,
1022 ) -> Self {
1023 Self {
1024 current_step: 0,
1025 tile_size,
1026 lights: analytical_lights_array,
1027 global_id,
1028 }
1029 }
1030
1031 pub fn next_index(&self) -> usize {
1032 let frag_tile_xy = self.global_id.xy() % self.tile_size;
1034 let offset = frag_tile_xy.y * self.tile_size + frag_tile_xy.x;
1036 let stride = (self.tile_size * self.tile_size) as usize;
1037 self.current_step * stride + offset as usize
1038 }
1039}
1040
1041struct LightTilingInvocation {
1042 global_id: UVec3,
1043 descriptor: LightTilingDescriptor,
1044}
1045
1046impl LightTilingInvocation {
1047 fn new(global_id: UVec3, descriptor: LightTilingDescriptor) -> Self {
1048 Self {
1049 global_id,
1050 descriptor,
1051 }
1052 }
1053
1054 fn frag_pos(&self) -> UVec2 {
1058 self.global_id.xy()
1059 }
1060
1061 fn tile_grid_size(&self) -> UVec2 {
1063 self.descriptor.tile_grid_size()
1064 }
1065
1066 fn tile_coord(&self) -> UVec2 {
1070 self.global_id.xy() / self.descriptor.tile_size
1071 }
1072
1073 fn tile_index(&self) -> usize {
1075 let tile_pos = self.tile_coord();
1076 let tile_dimensions = self.tile_grid_size();
1077 (tile_pos.y * tile_dimensions.x + tile_pos.x) as usize
1078 }
1079
1080 fn tile_ndc_midpoint(&self) -> Vec2 {
1082 let min_coord = self.tile_coord().as_vec2();
1083 let mid_coord = min_coord + 0.5;
1084 crate::math::convert_pixel_to_ndc(mid_coord, self.tile_grid_size())
1085 }
1086
1087 fn compute_min_and_max_depth(
1092 &self,
1093 depth_texture: &impl Fetch<UVec2, Output = Vec4>,
1094 lighting_slab: &mut [u32],
1095 ) {
1096 let frag_pos = self.frag_pos();
1097 let depth_texture_size = self.descriptor.depth_texture_size;
1098 if frag_pos.x >= depth_texture_size.x || frag_pos.y >= depth_texture_size.y {
1099 return;
1100 }
1101 let frag_depth: f32 = depth_texture.fetch(frag_pos).x;
1103 let frag_depth_u32 = quantize_depth_f32_to_u32(frag_depth);
1108
1109 let tile_index = self.tile_index();
1111 let lighting_desc = lighting_slab.read_unchecked(Id::<LightingDescriptor>::new(0));
1112 let tiling_desc = lighting_slab.read_unchecked(lighting_desc.light_tiling_descriptor_id);
1113 let tile_id = tiling_desc.tiles_array.at(tile_index);
1115 let min_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MIN;
1116 let max_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MAX;
1118
1119 let _prev_min_depth = crate::sync::atomic_u_min::<
1120 { spirv_std::memory::Scope::Workgroup as u32 },
1121 { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1122 >(lighting_slab, min_depth_index, frag_depth_u32);
1123 let _prev_max_depth = crate::sync::atomic_u_max::<
1124 { spirv_std::memory::Scope::Workgroup as u32 },
1125 { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1126 >(lighting_slab, max_depth_index, frag_depth_u32);
1127 }
1128
1129 fn should_invoke(&self) -> bool {
1131 self.global_id.x < self.descriptor.depth_texture_size.x
1132 && self.global_id.y < self.descriptor.depth_texture_size.y
1133 }
1134
1135 fn clear_tile(&self, lighting_slab: &mut [u32]) {
1140 let dimensions = self.tile_grid_size();
1141 let index = (self.global_id.y * dimensions.x + self.global_id.x) as usize;
1142 if index < self.descriptor.tiles_array.len() {
1143 let tile_id = self.descriptor.tiles_array.at(index);
1144 let mut tile = lighting_slab.read(tile_id);
1145 tile.depth_min = u32::MAX;
1146 tile.depth_max = 0;
1147 tile.next_light_index = 0;
1148 lighting_slab.write(tile_id, &tile);
1149 for id in tile.lights_array.iter() {
1151 lighting_slab.write(id, &Id::NONE);
1152 }
1153 }
1154 }
1155
1156 fn compute_light_lists(&self, geometry_slab: &[u32], lighting_slab: &mut [u32]) {
1161 let index = self.tile_index();
1162 let tile_id = self.descriptor.tiles_array.at(index);
1163 let depth_min_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MIN);
1165 let depth_max_u32 = lighting_slab.read_unchecked(tile_id + LightTile::OFFSET_OF_DEPTH_MAX);
1166 let depth_min = dequantize_depth_u32_to_f32(depth_min_u32);
1167 let depth_max = dequantize_depth_u32_to_f32(depth_max_u32);
1168
1169 if depth_min == depth_max {
1170 return;
1175 }
1176
1177 let camera_id = geometry_slab.read_unchecked(
1178 Id::<GeometryDescriptor>::new(0) + GeometryDescriptor::OFFSET_OF_CAMERA_ID,
1179 );
1180 let camera = geometry_slab.read_unchecked(camera_id);
1181
1182 let tile_ndc_midpoint = self.tile_ndc_midpoint();
1193 let tile_line_ndc = (
1194 tile_ndc_midpoint.extend(depth_min),
1195 tile_ndc_midpoint.extend(depth_max),
1196 );
1197 let inverse_viewproj = camera.view_projection().inverse();
1198 let tile_line = (
1199 inverse_viewproj.project_point3(tile_line_ndc.0),
1200 inverse_viewproj.project_point3(tile_line_ndc.1),
1201 );
1202
1203 let tile_index = self.tile_index();
1204 let tile_id = self.descriptor.tiles_array.at(tile_index);
1205 let tile_lights_array = lighting_slab.read(tile_id + LightTile::OFFSET_OF_LIGHTS_ARRAY);
1206 let next_light_id = tile_id + LightTile::OFFSET_OF_NEXT_LIGHT_INDEX;
1207
1208 let analytical_lights_array = lighting_slab.read_unchecked(
1210 Id::<LightingDescriptor>::new(0)
1211 + LightingDescriptor::OFFSET_OF_ANALYTICAL_LIGHTS_ARRAY,
1212 );
1213
1214 let next_light = NextLightIndex::new(
1217 self.global_id,
1218 self.descriptor.tile_size,
1219 analytical_lights_array,
1220 );
1221 for id_of_light_id in next_light {
1222 let light_id = lighting_slab.read_unchecked(id_of_light_id);
1223 let light = lighting_slab.read_unchecked(light_id);
1224 let transform = lighting_slab.read(light.transform_id);
1225 let (distance, intensity_candelas) = match light.light_type {
1228 LightStyle::Directional => {
1229 let directional_light = lighting_slab.read(light.into_directional_id());
1230 (0.0, directional_light.intensity.0 / 683.0)
1232 }
1233 LightStyle::Point => {
1234 let point_light = lighting_slab.read(light.into_point_id());
1235 let center = Mat4::from(transform).transform_point3(point_light.position);
1236 let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1237 (distance, point_light.intensity.0 / 683.0)
1239 }
1240 LightStyle::Spot => {
1241 let spot_light = lighting_slab.read(light.into_spot_id());
1244 let center = Mat4::from(transform).transform_point3(spot_light.position);
1245 let distance = crate::math::distance_to_line(center, tile_line.0, tile_line.1);
1246 (distance, spot_light.intensity.0 / 683.0)
1248 }
1249 };
1250
1251 let radius =
1252 radius_of_illumination(intensity_candelas, self.descriptor.minimum_illuminance_lux);
1253 let should_add = radius >= distance;
1254 if should_add {
1255 let next_index = crate::sync::atomic_i_increment::<
1259 { spirv_std::memory::Scope::Workgroup as u32 },
1260 { spirv_std::memory::Semantics::WORKGROUP_MEMORY.bits() },
1261 >(lighting_slab, next_light_id);
1262 if next_index as usize >= tile_lights_array.len() {
1263 break;
1268 } else {
1269 let binned_light_id = tile_lights_array.at(next_index as usize);
1272 lighting_slab.write(binned_light_id, &light_id);
1274 }
1275 }
1276 }
1277 }
1278}
1279
1280#[spirv(compute(threads(16, 16, 1)))]
1281pub fn light_tiling_clear_tiles(
1282 #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1283 #[spirv(global_invocation_id)] global_id: UVec3,
1284) {
1285 let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1286 let light_tiling_descriptor =
1287 lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1288 let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1289 invocation.clear_tile(lighting_slab);
1290}
1291
1292#[spirv(compute(threads(16, 16, 1)))]
1296pub fn light_tiling_compute_tile_min_and_max_depth(
1297 #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1298 #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2d,
1299 #[spirv(global_invocation_id)] global_id: UVec3,
1300) {
1301 let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1302 let light_tiling_descriptor =
1303 lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1304 let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1305 invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1306}
1307
1308#[spirv(compute(threads(16, 16, 1)))]
1312pub fn light_tiling_compute_tile_min_and_max_depth_multisampled(
1313 #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1314 #[spirv(descriptor_set = 0, binding = 2)] depth_texture: &DepthImage2dMultisampled,
1315 #[spirv(global_invocation_id)] global_id: UVec3,
1316) {
1317 let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1318 let light_tiling_descriptor =
1319 lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1320 let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1321 invocation.compute_min_and_max_depth(depth_texture, lighting_slab);
1322}
1323
1324#[spirv(compute(threads(16, 16, 1)))]
1325pub fn light_tiling_bin_lights(
1326 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
1327 #[spirv(storage_buffer, descriptor_set = 0, binding = 1)] lighting_slab: &mut [u32],
1328 #[spirv(global_invocation_id)] global_id: UVec3,
1329) {
1330 let lighting_descriptor = lighting_slab.read(Id::<LightingDescriptor>::new(0));
1331 let light_tiling_descriptor =
1332 lighting_slab.read(lighting_descriptor.light_tiling_descriptor_id);
1333 let invocation = LightTilingInvocation::new(global_id, light_tiling_descriptor);
1334 if invocation.should_invoke() {
1335 invocation.compute_light_lists(geometry_slab, lighting_slab);
1336 }
1337}