cvkg_render_gpu/passes/
opaque3d.rs1use crate::kvasir::nodes::PassId;
5use crate::kvasir::{ExecutionContext, KvasirNode, ResourceId};
6use crate::passes::shadow::{DirectionalLight, GpuMesh3d};
7
8pub struct Opaque3dNode {
10 pub mesh_instances: Vec<GpuMesh3d>,
12 pub light: DirectionalLight,
14 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 if self.mesh_instances.is_empty() {
43 return;
44 }
45
46 let scene_view = match ctx
48 .registry
49 .get_texture_view(crate::kvasir::nodes::RES_SCENE)
50 {
51 Some(v) => v,
52 None => {
53 tracing::error!("Opaque3dNode: missing scene color target");
54 return;
55 }
56 };
57 let depth_view = ctx.depth_view;
58
59 let shadow_bind_group = match ctx.registry.get_texture_view(self.shadow_map) {
61 Some(shadow_view) => {
62 let shadow_sampler = match ctx.renderer.shadow_sampler.as_ref() {
63 Some(s) => s,
64 None => {
65 tracing::warn!("Opaque3dNode: missing shadow sampler");
66 return;
67 }
68 };
69
70 let light_dir = self.light.direction;
72 let scene_radius = 100.0;
73 let light_pos = glam::Vec3::ZERO + light_dir * scene_radius * 2.0;
74 let light_view = glam::Mat4::look_at_lh(light_pos, glam::Vec3::ZERO, glam::Vec3::Y);
75 let light_proj = glam::Mat4::orthographic_lh(
76 -scene_radius,
77 scene_radius,
78 -scene_radius,
79 scene_radius,
80 0.0,
81 scene_radius * 4.0,
82 );
83 let light_vp = light_proj * light_view;
84
85 let mut scene = ctx.renderer.current_scene;
86 scene.light_direction = light_dir.to_array();
87 scene.light_color = self.light.color.to_array();
88 scene.light_vp = light_vp;
89 ctx.queue
90 .write_buffer(&ctx.renderer.scene_buffer, 0, bytemuck::bytes_of(&scene));
91
92 let ibl_view_owned = ctx
93 .registry
94 .get_texture_view(crate::kvasir::nodes::RES_BLUR_A);
95 let ibl_view = match &ibl_view_owned {
96 Some(view) => view,
97 None => &ctx.renderer.dummy_view,
98 };
99
100 Some(ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
101 label: Some("Opaque3d PBR Material Bind Group"),
102 layout: &ctx.renderer.pbr_material_bind_group_layout,
103 entries: &[
104 wgpu::BindGroupEntry {
105 binding: 0,
106 resource: wgpu::BindingResource::TextureView(&shadow_view),
107 },
108 wgpu::BindGroupEntry {
109 binding: 1,
110 resource: wgpu::BindingResource::Sampler(shadow_sampler),
111 },
112 wgpu::BindGroupEntry {
113 binding: 8,
114 resource: wgpu::BindingResource::TextureView(ibl_view),
115 },
116 wgpu::BindGroupEntry {
117 binding: 9,
118 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
119 },
120 wgpu::BindGroupEntry {
121 binding: 6,
122 resource: wgpu::BindingResource::TextureView(&ctx.renderer.dummy_view),
123 },
124 wgpu::BindGroupEntry {
125 binding: 7,
126 resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
127 },
128 ],
129 }))
130 }
131 None => {
132 tracing::warn!("Opaque3dNode: missing shadow map view, skipping shadow sampling");
133 return;
134 }
135 };
136
137 let bg = [0.02f32, 0.02, 0.05, 1.0];
139
140 let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
142 label: Some("Opaque3d Pass (PBR + Shadows)"),
143 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
144 view: &scene_view,
145 resolve_target: None,
146 ops: wgpu::Operations {
147 load: wgpu::LoadOp::Clear(wgpu::Color {
148 r: bg[0] as f64,
149 g: bg[1] as f64,
150 b: bg[2] as f64,
151 a: bg[3] as f64,
152 }),
153 store: wgpu::StoreOp::Store,
154 },
155 depth_slice: None,
156 })],
157 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
158 view: depth_view,
159 depth_ops: Some(wgpu::Operations {
160 load: wgpu::LoadOp::Clear(0.0),
161 store: wgpu::StoreOp::Store,
162 }),
163 stencil_ops: None,
164 }),
165 timestamp_writes: None,
166 occlusion_query_set: None,
167 multiview_mask: None,
168 });
169
170 pass.set_pipeline(&ctx.renderer.pbr_pipeline);
172 pass.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
173 if let Some(ref bg) = shadow_bind_group {
174 pass.set_bind_group(3, bg, &[]);
175 }
176
177 if let Some(ref inst_buffer) = ctx.renderer.instance_buffer_3d {
178 pass.set_vertex_buffer(1, inst_buffer.slice(..));
179 }
180
181 for mesh in self.mesh_instances.iter() {
186 pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
190 pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
192 let inst = mesh.instance_index;
193 pass.draw_indexed(0..mesh.index_count, 0, inst..(inst + 1));
194 }
195 }
196}