renderling/light/
shadow_map.rs

1//! Shadow mapping.
2
3use core::{ops::Deref, sync::atomic::AtomicUsize};
4use std::sync::Arc;
5
6use craballoc::{
7    prelude::Hybrid,
8    value::{HybridArray, HybridWriteGuard},
9};
10use crabslab::Id;
11use glam::{Mat4, UVec2};
12use snafu::ResultExt;
13
14use crate::{
15    atlas::{shader::AtlasTextureDescriptor, AtlasBlittingOperation, AtlasImage, AtlasTexture},
16    bindgroup::ManagedBindGroup,
17    light::{IsLight, Light},
18    primitive::Primitive,
19};
20
21use super::{
22    shader::{LightStyle, ShadowMapDescriptor},
23    AnalyticalLight, Lighting, LightingError, PollSnafu,
24};
25
26/// Projects shadows from a single light source for specific objects.
27///
28/// A `ShadowMap` is essentially a depth map rendering of the scene from one
29/// light's point of view. We use this rendering of the scene to determine if
30/// an object lies in shadow.
31///
32/// To create a new [`ShadowMap`], use
33/// [`Stage::new_shadow_map`](crate::stage::Stage::new_shadow_map).
34#[derive(Clone)]
35pub struct ShadowMap {
36    /// Last time the stage slab was bound.
37    pub(crate) stage_slab_buffer_creation_time: Arc<AtomicUsize>,
38    /// Last time the light slab was bound.
39    pub(crate) light_slab_buffer_creation_time: Arc<AtomicUsize>,
40    /// This shadow map's light transform,
41    pub(crate) shadowmap_descriptor: Hybrid<ShadowMapDescriptor>,
42    /// This shadow map's transforms.
43    ///
44    /// Directional and spot lights have 1, point lights
45    /// have 6.
46    pub(crate) light_space_transforms: HybridArray<Mat4>,
47    /// Bindgroup for the shadow map update shader
48    pub(crate) update_bindgroup: ManagedBindGroup,
49    pub(crate) atlas_textures: Vec<AtlasTexture>,
50    pub(crate) _atlas_textures_array: HybridArray<Id<AtlasTextureDescriptor>>,
51    pub(crate) update_texture: crate::texture::Texture,
52    pub(crate) blitting_op: AtlasBlittingOperation,
53    pub(crate) light_bundle: AnalyticalLight,
54}
55
56impl ShadowMap {
57    const LABEL: Option<&str> = Some("shadow-map");
58
59    pub fn create_update_bindgroup_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
60        device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
61            label: Self::LABEL,
62            entries: &[
63                wgpu::BindGroupLayoutEntry {
64                    binding: 0,
65                    visibility: wgpu::ShaderStages::VERTEX,
66                    ty: wgpu::BindingType::Buffer {
67                        ty: wgpu::BufferBindingType::Storage { read_only: true },
68                        has_dynamic_offset: false,
69                        min_binding_size: None,
70                    },
71                    count: None,
72                },
73                wgpu::BindGroupLayoutEntry {
74                    binding: 1,
75                    visibility: wgpu::ShaderStages::VERTEX,
76                    ty: wgpu::BindingType::Buffer {
77                        ty: wgpu::BufferBindingType::Storage { read_only: true },
78                        has_dynamic_offset: false,
79                        min_binding_size: None,
80                    },
81                    count: None,
82                },
83            ],
84        })
85    }
86
87    pub fn create_update_pipeline(
88        device: &wgpu::Device,
89        bindgroup_layout: &wgpu::BindGroupLayout,
90    ) -> wgpu::RenderPipeline {
91        let shadow_map_update_layout =
92            device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
93                label: ShadowMap::LABEL,
94                bind_group_layouts: &[bindgroup_layout],
95                push_constant_ranges: &[],
96            });
97        let shadow_map_update_vertex = crate::linkage::shadow_mapping_vertex::linkage(device);
98        device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
99            label: Self::LABEL,
100            layout: Some(&shadow_map_update_layout),
101            vertex: wgpu::VertexState {
102                module: &shadow_map_update_vertex.module,
103                entry_point: Some(shadow_map_update_vertex.entry_point),
104                compilation_options: wgpu::PipelineCompilationOptions::default(),
105                buffers: &[],
106            },
107            primitive: wgpu::PrimitiveState {
108                topology: wgpu::PrimitiveTopology::TriangleList,
109                strip_index_format: None,
110                front_face: wgpu::FrontFace::Ccw,
111                cull_mode: Some(wgpu::Face::Front),
112                unclipped_depth: false,
113                polygon_mode: wgpu::PolygonMode::Fill,
114                conservative: false,
115            },
116            depth_stencil: Some(wgpu::DepthStencilState {
117                format: wgpu::TextureFormat::Depth32Float,
118                depth_write_enabled: true,
119                depth_compare: wgpu::CompareFunction::Less,
120                stencil: wgpu::StencilState::default(),
121                bias: wgpu::DepthBiasState::default(),
122            }),
123            multisample: wgpu::MultisampleState::default(),
124            fragment: None,
125            multiview: None,
126            cache: None,
127        })
128    }
129
130    /// Create the bindgroup for the shadow map update shader.
131    fn create_update_bindgroup(
132        device: &wgpu::Device,
133        bindgroup_layout: &wgpu::BindGroupLayout,
134        geometry_slab_buffer: &wgpu::Buffer,
135        light_slab_buffer: &wgpu::Buffer,
136    ) -> wgpu::BindGroup {
137        device.create_bind_group(&wgpu::BindGroupDescriptor {
138            label: Self::LABEL,
139            layout: bindgroup_layout,
140            entries: &[
141                wgpu::BindGroupEntry {
142                    binding: 0,
143                    resource: wgpu::BindingResource::Buffer(
144                        geometry_slab_buffer.as_entire_buffer_binding(),
145                    ),
146                },
147                wgpu::BindGroupEntry {
148                    binding: 1,
149                    resource: wgpu::BindingResource::Buffer(
150                        light_slab_buffer.as_entire_buffer_binding(),
151                    ),
152                },
153            ],
154        })
155    }
156
157    /// Returns the [`Id`] of the inner [`ShadowMapDescriptor`].
158    pub fn descriptor_id(&self) -> Id<ShadowMapDescriptor> {
159        self.shadowmap_descriptor.id()
160    }
161
162    /// Returns a guard on the inner [`ShadowMapDescriptor`].
163    ///
164    /// Use this to update descriptor values before calling `ShadowMap::update`.
165    pub fn descriptor_lock(&self) -> HybridWriteGuard<'_, ShadowMapDescriptor> {
166        self.shadowmap_descriptor.lock()
167    }
168
169    /// Enable shadow mapping for the given [`AnalyticalLight`], creating
170    /// a new [`ShadowMap`].
171    pub fn new<T>(
172        lighting: &Lighting,
173        analytical_light_bundle: &AnalyticalLight<T>,
174        // Size of the shadow map
175        size: UVec2,
176        // Distance to the shadow map frustum's near plane
177        z_near: f32,
178        // Distance to the shadow map frustum's far plane
179        z_far: f32,
180    ) -> Result<Self, LightingError>
181    where
182        T: IsLight,
183        Light: From<T>,
184    {
185        let stage_slab_buffer = lighting.geometry_slab_buffer.read().unwrap();
186        let is_point_light = analytical_light_bundle.style() == LightStyle::Point;
187        let count = if is_point_light { 6 } else { 1 };
188        let atlas = &lighting.shadow_map_atlas;
189        let image = AtlasImage::new(size, crate::atlas::AtlasImageFormat::R32FLOAT);
190        // UNWRAP: safe because we know there's one in here
191        let atlas_textures = atlas.add_images(vec![&image; count])?;
192        let atlas_len = atlas.len();
193        // Regardless of light type, we only create one depth texture,
194        // but that texture may be of layer 1 or 6
195        let label = format!("shadow-map-{atlas_len}");
196        let update_texture = crate::texture::Texture::create_depth_texture_for_shadow_map(
197            atlas.device(),
198            size.x,
199            size.y,
200            1,
201            Some(&label),
202            is_point_light,
203        );
204        let atlas_textures_array = lighting
205            .light_slab
206            .new_array(atlas_textures.iter().map(|t| t.id()));
207        let blitting_op = AtlasBlittingOperation::new(
208            &lighting.shadow_map_update_blitter,
209            atlas,
210            if is_point_light { 6 } else { 1 },
211        );
212        let light_space_transforms =
213            lighting
214                .light_slab
215                .new_array(analytical_light_bundle.light_space_transforms(
216                    &analytical_light_bundle.transform().descriptor(),
217                    z_near,
218                    z_far,
219                ));
220        let shadowmap_descriptor = lighting.light_slab.new_value(ShadowMapDescriptor {
221            light_space_transforms_array: light_space_transforms.array(),
222            z_near,
223            z_far,
224            atlas_textures_array: atlas_textures_array.array(),
225            bias_min: 0.0005,
226            bias_max: 0.005,
227            pcf_samples: 4,
228        });
229        // Set the descriptor in the light, so the shader knows to use it
230        analytical_light_bundle.light_descriptor.modify(|light| {
231            light.shadow_map_desc_id = shadowmap_descriptor.id();
232        });
233        let light_slab_buffer = lighting.commit();
234        let update_bindgroup = ManagedBindGroup::from(ShadowMap::create_update_bindgroup(
235            lighting.light_slab.device(),
236            &lighting.shadow_map_update_bindgroup_layout,
237            stage_slab_buffer.deref(),
238            &light_slab_buffer,
239        ));
240
241        Ok(ShadowMap {
242            stage_slab_buffer_creation_time: Arc::new(stage_slab_buffer.creation_time().into()),
243            light_slab_buffer_creation_time: Arc::new(light_slab_buffer.creation_time().into()),
244            shadowmap_descriptor,
245            light_space_transforms,
246            update_bindgroup,
247            atlas_textures,
248            _atlas_textures_array: atlas_textures_array,
249            update_texture,
250            blitting_op,
251            light_bundle: analytical_light_bundle.clone().into_generic(),
252        })
253    }
254
255    /// Update the `ShadowMap`, rendering the given [`Primitive`]s to the map as
256    /// shadow casters.
257    ///
258    /// The `ShadowMap` contains a weak reference to the [`AnalyticalLight`] used to create
259    /// it. Updates made to this `AnalyticalLight` will automatically propogate to this
260    /// `ShadowMap`.
261    ///
262    /// ## Errors
263    /// If the `AnalyticalLight` used to create this `ShadowMap` has been
264    /// dropped, calling this function will err.
265    pub fn update<'a>(
266        &self,
267        lighting: impl AsRef<Lighting>,
268        renderlets: impl IntoIterator<Item = &'a Primitive>,
269    ) -> Result<(), LightingError> {
270        let lighting = lighting.as_ref();
271        let shadow_desc = self.shadowmap_descriptor.get();
272        let new_transforms = self.light_bundle.light_space_transforms(
273            &self.light_bundle.transform().descriptor(),
274            shadow_desc.z_near,
275            shadow_desc.z_far,
276        );
277        for (i, t) in (0..self.light_space_transforms.len()).zip(new_transforms) {
278            self.light_space_transforms.set_item(i, t);
279        }
280        if lighting.geometry_slab.has_queued_updates() {
281            lighting.geometry_slab.commit();
282        }
283        let renderlets = renderlets.into_iter().collect::<Vec<_>>();
284
285        let device = lighting.light_slab.device();
286        let queue = lighting.light_slab.queue();
287        let mut light_slab_buffer = lighting.light_slab_buffer.write().unwrap();
288        let mut stage_slab_buffer = lighting.geometry_slab_buffer.write().unwrap();
289
290        let bindgroup = {
291            light_slab_buffer.update_if_invalid();
292            stage_slab_buffer.update_if_invalid();
293            let stored_light_buffer_creation_time = self.light_slab_buffer_creation_time.swap(
294                light_slab_buffer.creation_time(),
295                std::sync::atomic::Ordering::Relaxed,
296            );
297            let stored_stage_buffer_creation_time = self.stage_slab_buffer_creation_time.swap(
298                stage_slab_buffer.creation_time(),
299                std::sync::atomic::Ordering::Relaxed,
300            );
301            let should_invalidate = light_slab_buffer.creation_time()
302                > stored_light_buffer_creation_time
303                || stage_slab_buffer.creation_time() > stored_stage_buffer_creation_time;
304            self.update_bindgroup.get(should_invalidate, || {
305                log::trace!("recreating shadow mapping bindgroup");
306                Self::create_update_bindgroup(
307                    device,
308                    &lighting.shadow_map_update_bindgroup_layout,
309                    &stage_slab_buffer,
310                    &light_slab_buffer,
311                )
312            })
313        };
314        for (i, atlas_texture) in self.atlas_textures.iter().enumerate() {
315            let mut encoder = device
316                .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Self::LABEL });
317
318            // Update the lighting descriptor to point to this shadow map, which tells the
319            // vertex shader which shadow map we're updating.
320            lighting.lighting_descriptor.modify(|ld| {
321                let id = self.shadowmap_descriptor.id();
322                log::trace!("updating the shadow map {id:?} {i}");
323                ld.update_shadow_map_id = id;
324                ld.update_shadow_map_texture_index = i as u32;
325            });
326            // Sync those changes
327            let _ = lighting.light_slab.commit();
328            let label = format!("{}-view-{i}", Self::LABEL.unwrap());
329            let view = self
330                .update_texture
331                .texture
332                .create_view(&wgpu::TextureViewDescriptor {
333                    label: Some(&label),
334                    base_array_layer: i as u32,
335                    array_layer_count: Some(1),
336                    dimension: Some(wgpu::TextureViewDimension::D2),
337                    ..Default::default()
338                });
339            {
340                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
341                    label: Self::LABEL,
342                    color_attachments: &[],
343                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
344                        view: &view,
345                        depth_ops: Some(wgpu::Operations {
346                            load: wgpu::LoadOp::Clear(1.0),
347                            store: wgpu::StoreOp::Store,
348                        }),
349                        stencil_ops: None,
350                    }),
351                    ..Default::default()
352                });
353                render_pass.set_pipeline(&lighting.shadow_map_update_pipeline);
354                render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
355                let mut count = 0;
356                for rlet in renderlets.iter() {
357                    let id = rlet.id();
358                    let rlet = rlet.descriptor();
359                    let vertex_range = 0..rlet.get_vertex_count();
360                    let instance_range = id.inner()..id.inner() + 1;
361                    render_pass.draw(vertex_range, instance_range);
362                    count += 1;
363                }
364                log::trace!("rendered {count} renderlets to the shadow map");
365            }
366            // Then copy the depth texture to our shadow map atlas in the lighting struct
367            self.blitting_op.run(
368                lighting.light_slab.runtime(),
369                &mut encoder,
370                &self.update_texture,
371                i as u32,
372                &lighting.shadow_map_atlas,
373                atlas_texture,
374            )?;
375            let submission = queue.submit(Some(encoder.finish()));
376            device
377                .poll(wgpu::PollType::WaitForSubmissionIndex(submission))
378                .context(PollSnafu)?;
379        }
380        Ok(())
381    }
382}
383
384#[cfg(test)]
385#[allow(clippy::unused_enumerate_index)]
386mod test {
387    use glam::{UVec2, Vec3};
388
389    use crate::{context::Context, test::BlockOnFuture};
390
391    #[test]
392    fn shadow_mapping_just_cuboid() {
393        let w = 800.0;
394        let h = 800.0;
395        let ctx = Context::headless(w as u32, h as u32).block();
396        let stage = ctx
397            .new_stage()
398            .with_lighting(true)
399            .with_msaa_sample_count(4);
400
401        // let hdr_path =
402        //     std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")).join("img/hdr/night.hdr");
403        // let hdr_img = AtlasImage::from_hdr_path(hdr_path).unwrap();
404
405        // let skybox = Skybox::new(&ctx, hdr_img, camera.id());
406        // stage.set_skybox(skybox);
407        let doc = stage
408            .load_gltf_document_from_path(
409                crate::test::workspace_dir()
410                    .join("gltf")
411                    .join("shadow_mapping_only_cuboid.gltf"),
412            )
413            .unwrap();
414        let camera = doc.cameras.first().unwrap();
415        camera
416            .as_ref()
417            .set_projection(crate::camera::perspective(w, h));
418        stage.use_camera(camera);
419
420        let frame = ctx.get_next_frame().unwrap();
421        stage.render(&frame.view());
422        let img = frame.read_image().block().unwrap();
423        frame.present();
424
425        // Rendering the scene without shadows as a sanity check
426        img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_before.png", img);
427
428        let gltf_light = doc.lights.first().unwrap();
429        let shadow_map = stage
430            .new_shadow_map(gltf_light, UVec2::splat(256), 0.0, 20.0)
431            .unwrap();
432        shadow_map.shadowmap_descriptor.modify(|desc| {
433            desc.bias_min = 0.00008;
434            desc.bias_max = 0.00008;
435        });
436        shadow_map.update(&stage, doc.renderlets_iter()).unwrap();
437
438        let frame = ctx.get_next_frame().unwrap();
439        stage.render(&frame.view());
440        let img = frame.read_image().block().unwrap();
441        img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_after.png", img);
442        frame.present();
443    }
444
445    #[test]
446    fn shadow_mapping_just_cuboid_red_and_blue() {
447        let w = 800.0;
448        let h = 800.0;
449        let ctx = Context::headless(w as u32, h as u32).block();
450        let stage = ctx
451            .new_stage()
452            .with_lighting(true)
453            .with_msaa_sample_count(4);
454
455        let doc = stage
456            .load_gltf_document_from_path(
457                crate::test::workspace_dir()
458                    .join("gltf")
459                    .join("shadow_mapping_only_cuboid_red_and_blue.gltf"),
460            )
461            .unwrap();
462        let camera = doc.cameras.first().unwrap();
463        camera
464            .as_ref()
465            .set_projection(crate::camera::perspective(w, h));
466        stage.use_camera(camera);
467
468        let gltf_light_a = doc.lights.first().unwrap();
469        let gltf_light_b = doc.lights.get(1).unwrap();
470        let shadow_map_a = stage
471            .new_shadow_map(gltf_light_a, UVec2::splat(256), 0.0, 20.0)
472            .unwrap();
473        shadow_map_a.shadowmap_descriptor.modify(|desc| {
474            desc.bias_min = 0.00008;
475            desc.bias_max = 0.00008;
476        });
477        shadow_map_a.update(&stage, doc.renderlets_iter()).unwrap();
478        let shadow_map_b = stage
479            .new_shadow_map(gltf_light_b, UVec2::splat(256), 0.0, 20.0)
480            .unwrap();
481        shadow_map_b.shadowmap_descriptor.modify(|desc| {
482            desc.bias_min = 0.00008;
483            desc.bias_max = 0.00008;
484        });
485        shadow_map_b.update(&stage, doc.renderlets_iter()).unwrap();
486
487        let frame = ctx.get_next_frame().unwrap();
488
489        stage.render(&frame.view());
490        let img = frame.read_image().block().unwrap();
491        img_diff::assert_img_eq(
492            "shadows/shadow_mapping_just_cuboid/red_and_blue/frame.png",
493            img,
494        );
495        frame.present();
496    }
497
498    #[test]
499    fn shadow_mapping_sanity() {
500        let w = 800.0;
501        let h = 800.0;
502        let ctx = Context::headless(w as u32, h as u32)
503            .block()
504            .with_shadow_mapping_atlas_texture_size([1024, 1024, 2]);
505        let stage = ctx.new_stage().with_lighting(true);
506
507        let doc = stage
508            .load_gltf_document_from_path(
509                crate::test::workspace_dir()
510                    .join("gltf")
511                    .join("shadow_mapping_sanity.gltf"),
512            )
513            .unwrap();
514        let camera = doc.cameras.first().unwrap();
515        camera
516            .as_ref()
517            .set_projection(crate::camera::perspective(w, h));
518        stage.use_camera(camera);
519
520        let frame = ctx.get_next_frame().unwrap();
521        stage.render(&frame.view());
522        let img = frame.read_image().block().unwrap();
523        frame.present();
524
525        // Rendering the scene without shadows as a sanity check
526        img_diff::assert_img_eq("shadows/shadow_mapping_sanity/scene_before.png", img);
527
528        let gltf_light = doc.lights.first().unwrap();
529        assert_eq!(
530            gltf_light.descriptor().transform_id,
531            gltf_light.transform().id(),
532            "light's global transform id is different from its transform_id"
533        );
534
535        let shadows = stage
536            .new_shadow_map(gltf_light, UVec2::new(w as u32, h as u32), 0.0, 20.0)
537            .unwrap();
538        shadows.update(&stage, doc.renderlets_iter()).unwrap();
539
540        // Extra sanity checks
541        // {
542        //     use crate::texture::DepthTexture;
543        //     use image::Luma;
544        //     {
545        //         // Ensure the state of the "update texture", which receives the depth of the scene on update
546        //         let shadow_map_update_texture =
547        //             DepthTexture::try_new_from(&ctx, shadows.update_texture.clone()).unwrap();
548        //         let mut shadow_map_update_img = shadow_map_update_texture.read_image().unwrap();
549        //         img_diff::normalize_gray_img(&mut shadow_map_update_img);
550        //         img_diff::save(
551        //             "shadows/shadow_mapping_sanity/shadows_update_texture.png",
552        //             shadow_map_update_img,
553        //         );
554        //     }
555
556        //     {
557        //         let lighting: &Lighting = stage.as_ref();
558        //         let shadow_depth_buffer = lighting.shadow_map_atlas.atlas_img_buffer(&ctx, 0);
559        //         let shadow_depth_img = shadow_depth_buffer
560        //             .into_image::<f32, Luma<f32>>(ctx.get_device())
561        //             .unwrap();
562        //         let shadow_depth_img = shadow_depth_img.into_luma8();
563        //         let mut depth_img = shadow_depth_img.clone();
564        //         img_diff::normalize_gray_img(&mut depth_img);
565        //         img_diff::save("shadows/shadow_mapping_sanity/depth.png", depth_img);
566        //     }
567        // }
568
569        // Now do the rendering *with the shadow map* to see if it works.
570        let frame = ctx.get_next_frame().unwrap();
571        stage.render(&frame.view());
572
573        let img = frame.read_image().block().unwrap();
574        frame.present();
575        img_diff::assert_img_eq_cfg(
576            "shadows/shadow_mapping_sanity/stage_render.png",
577            img,
578            img_diff::DiffCfg {
579                image_threshold: 0.01,
580                ..Default::default()
581            },
582        );
583    }
584
585    #[test]
586    fn shadow_mapping_spot_lights() {
587        let w = 800.0;
588        let h = 800.0;
589        let ctx = Context::headless(w as u32, h as u32).block();
590        let stage = ctx
591            .new_stage()
592            .with_lighting(true)
593            .with_msaa_sample_count(4);
594
595        let doc = stage
596            .load_gltf_document_from_path(
597                crate::test::workspace_dir()
598                    .join("gltf")
599                    .join("shadow_mapping_spots.glb"),
600            )
601            .unwrap();
602        let camera = doc.cameras.first().unwrap();
603        camera
604            .as_ref()
605            .set_projection(crate::camera::perspective(w, h));
606        stage.use_camera(camera);
607
608        let mut shadow_maps = vec![];
609        let z_near = 0.1;
610        let z_far = 100.0;
611        for (_i, light_bundle) in doc.lights.iter().enumerate() {
612            {
613                let desc = light_bundle.as_spot().unwrap().descriptor();
614                let (p, v) = desc.shadow_mapping_projection_and_view(
615                    &light_bundle.transform().as_mat4(),
616                    z_near,
617                    z_far,
618                );
619                let temp_camera = stage.new_camera().with_projection_and_view(p, v);
620                stage.use_camera(temp_camera);
621                let frame = ctx.get_next_frame().unwrap();
622                stage.render(&frame.view());
623                let _img = frame.read_image().block().unwrap();
624                // img_diff::assert_img_eq(
625                //     &format!("shadows/shadow_mapping_spots/light_pov_{i}.png"),
626                //     img,
627                // );
628                frame.present();
629            }
630            let shadow = stage
631                .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
632                .unwrap();
633            shadow.shadowmap_descriptor.modify(|desc| {
634                desc.bias_min = f32::EPSILON;
635                desc.bias_max = f32::EPSILON;
636            });
637
638            shadow.update(&stage, doc.renderlets_iter()).unwrap();
639            shadow_maps.push(shadow);
640        }
641
642        stage.use_camera(camera);
643
644        let frame = ctx.get_next_frame().unwrap();
645        stage.render(&frame.view());
646        let img = frame.read_image().block().unwrap();
647        img_diff::assert_img_eq("shadows/shadow_mapping_spots/frame.png", img);
648        frame.present();
649    }
650
651    #[test]
652    fn shadow_mapping_point_lights() {
653        let w = 800.0;
654        let h = 800.0;
655        let ctx = Context::headless(w as u32, h as u32).block();
656        let stage = ctx
657            .new_stage()
658            .with_lighting(true)
659            .with_background_color(Vec3::splat(0.05087).extend(1.0))
660            .with_msaa_sample_count(4);
661        let doc = stage
662            .load_gltf_document_from_path(
663                crate::test::workspace_dir()
664                    .join("gltf")
665                    .join("shadow_mapping_points.glb"),
666            )
667            .unwrap();
668        let camera = doc.cameras.first().unwrap();
669        camera
670            .as_ref()
671            .set_projection(crate::camera::perspective(w, h));
672        stage.use_camera(camera);
673
674        let mut shadows = vec![];
675        let z_near = 0.1;
676        let z_far = 100.0;
677        for (i, light_bundle) in doc.lights.iter().enumerate() {
678            {
679                let desc = light_bundle.as_point().unwrap().descriptor();
680                println!("point light {i}: {desc:?}");
681                let (p, vs) = desc.shadow_mapping_projection_and_view_matrices(
682                    &light_bundle.transform().as_mat4(),
683                    z_near,
684                    z_far,
685                );
686                for (_j, v) in vs.into_iter().enumerate() {
687                    stage.use_camera(stage.new_camera().with_projection_and_view(p, v));
688                    let frame = ctx.get_next_frame().unwrap();
689                    stage.render(&frame.view());
690                    let _img = frame.read_image().block().unwrap();
691                    // img_diff::assert_img_eq(
692                    //     &format!("shadows/shadow_mapping_points/light_{i}_pov_{j}.png"),
693                    //     img,
694                    // );
695                    frame.present();
696                }
697            }
698            let shadow = stage
699                .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
700                .unwrap();
701            shadow.shadowmap_descriptor.modify(|desc| {
702                desc.pcf_samples = 16;
703                desc.bias_min = 0.00010;
704                desc.bias_max = 0.010;
705            });
706            shadow.update(&stage, doc.renderlets_iter()).unwrap();
707            shadows.push(shadow);
708        }
709
710        stage.use_camera(camera);
711
712        let frame = ctx.get_next_frame().unwrap();
713        stage.render(&frame.view());
714        let img = frame.read_image().block().unwrap();
715        img_diff::assert_img_eq("shadows/shadow_mapping_points/frame.png", img);
716        frame.present();
717    }
718}