From cddf9540dd6072e10ad2a3345c9c71e151b69a6e Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Wed, 25 Mar 2026 14:06:20 +0900 Subject: [PATCH] fix(renderer): fix RT shadow shader syntax, experimental features, and R32Float sampler compatibility - Add 'enable wgpu_ray_query' to RT shadow shader - Fix RayDesc struct constructor syntax and rayQueryGetCommittedIntersection API - Enable ExperimentalFeatures in GpuContext for RT - Change RT shadow texture binding to non-filterable (R32Float) - Use textureLoad instead of textureSample for RT shadow in lighting pass Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/deferred_lighting.wgsl | 4 ++- .../voltex_renderer/src/deferred_pipeline.rs | 8 ++--- crates/voltex_renderer/src/gpu.rs | 8 +++++ .../voltex_renderer/src/rt_shadow_shader.wgsl | 33 +++++++++---------- examples/deferred_demo/src/main.rs | 20 +++++------ 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/crates/voltex_renderer/src/deferred_lighting.wgsl b/crates/voltex_renderer/src/deferred_lighting.wgsl index 5ba4f44..905ea66 100644 --- a/crates/voltex_renderer/src/deferred_lighting.wgsl +++ b/crates/voltex_renderer/src/deferred_lighting.wgsl @@ -267,7 +267,9 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let F0 = mix(vec3(0.04, 0.04, 0.04), albedo, metallic); // Shadow: sample RT shadow texture (1.0 = lit, 0.0 = shadowed) - let shadow_factor = textureSample(t_rt_shadow, s_rt_shadow, uv).r; + let rt_dims = textureDimensions(t_rt_shadow); + let rt_coord = vec2(vec2(uv.x * f32(rt_dims.x), uv.y * f32(rt_dims.y))); + let shadow_factor = textureLoad(t_rt_shadow, rt_coord, 0).r; // Accumulate contribution from all active lights var Lo = vec3(0.0); diff --git a/crates/voltex_renderer/src/deferred_pipeline.rs b/crates/voltex_renderer/src/deferred_pipeline.rs index bac182a..d49cd80 100644 --- a/crates/voltex_renderer/src/deferred_pipeline.rs +++ b/crates/voltex_renderer/src/deferred_pipeline.rs @@ -274,22 +274,22 @@ pub fn lighting_shadow_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGro ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), count: None, }, - // binding 7: RT shadow texture (R32Float → filterable float) + // binding 7: RT shadow texture (R32Float → non-filterable) wgpu::BindGroupLayoutEntry { binding: 7, visibility: wgpu::ShaderStages::FRAGMENT, ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, + sample_type: wgpu::TextureSampleType::Float { filterable: false }, view_dimension: wgpu::TextureViewDimension::D2, multisampled: false, }, count: None, }, - // binding 8: filtering sampler for RT shadow texture + // binding 8: non-filtering sampler for RT shadow texture wgpu::BindGroupLayoutEntry { binding: 8, visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), count: None, }, ], diff --git a/crates/voltex_renderer/src/gpu.rs b/crates/voltex_renderer/src/gpu.rs index 003a6f1..46b7259 100644 --- a/crates/voltex_renderer/src/gpu.rs +++ b/crates/voltex_renderer/src/gpu.rs @@ -63,12 +63,20 @@ impl GpuContext { adapter.limits() }; + let experimental = if extra_features.is_empty() { + wgpu::ExperimentalFeatures::disabled() + } else { + // Safety: we acknowledge experimental features may have bugs + unsafe { wgpu::ExperimentalFeatures::enabled() } + }; + let (device, queue) = adapter .request_device(&wgpu::DeviceDescriptor { label: Some("Voltex Device"), required_features: extra_features, required_limits, memory_hints: Default::default(), + experimental_features: experimental, ..Default::default() }) .await diff --git a/crates/voltex_renderer/src/rt_shadow_shader.wgsl b/crates/voltex_renderer/src/rt_shadow_shader.wgsl index 15fe1a0..824f1d0 100644 --- a/crates/voltex_renderer/src/rt_shadow_shader.wgsl +++ b/crates/voltex_renderer/src/rt_shadow_shader.wgsl @@ -1,3 +1,5 @@ +enable wgpu_ray_query; + // RT Shadow compute shader. // Reads world-space position and normal from the G-Buffer, then fires a // shadow ray against the TLAS to determine per-pixel visibility. @@ -51,27 +53,24 @@ fn cs_main(@builtin(global_invocation_id) gid: vec3) { let ray_origin = world_pos + N * 0.01; let ray_dir = normalize(-uniforms.light_direction); - // Ray query: check for any occluder between surface and "infinity" - var rq: ray_query; - rayQueryInitialize( - &rq, - tlas, - RayDesc( - RAY_FLAG_TERMINATE_ON_FIRST_HIT | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, - 0xFF, - 0.0001, - 1.0e6, - ray_origin, - ray_dir, - ), - ); + // Build ray descriptor + var desc: RayDesc; + desc.flags = RAY_FLAG_TERMINATE_ON_FIRST_HIT; + desc.cull_mask = 0xFFu; + desc.tmin = 0.001; + desc.tmax = 1000.0; + desc.origin = ray_origin; + desc.dir = ray_dir; - // Advance until the query is done (with TERMINATE_ON_FIRST_HIT this is at most one step) + // Ray query + var rq: ray_query; + rayQueryInitialize(&rq, tlas, desc); while rayQueryProceed(&rq) {} - // If anything was hit, the pixel is in shadow + // Check result + let intersection = rayQueryGetCommittedIntersection(&rq); var shadow: f32 = 1.0; - if rayQueryGetCommittedIntersectionType(&rq) != RAY_QUERY_INTERSECTION_NONE { + if intersection.kind != RAY_QUERY_INTERSECTION_NONE { shadow = 0.0; } diff --git a/examples/deferred_demo/src/main.rs b/examples/deferred_demo/src/main.rs index 5e452e7..782a5bd 100644 --- a/examples/deferred_demo/src/main.rs +++ b/examples/deferred_demo/src/main.rs @@ -427,13 +427,13 @@ impl ApplicationHandler for DeferredDemoApp { ..Default::default() }); - let rt_shadow_filtering_sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("RT Shadow Filtering Sampler"), + let rt_shadow_nearest_sampler = gpu.device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("RT Shadow Nearest Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); @@ -447,7 +447,7 @@ impl ApplicationHandler for DeferredDemoApp { &ssgi, &ssgi_filtering_sampler, &rt_shadow, - &rt_shadow_filtering_sampler, + &rt_shadow_nearest_sampler, ); // Lighting pipeline — now renders to HDR format instead of surface @@ -696,13 +696,13 @@ impl ApplicationHandler for DeferredDemoApp { mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); - let rt_shadow_filtering_sampler = state.gpu.device.create_sampler(&wgpu::SamplerDescriptor { - label: Some("RT Shadow Filtering Sampler"), + let rt_shadow_nearest_sampler = state.gpu.device.create_sampler(&wgpu::SamplerDescriptor { + label: Some("RT Shadow Nearest Sampler"), address_mode_u: wgpu::AddressMode::ClampToEdge, address_mode_v: wgpu::AddressMode::ClampToEdge, address_mode_w: wgpu::AddressMode::ClampToEdge, - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, + mag_filter: wgpu::FilterMode::Nearest, + min_filter: wgpu::FilterMode::Nearest, mipmap_filter: wgpu::MipmapFilterMode::Nearest, ..Default::default() }); @@ -715,7 +715,7 @@ impl ApplicationHandler for DeferredDemoApp { &state.ssgi, &ssgi_filtering_sampler, &state.rt_shadow, - &rt_shadow_filtering_sampler, + &rt_shadow_nearest_sampler, ); // Resize HDR target