diff --git a/crates/bevy_pbr/src/ssr/mod.rs b/crates/bevy_pbr/src/ssr/mod.rs index 40b84f3d733f7..7bd9d02c529de 100644 --- a/crates/bevy_pbr/src/ssr/mod.rs +++ b/crates/bevy_pbr/src/ssr/mod.rs @@ -552,6 +552,9 @@ impl SpecializedRenderPipeline for ScreenSpaceReflectionsPipeline { shader_defs.push("ATMOSPHERE".into()); } + #[cfg(not(target_arch = "wasm32"))] + shader_defs.push("USE_DEPTH_SAMPLERS".into()); + RenderPipelineDescriptor { label: Some("SSR pipeline".into()), layout, diff --git a/crates/bevy_pbr/src/ssr/raymarch.wgsl b/crates/bevy_pbr/src/ssr/raymarch.wgsl index 12140c91e3170..20a76afa03202 100644 --- a/crates/bevy_pbr/src/ssr/raymarch.wgsl +++ b/crates/bevy_pbr/src/ssr/raymarch.wgsl @@ -24,11 +24,60 @@ position_world_to_ndc, } +#ifdef USE_DEPTH_SAMPLERS // Allows us to sample from the depth buffer with bilinear filtering. @group(2) @binding(2) var depth_linear_sampler: sampler; // Allows us to sample from the depth buffer with nearest-neighbor filtering. @group(2) @binding(3) var depth_nearest_sampler: sampler; +#endif + +// Manual depth fetch helpers used on WebGPU where depth + filtering sampler is invalid. +#ifndef USE_DEPTH_SAMPLERS +fn depth_texel_clamped(texel: vec2) -> f32 { + let dims = textureDimensions(depth_prepass_texture); + let max_coord = vec2(i32(dims.x) - 1, i32(dims.y) - 1); + let clamped = clamp(texel, vec2(0), max_coord); + return textureLoad(depth_prepass_texture, clamped, 0); +} + +fn depth_sample_nearest_clamped(uv: vec2, tex_size: vec2) -> f32 { + // Match nearest sampling by snapping to the closest texel center. + let coord = uv * tex_size - vec2(0.5); + return depth_texel_clamped(vec2(floor(coord + vec2(0.5)))); +} + +fn depth_sample_bilinear_clamped(uv: vec2, tex_size: vec2) -> f32 { + let coord = uv * tex_size - vec2(0.5); + let base = vec2(floor(coord)); + let frac = coord - floor(coord); + + let d00 = depth_texel_clamped(base); + let d10 = depth_texel_clamped(base + vec2(1, 0)); + let d01 = depth_texel_clamped(base + vec2(0, 1)); + let d11 = depth_texel_clamped(base + vec2(1, 1)); + + let d0 = mix(d00, d10, frac.x); + let d1 = mix(d01, d11, frac.x); + return mix(d0, d1, frac.y); +} +#endif + +fn depth_sample_linear(uv: vec2, tex_size: vec2) -> f32 { +#ifdef USE_DEPTH_SAMPLERS + return textureSampleLevel(depth_prepass_texture, depth_linear_sampler, uv, 0u); +#else + return depth_sample_bilinear_clamped(uv, tex_size); +#endif +} + +fn depth_sample_nearest(uv: vec2, tex_size: vec2) -> f32 { +#ifdef USE_DEPTH_SAMPLERS + return textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, uv, 0u); +#else + return depth_sample_nearest_clamped(uv, tex_size); +#endif +} // Main code @@ -241,10 +290,8 @@ fn depth_raymarch_distance_fn_evaluate( // * The false occlusions due to duplo land are rejected because the ray stays above the smooth surface. // * The shrink-wrap surface is no longer continuous, so it's possible for rays to miss it. - let linear_depth = - 1.0 / textureSampleLevel(depth_prepass_texture, depth_linear_sampler, interp_uv, 0u); - let unfiltered_depth = - 1.0 / textureSampleLevel(depth_prepass_texture, depth_nearest_sampler, interp_uv, 0u); + let linear_depth = 1.0 / depth_sample_linear(interp_uv, (*distance_fn).depth_tex_size); + let unfiltered_depth = 1.0 / depth_sample_nearest(interp_uv, (*distance_fn).depth_tex_size); var max_depth: f32; var min_depth: f32;