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) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 14:06:20 +09:00
parent b8334ea361
commit cddf9540dd
5 changed files with 41 additions and 32 deletions

View File

@@ -267,7 +267,9 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let F0 = mix(vec3<f32>(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<i32>(vec2<f32>(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<f32>(0.0);

View File

@@ -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,
},
],

View File

@@ -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

View File

@@ -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<u32>) {
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;
}

View File

@@ -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