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}
56
57#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
58pub struct ShadowMapDescriptor {
59 pub light_space_transforms_array: Array<Mat4>,
60 pub z_near: f32,
62 pub z_far: f32,
64 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#[spirv(vertex)]
116#[allow(clippy::too_many_arguments)]
117pub fn shadow_mapping_vertex(
118 #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
120 #[spirv(vertex_index)] vertex_index: u32,
122 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
124 #[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 *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#[derive(Clone, Copy, Default, core::fmt::Debug)]
178pub struct SpotLightCalculation {
179 pub light_position: Vec3,
181 pub frag_position: Vec3,
183 pub frag_to_light: Vec3,
185 pub frag_to_light_distance: f32,
187 pub light_direction: Vec3,
189 pub cos_inner_cutoff: f32,
193 pub cos_outer_cutoff: f32,
200 pub fragment_is_inside_inner_cone: bool,
202 pub fragment_is_inside_outer_cone: bool,
204 pub epsilon: f32,
206 pub cos_theta: f32,
213 pub contribution_unclamped: f32,
214 pub contribution: f32,
217}
218
219impl SpotLightCalculation {
220 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#[repr(C)]
274#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
275#[derive(Copy, Clone, SlabItem)]
276pub struct SpotLightDescriptor {
277 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#[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#[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 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 z_near: f32,
424 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 pub position: Vec3,
450 pub color: Vec4,
451 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
502pub 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#[repr(C)]
557#[cfg_attr(not(target_arch = "spirv"), derive(Debug))]
558#[derive(Copy, Clone, PartialEq, SlabItem)]
559pub struct LightDescriptor {
560 pub light_type: LightStyle,
562 pub index: u32,
564 pub transform_id: Id<TransformDescriptor>,
572 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
634pub 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 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 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 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 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 let fragment_depth = frag_pos_in_light_space.z;
754
755 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 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#[spirv(vertex)]
866pub fn light_tiling_depth_pre_pass(
867 #[spirv(instance_index)] renderlet_id: Id<PrimitiveDescriptor>,
869 #[spirv(vertex_index)] vertex_index: u32,
871 #[spirv(storage_buffer, descriptor_set = 0, binding = 0)] geometry_slab: &[u32],
873 #[spirv(position)] out_clip_pos: &mut Vec4,
875) {
876 let renderlet = geometry_slab.read_unchecked(renderlet_id);
877 if !renderlet.visible {
878 *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#[derive(Clone, Copy, Default, SlabItem)]
898#[offsets]
899pub struct LightTile {
900 pub depth_min: u32,
902 pub depth_max: u32,
904 pub next_light_index: u32,
908 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#[derive(Clone, Copy, SlabItem, core::fmt::Debug)]
926pub struct LightTilingDescriptor {
927 pub depth_texture_size: UVec2,
929 pub tile_size: u32,
931 pub tiles_array: Array<LightTile>,
933 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 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
969pub fn quantize_depth_f32_to_u32(depth: f32) -> u32 {
971 (u32::MAX as f32 * depth).round() as u32
972}
973
974pub fn dequantize_depth_u32_to_f32(depth: u32) -> f32 {
976 depth as f32 / u32::MAX as f32
977}
978
979pub(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 let frag_tile_xy = self.global_id.xy() % self.tile_size;
1019 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 fn frag_pos(&self) -> UVec2 {
1043 self.global_id.xy()
1044 }
1045
1046 fn tile_grid_size(&self) -> UVec2 {
1048 self.descriptor.tile_grid_size()
1049 }
1050
1051 fn tile_coord(&self) -> UVec2 {
1055 self.global_id.xy() / self.descriptor.tile_size
1056 }
1057
1058 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 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 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 let frag_depth: f32 = depth_texture.fetch(frag_pos).x;
1087 let frag_depth_u32 = quantize_depth_f32_to_u32(frag_depth);
1091
1092 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 let tile_id = tiling_desc.tiles_array.at(tile_index);
1098 let min_depth_index = tile_id + LightTile::OFFSET_OF_DEPTH_MIN;
1099 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 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 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 for id in tile.lights_array.iter() {
1134 lighting_slab.write(id, &Id::NONE);
1135 }
1136 }
1137 }
1138
1139 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 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 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 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 let analytical_lights_array = lighting_slab.read_unchecked(
1191 Id::<LightingDescriptor>::new(0)
1192 + LightingDescriptor::OFFSET_OF_ANALYTICAL_LIGHTS_ARRAY,
1193 );
1194
1195 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 let (distance, intensity_candelas) = match light.light_type {
1209 LightStyle::Directional => {
1210 let directional_light = lighting_slab.read(light.into_directional_id());
1211 (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 (distance, point_light.intensity.0 / 683.0)
1220 }
1221 LightStyle::Spot => {
1222 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 (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 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 break;
1246 } else {
1247 let binned_light_id = tile_lights_array.at(next_index as usize);
1249 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#[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#[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}