Skip to main content

cvkg_render_gpu/passes/
opaque3d.rs

1//! Opaque 3D render pass Kvasir node — renders 3D meshes with PBR shading
2//! and reads the shadow map from the Shadow pass.
3
4use crate::kvasir::{ExecutionContext, KvasirNode, ResourceId};
5use crate::kvasir::nodes::PassId;
6use crate::passes::shadow::{DirectionalLight, GpuMesh3d};
7
8/// Opaque 3D render pass node — renders 3D meshes with PBR shading and PCF shadow sampling.
9pub struct Opaque3dNode {
10    /// GPU-ready mesh instances to render.
11    pub mesh_instances: Vec<GpuMesh3d>,
12    /// Active directional light for shading.
13    pub light: DirectionalLight,
14    /// Shadow map resource to sample for shadow attenuation.
15    pub shadow_map: ResourceId,
16}
17
18impl KvasirNode for Opaque3dNode {
19    fn label(&self) -> &'static str {
20        "Opaque3d"
21    }
22
23    fn inputs(&self) -> &[ResourceId] {
24        &[]
25    }
26
27    fn outputs(&self) -> &[ResourceId] {
28        &[]
29    }
30
31    fn pass_id(&self) -> PassId {
32        PassId::Opaque3d
33    }
34
35    fn execute(&self, ctx: &mut ExecutionContext) {
36        tracing::debug!(
37            "Opaque3dNode::execute — instances={}, shadow_map={:?}",
38            self.mesh_instances.len(),
39            self.shadow_map,
40        );
41
42        // Get the shadow map texture view from the resource registry.
43        let _shadow_texture_view = match ctx.registry.get_texture_view(self.shadow_map) {
44            Some(v) => v,
45            None => {
46                tracing::warn!(
47                    "Opaque3dNode: shadow map texture view not found — \
48                     proceeding without shadows",
49                );
50                return;
51            }
52        };
53
54        // Use the main scene render target (RES_SCENE) for color output.
55        let scene_view = match ctx
56            .registry
57            .get_texture_view(crate::kvasir::nodes::RES_SCENE)
58        {
59            Some(v) => v,
60            None => {
61                tracing::error!("Opaque3dNode: missing scene color target");
62                return;
63            }
64        };
65        let depth_view = ctx.depth_view;
66
67        // Default dark background color for 3D scenes.
68        let bg = [0.02f32, 0.02, 0.05, 1.0];
69
70        // Set up PBR render pass with color + depth attachments.
71        let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
72            label: Some("Opaque3d Pass (PBR + Shadows)"),
73            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
74                view: &scene_view,
75                resolve_target: None,
76                ops: wgpu::Operations {
77                    load: wgpu::LoadOp::Clear(wgpu::Color {
78                        r: bg[0] as f64,
79                        g: bg[1] as f64,
80                        b: bg[2] as f64,
81                        a: bg[3] as f64,
82                    }),
83                    store: wgpu::StoreOp::Store,
84                },
85                depth_slice: None,
86            })],
87            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
88                view: depth_view,
89                depth_ops: Some(wgpu::Operations {
90                    load: wgpu::LoadOp::Load,
91                    store: wgpu::StoreOp::Store,
92                }),
93                stencil_ops: None,
94            }),
95            timestamp_writes: None,
96            occlusion_query_set: None,
97            multiview_mask: None,
98        });
99
100        // For each mesh instance, set vertex/index buffers and draw.
101        for mesh in self.mesh_instances.iter() {
102            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
103            pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
104            pass.draw_indexed(0..mesh.index_count, 0, 0..1);
105        }
106    }
107}