Skip to main content

cvkg_render_gpu/passes/
shadow.rs

1//! Shadow pass types and KvasirNode — renders depth-only shadow map from
2//! light's perspective using 3D mesh data.
3
4use crate::kvasir::{ExecutionContext, KvasirNode, ResourceId};
5use crate::kvasir::nodes::PassId;
6use glam::Mat4;
7use wgpu::Buffer;
8
9/// Directional light for shadow rendering.
10#[derive(Debug, Clone, Copy)]
11pub struct DirectionalLight {
12    pub direction: glam::Vec3,
13    pub color: glam::Vec3,
14    pub intensity: f32,
15}
16
17impl Default for DirectionalLight {
18    fn default() -> Self {
19        Self {
20            direction: glam::Vec3::new(0.0, -1.0, 0.0),
21            color: glam::Vec3::ONE,
22            intensity: 1.0,
23        }
24    }
25}
26
27/// GPU resources for a single 3D mesh instance ready for rendering.
28#[derive(Debug, Clone)]
29pub struct GpuMesh3d {
30    /// Vertex buffer (position, normal, UV, etc.).
31    pub vertex_buffer: Buffer,
32    /// Index buffer.
33    pub index_buffer: Buffer,
34    /// Number of indices to draw.
35    pub index_count: u32,
36    /// Per-instance model matrix.
37    pub transform: Mat4,
38}
39
40/// Shadow pass node — renders depth-only shadow map from light's perspective.
41pub struct ShadowNode {
42    pub light: DirectionalLight,
43    pub shadow_map: ResourceId,
44    /// GPU-ready mesh instances to render into the shadow map.
45    pub mesh_instances: Vec<GpuMesh3d>,
46    /// Bounds of the scene — used for light VP frustum computation.
47    pub scene_radius: f32,
48}
49
50impl KvasirNode for ShadowNode {
51    fn label(&self) -> &'static str {
52        "ShadowPass"
53    }
54
55    fn inputs(&self) -> &[ResourceId] {
56        &[]
57    }
58
59    fn outputs(&self) -> &[ResourceId] {
60        std::slice::from_ref(&self.shadow_map)
61    }
62
63    fn pass_id(&self) -> PassId {
64        PassId::Shadow
65    }
66
67    fn execute(&self, ctx: &mut ExecutionContext) {
68        // Compute light VP: orthographic frustum from light direction toward scene origin.
69        let light_dir = self.light.direction;
70        let scene_center = glam::Vec3::ZERO;
71        let light_pos = scene_center + light_dir * self.scene_radius * 2.0;
72        let light_view = glam::Mat4::look_at_lh(light_pos, scene_center, glam::Vec3::Y);
73
74        // Orthographic projection covering the scene bounds.
75        let r = self.scene_radius;
76        let light_proj = glam::Mat4::orthographic_lh(-r, r, -r, r, 0.0, self.scene_radius * 4.0);
77        let _light_vp = light_proj * light_view;
78
79        tracing::debug!(
80            "ShadowNode::execute — instances={}, shadow_map={:?}, light_dir=({:.2},{:.2},{:.2})",
81            self.mesh_instances.len(),
82            self.shadow_map,
83            light_dir.x, light_dir.y, light_dir.z,
84        );
85
86        // Get the shadow map texture view from the resource registry.
87        let shadow_view = match ctx.registry.get_texture_view(self.shadow_map) {
88            Some(v) => v,
89            None => {
90                tracing::error!(
91                    "ShadowNode: missing shadow map texture view for {:?}",
92                    self.shadow_map,
93                );
94                return;
95            }
96        };
97
98        // Create a depth-only render pass.
99        let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
100            label: Some("Shadow Pass (Depth-Only)"),
101            color_attachments: &[], // No color output.
102            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
103                view: &shadow_view,
104                depth_ops: Some(wgpu::Operations {
105                    load: wgpu::LoadOp::Clear(1.0),
106                    store: wgpu::StoreOp::Store,
107                }),
108                stencil_ops: None,
109            }),
110            timestamp_writes: None,
111            occlusion_query_set: None,
112            multiview_mask: None,
113        });
114
115        // For each mesh, set vertex/index buffers and draw depth only.
116        for mesh in self.mesh_instances.iter() {
117            // Set vertex buffer (assumes interleaved position at location 0).
118            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
119            pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
120
121            // Bind per-instance transform and draw.
122            pass.draw_indexed(0..mesh.index_count, 0, 0..1);
123        }
124    }
125}