From 12d98c3ae4a17154a719eb0df38efc7577eb2131 Mon Sep 17 00:00:00 2001 From: GHowe <116420022+GageHowe@users.noreply.github.com> Date: Thu, 2 Apr 2026 11:25:34 -0500 Subject: [PATCH 1/4] fmt From c90dc4b77f4e47ef5e93bd4a0f4fb1c19b15b1d8 Mon Sep 17 00:00:00 2001 From: Gage Howe Date: Tue, 12 May 2026 09:54:58 -0500 Subject: [PATCH 2/4] Expose SSAO radius setting --- crates/bevy_pbr/src/ssao/mod.rs | 35 ++++++++++++++++++++++++------ crates/bevy_pbr/src/ssao/ssao.wgsl | 15 ++++++------- examples/3d/ssao.rs | 18 +++++++++++++++ 3 files changed, 53 insertions(+), 15 deletions(-) diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 65a200c70fdd2..0a47b8278104e 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -115,6 +115,10 @@ impl Plugin for ScreenSpaceAmbientOcclusionPlugin { pub struct ScreenSpaceAmbientOcclusion { /// Quality of the SSAO effect. pub quality_level: ScreenSpaceAmbientOcclusionQualityLevel, + /// Maximum sample distance, in view-space units. + /// + /// Use larger values for broader occlusion. This can increase haloing and noise. + pub radius: f32, /// A constant estimated thickness of objects. /// /// This value is used to decide how far behind an object a ray of light needs to be in order @@ -122,15 +126,24 @@ pub struct ScreenSpaceAmbientOcclusion { pub constant_object_thickness: f32, } +const DEFAULT_SSAO_RADIUS: f32 = 0.5 * 1.457; + impl Default for ScreenSpaceAmbientOcclusion { fn default() -> Self { Self { quality_level: ScreenSpaceAmbientOcclusionQualityLevel::default(), + radius: DEFAULT_SSAO_RADIUS, constant_object_thickness: 0.25, } } } +#[derive(Clone, Copy, ShaderType)] +struct SsaoSettingsUniform { + radius: f32, + constant_object_thickness: f32, +} + #[derive(Reflect, PartialEq, Eq, Hash, Clone, Copy, Default, Debug)] #[reflect(PartialEq, Hash, Clone, Default)] pub enum ScreenSpaceAmbientOcclusionQualityLevel { @@ -368,7 +381,7 @@ impl FromWorld for SsaoPipelines { texture_storage_2d(depth_format, StorageTextureAccess::WriteOnly), texture_storage_2d(TextureFormat::R32Uint, StorageTextureAccess::WriteOnly), uniform_buffer::(false), - uniform_buffer::(false), + uniform_buffer::(false), ), ), ); @@ -508,7 +521,7 @@ pub struct ScreenSpaceAmbientOcclusionResources { ssao_noisy_texture: CachedTexture, // Pre-spatially denoised texture pub screen_space_ambient_occlusion_texture: CachedTexture, // Spatially denoised texture depth_differences_texture: CachedTexture, - thickness_buffer: Buffer, + settings_buffer: Buffer, } fn prepare_ssao_textures( @@ -580,9 +593,17 @@ fn prepare_ssao_textures( }, ); - let thickness_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("thickness_buffer"), - contents: &ssao_settings.constant_object_thickness.to_le_bytes(), + let mut settings_uniform_buffer = encase::UniformBuffer::new(Vec::new()); + settings_uniform_buffer + .write(&SsaoSettingsUniform { + radius: ssao_settings.radius, + constant_object_thickness: ssao_settings.constant_object_thickness, + }) + .unwrap(); + + let settings_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + label: Some("ssao_settings_buffer"), + contents: settings_uniform_buffer.as_ref(), usage: BufferUsages::UNIFORM, }); @@ -593,7 +614,7 @@ fn prepare_ssao_textures( ssao_noisy_texture, screen_space_ambient_occlusion_texture: ssao_texture, depth_differences_texture, - thickness_buffer, + settings_buffer, }); } } @@ -698,7 +719,7 @@ fn prepare_ssao_bind_groups( &ssao_resources.ssao_noisy_texture.default_view, &ssao_resources.depth_differences_texture.default_view, globals_uniforms.clone(), - ssao_resources.thickness_buffer.as_entire_binding(), + ssao_resources.settings_buffer.as_entire_binding(), )), ); diff --git a/crates/bevy_pbr/src/ssao/ssao.wgsl b/crates/bevy_pbr/src/ssao/ssao.wgsl index aa715be052152..6912f9fae86a9 100644 --- a/crates/bevy_pbr/src/ssao/ssao.wgsl +++ b/crates/bevy_pbr/src/ssao/ssao.wgsl @@ -28,7 +28,11 @@ #endif @group(0) @binding(4) var depth_differences: texture_storage_2d; @group(0) @binding(5) var globals: Globals; -@group(0) @binding(6) var thickness: f32; +struct SsaoSettings { + radius: f32, + thickness: f32, +} +@group(0) @binding(6) var settings: SsaoSettings; @group(1) @binding(0) var point_clamp_sampler: sampler; @group(1) @binding(1) var linear_clamp_sampler: sampler; @group(1) @binding(2) var view: View; @@ -116,7 +120,7 @@ fn processSample( samples_per_slice: f32, bitmask: ptr, ) { - let delta_position_back_face = delta_position - view_vec * thickness; + let delta_position_back_face = delta_position - view_vec * settings.thickness; var front_back_horizon = vec2( fast_acos(dot(normalize(delta_position), view_vec)), @@ -134,11 +138,6 @@ fn processSample( fn ssao(@builtin(global_invocation_id) global_id: vec3) { let slice_count = f32(#SLICE_COUNT); let samples_per_slice_side = f32(#SAMPLES_PER_SLICE_SIDE); - let effect_radius = 0.5 * 1.457; - let falloff_range = 0.615 * effect_radius; - let falloff_from = effect_radius * (1.0 - 0.615); - let falloff_mul = -1.0 / falloff_range; - let falloff_add = falloff_from / falloff_range + 1.0; let pixel_coordinates = vec2(global_id.xy); let uv = (vec2(pixel_coordinates) + 0.5) / view.viewport.zw; @@ -151,7 +150,7 @@ fn ssao(@builtin(global_invocation_id) global_id: vec3) { let view_vec = normalize(-pixel_position); let noise = load_noise(pixel_coordinates); - let sample_scale = (-0.5 * effect_radius * view.clip_from_view[0][0]) / pixel_position.z; + let sample_scale = (-0.5 * settings.radius * view.clip_from_view[0][0]) / pixel_position.z; var visibility = 0.0; var occluded_sample_count = 0u; diff --git a/examples/3d/ssao.rs b/examples/3d/ssao.rs index 4aad9b530adda..3de273ed71889 100644 --- a/examples/3d/ssao.rs +++ b/examples/3d/ssao.rs @@ -151,6 +151,20 @@ fn update( ..current_ssao }, || keycode.just_pressed(KeyCode::ArrowDown), + ) + .insert_if( + ScreenSpaceAmbientOcclusion { + radius: (current_ssao.radius * 1.25).min(8.0), + ..current_ssao + }, + || keycode.just_pressed(KeyCode::ArrowRight), + ) + .insert_if( + ScreenSpaceAmbientOcclusion { + radius: (current_ssao.radius * 0.8).max(0.1), + ..current_ssao + }, + || keycode.just_pressed(KeyCode::ArrowLeft), ); if keycode.just_pressed(KeyCode::Digit1) { commands.remove::(); @@ -174,6 +188,10 @@ fn update( _ => unreachable!(), }; + if let Some(radius) = ssao.map(|s| s.radius) { + text.push_str(&format!("Radius: {radius} (Left/Right)\n")); + } + if let Some(thickness) = ssao.map(|s| s.constant_object_thickness) { text.push_str(&format!( "Constant object thickness: {thickness} (Up/Down)\n\n" From c67e829ad3e2d32ea3d51b5a196ea984d8ab8419 Mon Sep 17 00:00:00 2001 From: Gage Howe Date: Tue, 12 May 2026 11:52:00 -0500 Subject: [PATCH 3/4] Validate SSAO radius in shader --- crates/bevy_pbr/src/ssao/mod.rs | 3 +++ crates/bevy_pbr/src/ssao/ssao.wgsl | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 0a47b8278104e..10d541b976f6b 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -118,6 +118,9 @@ pub struct ScreenSpaceAmbientOcclusion { /// Maximum sample distance, in view-space units. /// /// Use larger values for broader occlusion. This can increase haloing and noise. + /// This value must be greater than `0.0`. + /// + /// To disable SSAO, remove [`ScreenSpaceAmbientOcclusion`] from the camera instead. pub radius: f32, /// A constant estimated thickness of objects. /// diff --git a/crates/bevy_pbr/src/ssao/ssao.wgsl b/crates/bevy_pbr/src/ssao/ssao.wgsl index 6912f9fae86a9..f26985922787a 100644 --- a/crates/bevy_pbr/src/ssao/ssao.wgsl +++ b/crates/bevy_pbr/src/ssao/ssao.wgsl @@ -150,7 +150,8 @@ fn ssao(@builtin(global_invocation_id) global_id: vec3) { let view_vec = normalize(-pixel_position); let noise = load_noise(pixel_coordinates); - let sample_scale = (-0.5 * settings.radius * view.clip_from_view[0][0]) / pixel_position.z; + let safe_radius = max(settings.radius, 0.0001); + let sample_scale = (-0.5 * safe_radius * view.clip_from_view[0][0]) / pixel_position.z; var visibility = 0.0; var occluded_sample_count = 0u; From 25e8984b8168f7bc3d8d1c7ff85037a6f8db90a2 Mon Sep 17 00:00:00 2001 From: Gage Howe Date: Tue, 12 May 2026 17:44:46 -0500 Subject: [PATCH 4/4] Document SSAO radius default --- crates/bevy_pbr/src/ssao/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_pbr/src/ssao/mod.rs b/crates/bevy_pbr/src/ssao/mod.rs index 10d541b976f6b..49031594f43ef 100644 --- a/crates/bevy_pbr/src/ssao/mod.rs +++ b/crates/bevy_pbr/src/ssao/mod.rs @@ -119,6 +119,7 @@ pub struct ScreenSpaceAmbientOcclusion { /// /// Use larger values for broader occlusion. This can increase haloing and noise. /// This value must be greater than `0.0`. + /// Defaults to `0.7285`. /// /// To disable SSAO, remove [`ScreenSpaceAmbientOcclusion`] from the camera instead. pub radius: f32,