Skip to main content

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
186            .geometry_slab_buffer
187            .read()
188            .expect("geometry_slab_buffer read");
189        let is_point_light = analytical_light_bundle.style() == LightStyle::Point;
190        let count = if is_point_light { 6 } else { 1 };
191        let atlas = &lighting.shadow_map_atlas;
192        let image = AtlasImage::new(size, crate::atlas::AtlasImageFormat::R32FLOAT);
193        // UNWRAP: safe because we know there's one in here
194        let atlas_textures = atlas.add_images(vec![&image; count])?;
195        let atlas_len = atlas.len();
196        // Regardless of light type, we only create one depth texture,
197        // but that texture may be of layer 1 or 6
198        let label = format!("shadow-map-{atlas_len}");
199        let update_texture = crate::texture::Texture::create_depth_texture_for_shadow_map(
200            atlas.device(),
201            size.x,
202            size.y,
203            1,
204            Some(&label),
205            is_point_light,
206        );
207        let atlas_textures_array = lighting
208            .light_slab
209            .new_array(atlas_textures.iter().map(|t| t.id()));
210        let blitting_op = AtlasBlittingOperation::new(
211            &lighting.shadow_map_update_blitter,
212            atlas,
213            if is_point_light { 6 } else { 1 },
214        );
215        let light_space_transforms =
216            lighting
217                .light_slab
218                .new_array(analytical_light_bundle.light_space_transforms(
219                    &analytical_light_bundle.transform().descriptor(),
220                    z_near,
221                    z_far,
222                ));
223        let shadowmap_descriptor = lighting.light_slab.new_value(ShadowMapDescriptor {
224            light_space_transforms_array: light_space_transforms.array(),
225            z_near,
226            z_far,
227            atlas_textures_array: atlas_textures_array.array(),
228            bias_min: 0.0005,
229            bias_max: 0.005,
230            pcf_samples: 4,
231        });
232        // Set the descriptor in the light, so the shader knows to use it
233        analytical_light_bundle.light_descriptor.modify(|light| {
234            light.shadow_map_desc_id = shadowmap_descriptor.id();
235        });
236        let light_slab_buffer = lighting.commit();
237        let update_bindgroup = ManagedBindGroup::from(ShadowMap::create_update_bindgroup(
238            lighting.light_slab.device(),
239            &lighting.shadow_map_update_bindgroup_layout,
240            stage_slab_buffer.deref(),
241            &light_slab_buffer,
242        ));
243
244        Ok(ShadowMap {
245            stage_slab_buffer_creation_time: Arc::new(stage_slab_buffer.creation_time().into()),
246            light_slab_buffer_creation_time: Arc::new(light_slab_buffer.creation_time().into()),
247            shadowmap_descriptor,
248            light_space_transforms,
249            update_bindgroup,
250            atlas_textures,
251            _atlas_textures_array: atlas_textures_array,
252            update_texture,
253            blitting_op,
254            light_bundle: analytical_light_bundle.clone().into_generic(),
255        })
256    }
257
258    /// Update the `ShadowMap`, rendering the given [`Primitive`]s to the map as
259    /// shadow casters.
260    ///
261    /// The `ShadowMap` contains a weak reference to the [`AnalyticalLight`]
262    /// used to create it. Updates made to this `AnalyticalLight` will
263    /// automatically propogate to this `ShadowMap`.
264    ///
265    /// ## Errors
266    /// If the `AnalyticalLight` used to create this `ShadowMap` has been
267    /// dropped, calling this function will err.
268    pub fn update<'a>(
269        &self,
270        lighting: impl AsRef<Lighting>,
271        renderlets: impl IntoIterator<Item = &'a Primitive>,
272    ) -> Result<(), LightingError> {
273        let lighting = lighting.as_ref();
274        let shadow_desc = self.shadowmap_descriptor.get();
275        let new_transforms = self.light_bundle.light_space_transforms(
276            &self.light_bundle.transform().descriptor(),
277            shadow_desc.z_near,
278            shadow_desc.z_far,
279        );
280        for (i, t) in (0..self.light_space_transforms.len()).zip(new_transforms) {
281            self.light_space_transforms.set_item(i, t);
282        }
283        if lighting.geometry_slab.has_queued_updates() {
284            lighting.geometry_slab.commit();
285        }
286        let renderlets = renderlets.into_iter().collect::<Vec<_>>();
287
288        let device = lighting.light_slab.device();
289        let queue = lighting.light_slab.queue();
290        let mut light_slab_buffer = lighting
291            .light_slab_buffer
292            .write()
293            .expect("light_slab_buffer write");
294        let mut stage_slab_buffer = lighting
295            .geometry_slab_buffer
296            .write()
297            .expect("geometry_slab_buffer write");
298
299        let bindgroup = {
300            light_slab_buffer.update_if_invalid();
301            stage_slab_buffer.update_if_invalid();
302            let stored_light_buffer_creation_time = self.light_slab_buffer_creation_time.swap(
303                light_slab_buffer.creation_time(),
304                std::sync::atomic::Ordering::Relaxed,
305            );
306            let stored_stage_buffer_creation_time = self.stage_slab_buffer_creation_time.swap(
307                stage_slab_buffer.creation_time(),
308                std::sync::atomic::Ordering::Relaxed,
309            );
310            let should_invalidate = light_slab_buffer.creation_time()
311                > stored_light_buffer_creation_time
312                || stage_slab_buffer.creation_time() > stored_stage_buffer_creation_time;
313            self.update_bindgroup.get(should_invalidate, || {
314                log::trace!("recreating shadow mapping bindgroup");
315                Self::create_update_bindgroup(
316                    device,
317                    &lighting.shadow_map_update_bindgroup_layout,
318                    &stage_slab_buffer,
319                    &light_slab_buffer,
320                )
321            })
322        };
323        for (i, atlas_texture) in self.atlas_textures.iter().enumerate() {
324            let mut encoder = device
325                .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Self::LABEL });
326
327            // Update the lighting descriptor to point to this shadow map, which
328            // tells the vertex shader which shadow map we're
329            // updating.
330            lighting.lighting_descriptor.modify(|ld| {
331                let id = self.shadowmap_descriptor.id();
332                log::trace!("updating the shadow map {id:?} {i}");
333                ld.update_shadow_map_id = id;
334                ld.update_shadow_map_texture_index = i as u32;
335            });
336            // Sync those changes
337            let _ = lighting.light_slab.commit();
338            let label = format!("{}-view-{i}", Self::LABEL.unwrap());
339            let view = self
340                .update_texture
341                .texture
342                .create_view(&wgpu::TextureViewDescriptor {
343                    label: Some(&label),
344                    base_array_layer: i as u32,
345                    array_layer_count: Some(1),
346                    dimension: Some(wgpu::TextureViewDimension::D2),
347                    ..Default::default()
348                });
349            {
350                let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
351                    label: Self::LABEL,
352                    color_attachments: &[],
353                    depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
354                        view: &view,
355                        depth_ops: Some(wgpu::Operations {
356                            load: wgpu::LoadOp::Clear(1.0),
357                            store: wgpu::StoreOp::Store,
358                        }),
359                        stencil_ops: None,
360                    }),
361                    ..Default::default()
362                });
363                render_pass.set_pipeline(&lighting.shadow_map_update_pipeline);
364                render_pass.set_bind_group(0, Some(bindgroup.as_ref()), &[]);
365                let mut count = 0;
366                for rlet in renderlets.iter() {
367                    let id = rlet.id();
368                    let rlet = rlet.descriptor();
369                    let vertex_range = 0..rlet.get_vertex_count();
370                    let instance_range = id.inner()..id.inner() + 1;
371                    render_pass.draw(vertex_range, instance_range);
372                    count += 1;
373                }
374                log::trace!("rendered {count} renderlets to the shadow map");
375            }
376            // Then copy the depth texture to our shadow map atlas in the
377            // lighting struct
378            self.blitting_op.run(
379                lighting.light_slab.runtime(),
380                &mut encoder,
381                &self.update_texture,
382                i as u32,
383                &lighting.shadow_map_atlas,
384                atlas_texture,
385            )?;
386            let submission = queue.submit(Some(encoder.finish()));
387            device
388                .poll(wgpu::PollType::WaitForSubmissionIndex(submission))
389                .context(PollSnafu)?;
390        }
391        Ok(())
392    }
393}
394
395#[cfg(test)]
396#[allow(clippy::unused_enumerate_index)]
397mod test {
398    use glam::{UVec2, Vec3};
399
400    use crate::{context::Context, test::BlockOnFuture};
401
402    #[test]
403    fn shadow_mapping_just_cuboid() {
404        let w = 800.0;
405        let h = 800.0;
406        let ctx = Context::headless(w as u32, h as u32).block();
407        let stage = ctx
408            .new_stage()
409            .with_lighting(true)
410            .with_msaa_sample_count(4);
411
412        // let hdr_path =
413        //     std::path::PathBuf::from(std::env!("CARGO_WORKSPACE_DIR")).join("
414        // img/hdr/ night.hdr"); let hdr_img =
415        // AtlasImage::from_hdr_path(hdr_path).unwrap();
416
417        // let skybox = Skybox::new(&ctx, hdr_img, camera.id());
418        // stage.set_skybox(skybox);
419        let doc = stage
420            .load_gltf_document_from_path(
421                crate::test::workspace_dir()
422                    .join("gltf")
423                    .join("shadow_mapping_only_cuboid.gltf"),
424            )
425            .unwrap();
426        let camera = doc.cameras.first().unwrap();
427        camera
428            .as_ref()
429            .set_projection(crate::camera::perspective(w, h));
430        stage.use_camera(camera);
431
432        let frame = ctx.get_next_frame().unwrap();
433        stage.render(&frame.view());
434        let img = frame.read_image().block().unwrap();
435        frame.present();
436
437        // Rendering the scene without shadows as a sanity check
438        img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_before.png", img);
439
440        let gltf_light = doc.lights.first().unwrap();
441        let shadow_map = stage
442            .new_shadow_map(gltf_light, UVec2::splat(256), 0.0, 20.0)
443            .unwrap();
444        shadow_map.shadowmap_descriptor.modify(|desc| {
445            desc.bias_min = 0.00008;
446            desc.bias_max = 0.00008;
447        });
448        shadow_map.update(&stage, doc.renderlets_iter()).unwrap();
449
450        let frame = ctx.get_next_frame().unwrap();
451        stage.render(&frame.view());
452        let img = frame.read_image().block().unwrap();
453        img_diff::assert_img_eq("shadows/shadow_mapping_just_cuboid/scene_after.png", img);
454        frame.present();
455    }
456
457    #[test]
458    fn shadow_mapping_just_cuboid_red_and_blue() {
459        let w = 800.0;
460        let h = 800.0;
461        let ctx = Context::headless(w as u32, h as u32).block();
462        let stage = ctx
463            .new_stage()
464            .with_lighting(true)
465            .with_msaa_sample_count(4);
466
467        let doc = stage
468            .load_gltf_document_from_path(
469                crate::test::workspace_dir()
470                    .join("gltf")
471                    .join("shadow_mapping_only_cuboid_red_and_blue.gltf"),
472            )
473            .unwrap();
474        let camera = doc.cameras.first().unwrap();
475        camera
476            .as_ref()
477            .set_projection(crate::camera::perspective(w, h));
478        stage.use_camera(camera);
479
480        let gltf_light_a = doc.lights.first().unwrap();
481        let gltf_light_b = doc.lights.get(1).unwrap();
482        let shadow_map_a = stage
483            .new_shadow_map(gltf_light_a, UVec2::splat(256), 0.0, 20.0)
484            .unwrap();
485        shadow_map_a.shadowmap_descriptor.modify(|desc| {
486            desc.bias_min = 0.00008;
487            desc.bias_max = 0.00008;
488        });
489        shadow_map_a.update(&stage, doc.renderlets_iter()).unwrap();
490        let shadow_map_b = stage
491            .new_shadow_map(gltf_light_b, UVec2::splat(256), 0.0, 20.0)
492            .unwrap();
493        shadow_map_b.shadowmap_descriptor.modify(|desc| {
494            desc.bias_min = 0.00008;
495            desc.bias_max = 0.00008;
496        });
497        shadow_map_b.update(&stage, doc.renderlets_iter()).unwrap();
498
499        let frame = ctx.get_next_frame().unwrap();
500
501        stage.render(&frame.view());
502        let img = frame.read_image().block().unwrap();
503        img_diff::assert_img_eq(
504            "shadows/shadow_mapping_just_cuboid/red_and_blue/frame.png",
505            img,
506        );
507        frame.present();
508    }
509
510    #[test]
511    fn shadow_mapping_sanity() {
512        let w = 800.0;
513        let h = 800.0;
514        let ctx = Context::headless(w as u32, h as u32)
515            .block()
516            .with_shadow_mapping_atlas_texture_size([1024, 1024, 2]);
517        let stage = ctx.new_stage().with_lighting(true);
518
519        let doc = stage
520            .load_gltf_document_from_path(
521                crate::test::workspace_dir()
522                    .join("gltf")
523                    .join("shadow_mapping_sanity.gltf"),
524            )
525            .unwrap();
526        let camera = doc.cameras.first().unwrap();
527        camera
528            .as_ref()
529            .set_projection(crate::camera::perspective(w, h));
530        stage.use_camera(camera);
531
532        let frame = ctx.get_next_frame().unwrap();
533        stage.render(&frame.view());
534        let img = frame.read_image().block().unwrap();
535        frame.present();
536
537        // Rendering the scene without shadows as a sanity check
538        img_diff::assert_img_eq("shadows/shadow_mapping_sanity/scene_before.png", img);
539
540        let gltf_light = doc.lights.first().unwrap();
541        assert_eq!(
542            gltf_light.descriptor().transform_id,
543            gltf_light.transform().id(),
544            "light's global transform id is different from its transform_id"
545        );
546
547        let shadows = stage
548            .new_shadow_map(gltf_light, UVec2::new(w as u32, h as u32), 0.0, 20.0)
549            .unwrap();
550        shadows.update(&stage, doc.renderlets_iter()).unwrap();
551
552        // Extra sanity checks
553        // {
554        //     use crate::texture::DepthTexture;
555        //     use image::Luma;
556        //     {
557        //         // Ensure the state of the "update texture", which receives
558        // the depth of the scene on update         let
559        // shadow_map_update_texture =             
560        // DepthTexture::try_new_from(&ctx, shadows.update_texture.
561        // clone()).unwrap();         let mut shadow_map_update_img =
562        // shadow_map_update_texture.read_image().unwrap();
563        //         img_diff::normalize_gray_img(&mut shadow_map_update_img);
564        //         img_diff::save(
565        //             
566        // "shadows/shadow_mapping_sanity/shadows_update_texture.png",
567        //             shadow_map_update_img,
568        //         );
569        //     }
570
571        //     {
572        //         let lighting: &Lighting = stage.as_ref();
573        //         let shadow_depth_buffer =
574        // lighting.shadow_map_atlas.atlas_img_buffer(&ctx, 0);         let
575        // shadow_depth_img = shadow_depth_buffer             .into_image::<f32,
576        // Luma<f32>>(ctx.get_device())             .unwrap();
577        //         let shadow_depth_img = shadow_depth_img.into_luma8();
578        //         let mut depth_img = shadow_depth_img.clone();
579        //         img_diff::normalize_gray_img(&mut depth_img);
580        //         img_diff::save("shadows/shadow_mapping_sanity/depth.png",
581        // depth_img);     }
582        // }
583
584        // Now do the rendering *with the shadow map* to see if it works.
585        let frame = ctx.get_next_frame().unwrap();
586        stage.render(&frame.view());
587
588        let img = frame.read_image().block().unwrap();
589        frame.present();
590        img_diff::assert_img_eq_cfg(
591            "shadows/shadow_mapping_sanity/stage_render.png",
592            img,
593            img_diff::DiffCfg {
594                image_threshold: 0.01,
595                ..Default::default()
596            },
597        );
598    }
599
600    #[test]
601    fn shadow_mapping_spot_lights() {
602        let w = 800.0;
603        let h = 800.0;
604        let ctx = Context::headless(w as u32, h as u32).block();
605        let stage = ctx
606            .new_stage()
607            .with_lighting(true)
608            .with_msaa_sample_count(4);
609
610        let doc = stage
611            .load_gltf_document_from_path(
612                crate::test::workspace_dir()
613                    .join("gltf")
614                    .join("shadow_mapping_spots.glb"),
615            )
616            .unwrap();
617        let camera = doc.cameras.first().unwrap();
618        camera
619            .as_ref()
620            .set_projection(crate::camera::perspective(w, h));
621        stage.use_camera(camera);
622
623        let mut shadow_maps = vec![];
624        let z_near = 0.1;
625        let z_far = 100.0;
626        for (_i, light_bundle) in doc.lights.iter().enumerate() {
627            {
628                let desc = light_bundle.as_spot().unwrap().descriptor();
629                let (p, v) = desc.shadow_mapping_projection_and_view(
630                    &light_bundle.transform().as_mat4(),
631                    z_near,
632                    z_far,
633                );
634                let temp_camera = stage.new_camera().with_projection_and_view(p, v);
635                stage.use_camera(temp_camera);
636                let frame = ctx.get_next_frame().unwrap();
637                stage.render(&frame.view());
638                let _img = frame.read_image().block().unwrap();
639                // img_diff::assert_img_eq(
640                //     &format!("shadows/shadow_mapping_spots/light_pov_{i}.png"
641                // ),     img,
642                // );
643                frame.present();
644            }
645            let shadow = stage
646                .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
647                .unwrap();
648            shadow.shadowmap_descriptor.modify(|desc| {
649                desc.bias_min = f32::EPSILON;
650                desc.bias_max = f32::EPSILON;
651            });
652
653            shadow.update(&stage, doc.renderlets_iter()).unwrap();
654            shadow_maps.push(shadow);
655        }
656
657        stage.use_camera(camera);
658
659        let frame = ctx.get_next_frame().unwrap();
660        stage.render(&frame.view());
661        let img = frame.read_image().block().unwrap();
662        img_diff::assert_img_eq("shadows/shadow_mapping_spots/frame.png", img);
663        frame.present();
664    }
665
666    #[test]
667    fn shadow_mapping_point_lights() {
668        let w = 800.0;
669        let h = 800.0;
670        let ctx = Context::headless(w as u32, h as u32).block();
671        let stage = ctx
672            .new_stage()
673            .with_lighting(true)
674            .with_background_color(Vec3::splat(0.05087).extend(1.0))
675            .with_msaa_sample_count(4);
676        let doc = stage
677            .load_gltf_document_from_path(
678                crate::test::workspace_dir()
679                    .join("gltf")
680                    .join("shadow_mapping_points.glb"),
681            )
682            .unwrap();
683        let camera = doc.cameras.first().unwrap();
684        camera
685            .as_ref()
686            .set_projection(crate::camera::perspective(w, h));
687        stage.use_camera(camera);
688
689        let mut shadows = vec![];
690        let z_near = 0.1;
691        let z_far = 100.0;
692        for (i, light_bundle) in doc.lights.iter().enumerate() {
693            {
694                let desc = light_bundle.as_point().unwrap().descriptor();
695                println!("point light {i}: {desc:?}");
696                let (p, vs) = desc.shadow_mapping_projection_and_view_matrices(
697                    &light_bundle.transform().as_mat4(),
698                    z_near,
699                    z_far,
700                );
701                for (_j, v) in vs.into_iter().enumerate() {
702                    stage.use_camera(stage.new_camera().with_projection_and_view(p, v));
703                    let frame = ctx.get_next_frame().unwrap();
704                    stage.render(&frame.view());
705                    let _img = frame.read_image().block().unwrap();
706                    // img_diff::assert_img_eq(
707                    //     &format!("shadows/shadow_mapping_points/
708                    // light_{i}_pov_{j}.png"),     img,
709                    // );
710                    frame.present();
711                }
712            }
713            let shadow = stage
714                .new_shadow_map(light_bundle, UVec2::splat(256), z_near, z_far)
715                .unwrap();
716            shadow.shadowmap_descriptor.modify(|desc| {
717                desc.pcf_samples = 16;
718                desc.bias_min = 0.00010;
719                desc.bias_max = 0.010;
720            });
721            shadow.update(&stage, doc.renderlets_iter()).unwrap();
722            shadows.push(shadow);
723        }
724
725        stage.use_camera(camera);
726
727        let frame = ctx.get_next_frame().unwrap();
728        stage.render(&frame.view());
729        let img = frame.read_image().block().unwrap();
730        img_diff::assert_img_eq("shadows/shadow_mapping_points/frame.png", img);
731        frame.present();
732    }
733}