Skip to main content

cvkg_render_gpu/passes/
transparent.rs

1//! Transparent pass KvasirNode — renders transparent 3D meshes with proper
2//! back-to-front sorting using view_depth.
3
4use crate::kvasir::nodes::PassId;
5use crate::kvasir::{ExecutionContext, KvasirNode, ResourceId};
6use crate::passes::shadow::GpuMesh3d;
7
8/// Transparent 3D render pass node — renders transparent meshes with alpha blending.
9/// Uses view_depth for proper back-to-front sorting.
10pub struct TransparentNode {
11    /// GPU-ready mesh instances to render (sorted by view_depth descending).
12    pub mesh_instances: Vec<GpuMesh3d>,
13    /// Shadow map resource for darkening transparent surfaces.
14    pub shadow_map: ResourceId,
15    /// Camera position for view_depth calculation.
16    pub camera_pos: glam::Vec3,
17}
18
19impl KvasirNode for TransparentNode {
20    fn label(&self) -> &'static str {
21        "Transparent3d"
22    }
23
24    fn inputs(&self) -> &[ResourceId] {
25        std::slice::from_ref(&self.shadow_map)
26    }
27
28    fn outputs(&self) -> &[ResourceId] {
29        &[]
30    }
31
32    fn pass_id(&self) -> PassId {
33        PassId::Transparent3d
34    }
35
36    fn execute(&self, ctx: &mut ExecutionContext) {
37        tracing::debug!(
38            "TransparentNode::execute — instances={}, shadow_map={:?}",
39            self.mesh_instances.len(),
40            self.shadow_map
41        );
42
43        if self.mesh_instances.is_empty() {
44            return;
45        }
46
47        // Use the main scene render target (RES_SCENE) for color output.
48        let scene_view = match ctx
49            .registry
50            .get_texture_view(crate::kvasir::nodes::RES_SCENE)
51        {
52            Some(v) => v,
53            None => {
54                tracing::error!("TransparentNode: missing scene color target");
55                return;
56            }
57        };
58        let depth_view = ctx.depth_view;
59
60        // Get shadow resources and create bind group 3
61        let shadow_bind_group = match ctx.registry.get_texture_view(self.shadow_map) {
62            Some(shadow_view) => {
63                let shadow_sampler = match ctx.renderer.shadow_sampler.as_ref() {
64                    Some(s) => s,
65                    None => {
66                        tracing::warn!("TransparentNode: missing shadow sampler");
67                        return;
68                    }
69                };
70
71                let ibl_view_owned = ctx
72                    .registry
73                    .get_texture_view(crate::kvasir::nodes::RES_BLUR_A);
74                let ibl_view = match &ibl_view_owned {
75                    Some(view) => view,
76                    None => &ctx.renderer.dummy_view,
77                };
78
79                Some(ctx.device.create_bind_group(&wgpu::BindGroupDescriptor {
80                    label: Some("TransparentNode PBR Material Bind Group"),
81                    layout: &ctx.renderer.pbr_material_bind_group_layout,
82                    entries: &[
83                        wgpu::BindGroupEntry {
84                            binding: 0,
85                            resource: wgpu::BindingResource::TextureView(&shadow_view),
86                        },
87                        wgpu::BindGroupEntry {
88                            binding: 1,
89                            resource: wgpu::BindingResource::Sampler(shadow_sampler),
90                        },
91                        wgpu::BindGroupEntry {
92                            binding: 8,
93                            resource: wgpu::BindingResource::TextureView(ibl_view),
94                        },
95                        wgpu::BindGroupEntry {
96                            binding: 9,
97                            resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
98                        },
99                        wgpu::BindGroupEntry {
100                            binding: 6,
101                            resource: wgpu::BindingResource::TextureView(&ctx.renderer.dummy_view),
102                        },
103                        wgpu::BindGroupEntry {
104                            binding: 7,
105                            resource: wgpu::BindingResource::Sampler(&ctx.renderer.sampler),
106                        },
107                    ],
108                }))
109            }
110            None => None,
111        };
112
113        // Set up transparent render pass with alpha blending.
114        let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
115            label: Some("Transparent3d Pass (PBR + Shadows)"),
116            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
117                view: &scene_view,
118                resolve_target: None,
119                ops: wgpu::Operations {
120                    load: wgpu::LoadOp::Load, // Load existing (opaque) content
121                    store: wgpu::StoreOp::Store,
122                },
123                depth_slice: None,
124            })],
125            depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
126                view: depth_view,
127                depth_ops: Some(wgpu::Operations {
128                    load: wgpu::LoadOp::Load, // Load existing depth
129                    store: wgpu::StoreOp::Store,
130                }),
131                stencil_ops: None,
132            }),
133            timestamp_writes: None,
134            occlusion_query_set: None,
135            multiview_mask: None,
136        });
137
138        // Bind the transparent PBR pipeline and required bind groups.
139        pass.set_pipeline(&ctx.renderer.transparent_pipeline);
140        pass.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
141        if let Some(ref bg) = shadow_bind_group {
142            pass.set_bind_group(3, bg, &[]);
143        }
144
145        if let Some(ref inst_buffer) = ctx.renderer.instance_buffer_3d {
146            pass.set_vertex_buffer(1, inst_buffer.slice(..));
147        }
148
149        // Render meshes sorted by view_depth (back-to-front for transparency).
150        for mesh in self.mesh_instances.iter() {
151            pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
152            pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
153            let inst = mesh.instance_index;
154            pass.draw_indexed(0..mesh.index_count, 0, inst..(inst + 1));
155        }
156    }
157}