From 31ee691fbf4f04990cf25c92603113000462a209 Mon Sep 17 00:00:00 2001 From: jon1solution <143551917+jon1solution@users.noreply.github.com> Date: Fri, 8 Aug 2025 14:37:39 -0700 Subject: [PATCH] Implemented a very simple SSAO in GLES3. --- drivers/gles3/effects/post_effects.cpp | 37 ++++++++++- drivers/gles3/effects/post_effects.h | 5 +- drivers/gles3/rasterizer_scene_gles3.cpp | 45 ++++++++++++- drivers/gles3/shaders/effects/post.glsl | 33 ++++++++++ drivers/gles3/shaders/s4ao_inc.glsl | 63 +++++++++++++++++++ drivers/gles3/shaders/s4ao_mega_inc.glsl | 50 +++++++++++++++ drivers/gles3/shaders/s4ao_micro_inc.glsl | 32 ++++++++++ scene/resources/environment.cpp | 9 +++ .../rendering/storage/environment_storage.cpp | 4 +- 9 files changed, 271 insertions(+), 7 deletions(-) create mode 100644 drivers/gles3/shaders/s4ao_inc.glsl create mode 100644 drivers/gles3/shaders/s4ao_mega_inc.glsl create mode 100644 drivers/gles3/shaders/s4ao_micro_inc.glsl diff --git a/drivers/gles3/effects/post_effects.cpp b/drivers/gles3/effects/post_effects.cpp index a7191385b4e..a9f51fc3a87 100644 --- a/drivers/gles3/effects/post_effects.cpp +++ b/drivers/gles3/effects/post_effects.cpp @@ -87,7 +87,11 @@ void PostEffects::_draw_screen_triangle() { glBindVertexArray(0); } -void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, float p_srgb_white, uint32_t p_view, bool p_use_multiview, uint64_t p_spec_constants) { +void PostEffects::post_copy( + GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, + GLuint p_source_depth, bool p_ssao_enabled, int p_ssao_quality_level, float p_ssao_strength, float p_ssao_radius, + Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, + float p_srgb_white, uint32_t p_view, bool p_use_multiview, uint64_t p_spec_constants) { glDisable(GL_DEPTH_TEST); glDepthMask(GL_FALSE); glDisable(GL_BLEND); @@ -103,6 +107,19 @@ void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuin if (p_glow_buffers != nullptr) { flags |= PostShaderGLES3::USE_GLOW; } + if (p_ssao_enabled) { + if (p_ssao_quality_level == RS::ENV_SSAO_QUALITY_VERY_LOW) { + flags |= PostShaderGLES3::USE_SSAO_ABYSS; + } else if (p_ssao_quality_level == RS::ENV_SSAO_QUALITY_LOW) { + flags |= PostShaderGLES3::USE_SSAO_LOW; + } else if (p_ssao_quality_level == RS::ENV_SSAO_QUALITY_HIGH) { + flags |= PostShaderGLES3::USE_SSAO_HIGH; + } else if (p_ssao_quality_level == RS::ENV_SSAO_QUALITY_ULTRA) { + flags |= PostShaderGLES3::USE_SSAO_MEGA; + } else { + flags |= PostShaderGLES3::USE_SSAO_MED; + } + } if (p_luminance_multiplier != 1.0) { flags |= PostShaderGLES3::USE_LUMINANCE_MULTIPLIER; } @@ -118,6 +135,20 @@ void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuin glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + if (p_ssao_enabled) { + glActiveTexture(GL_TEXTURE3); + glBindTexture(texture_target, p_source_depth); + glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Thanks to mrjustaguy! + glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + post.shader.version_set_uniform(PostShaderGLES3::SSAO_INTENSITY, p_ssao_strength, post.shader_version, mode, flags); + post.shader.version_set_uniform(PostShaderGLES3::SSAO_RADIUS_FRAC, p_ssao_radius, post.shader_version, mode, flags); + post.shader.version_set_uniform(PostShaderGLES3::SSAO_PRN_UV, // This converts the UV coordinate into a pseudo-random number. + p_source_size.x * 1.087f * ((1.0f + sqrt(5.0f)) / 2.0f), + p_source_size.y * 1.087f * ((9.0f + sqrt(221.0f)) / 10.0f), + post.shader_version, mode, flags); + } + if (p_glow_buffers != nullptr) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, p_glow_buffers[0].color); @@ -137,6 +168,10 @@ void PostEffects::post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuin glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); } + if (p_ssao_enabled) { + glActiveTexture(GL_TEXTURE3); + glBindTexture(texture_target, 0); + } // Return back to nearest glActiveTexture(GL_TEXTURE0); diff --git a/drivers/gles3/effects/post_effects.h b/drivers/gles3/effects/post_effects.h index 8fbf8b3d685..ae686e249da 100644 --- a/drivers/gles3/effects/post_effects.h +++ b/drivers/gles3/effects/post_effects.h @@ -58,7 +58,10 @@ public: PostEffects(); ~PostEffects(); - void post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, float p_srgb_white, uint32_t p_view = 0, bool p_use_multiview = false, uint64_t p_spec_constants = 0); + void post_copy(GLuint p_dest_framebuffer, Size2i p_dest_size, GLuint p_source_color, + GLuint p_source_depth, bool p_ssao_enabled, int p_ssao_quality_level, float p_ssao_strength, float p_ssao_radius, + Size2i p_source_size, float p_luminance_multiplier, const Glow::GLOWLEVEL *p_glow_buffers, float p_glow_intensity, + float p_srgb_white, uint32_t p_view = 0, bool p_use_multiview = false, uint64_t p_spec_constants = 0); }; } //namespace GLES3 diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp index 293647dc9aa..f8cf3773b7a 100644 --- a/drivers/gles3/rasterizer_scene_gles3.cpp +++ b/drivers/gles3/rasterizer_scene_gles3.cpp @@ -1114,6 +1114,7 @@ void RasterizerSceneGLES3::environment_set_ssr_roughness_quality(RS::Environment } void RasterizerSceneGLES3::environment_set_ssao_quality(RS::EnvironmentSSAOQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) { + ssao_quality = p_quality; } void RasterizerSceneGLES3::environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) { @@ -2275,6 +2276,15 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ } } + bool ssao_enabled = false; + if (p_environment.is_valid()) { + ssao_enabled = environment_get_ssao_enabled(p_environment); + if (ssao_enabled) { + // If SSAO is enabled, we apply tonemapping etc. in post, so disable it during rendering + apply_color_adjustments_in_post = true; + } + } + // Assign render data // Use the format from rendererRD RenderDataGLES3 render_data; @@ -2552,6 +2562,11 @@ void RasterizerSceneGLES3::render_scene(const Ref &p_render_ glBindFramebuffer(GL_FRAMEBUFFER, fbo); glViewport(0, 0, rb->internal_size.x, rb->internal_size.y); + // If SSAO is enabled, we definitely need the depth buffer. + if (ssao_enabled) { + scene_state.used_depth_texture = true; + } + // Do depth prepass if it's explicitly enabled bool use_depth_prepass = config->use_depth_prepass; @@ -2835,6 +2850,18 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend rb->check_glow_buffers(); } + // Check if we want and can have SSAO. + bool ssao_enabled = false; + float ssao_strength = 4.0; + float ssao_radius = 0.5; + if (p_render_data->environment.is_valid()) { + ssao_enabled = environment_get_ssao_enabled(p_render_data->environment); + // This SSAO is not implemented the same way, but uses the intensity and radius + // in a similar way. The parameters are scaled so the SSAO defaults look ok. + ssao_strength = environment_get_ssao_intensity(p_render_data->environment) * 2.0; + ssao_radius = environment_get_ssao_radius(p_render_data->environment) * 0.5; + } + uint64_t bcs_spec_constants = 0; if (p_render_data->environment.is_valid()) { bool use_bcs = environment_get_adjustments_enabled(p_render_data->environment); @@ -2882,6 +2909,10 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend if (fbo_int != 0) { // Apply glow/bloom if requested? then populate our glow buffers GLuint color = fbo_int != 0 ? rb->get_internal_color() : texture_storage->render_target_get_color(render_target); + + // We need to pass this in for SSAO. + GLuint depth_buffer = fbo_int != 0 ? rb->get_internal_depth() : texture_storage->render_target_get_depth(render_target); + const GLES3::Glow::GLOWLEVEL *glow_buffers = nullptr; if (glow_enabled) { glow_buffers = rb->get_glow_buffers(); @@ -2898,7 +2929,10 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend } // Copy color buffer - post_effects->post_copy(fbo_rt, target_size, color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, srgb_white, 0, false, bcs_spec_constants); + post_effects->post_copy(fbo_rt, target_size, color, + depth_buffer, ssao_enabled, ssao_quality, ssao_strength, ssao_radius, + internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, + srgb_white, 0, false, bcs_spec_constants); // Copy depth buffer glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_int); @@ -2945,6 +2979,9 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend const GLES3::Glow::GLOWLEVEL *glow_buffers = nullptr; GLuint source_color = fbo_int != 0 ? rb->get_internal_color() : texture_storage->render_target_get_color(render_target); + // Moved this up so SSAO could use it too. + GLuint read_depth = rb->get_internal_depth(); + if (glow_enabled) { glow_buffers = rb->get_glow_buffers(); @@ -2966,11 +3003,13 @@ void RasterizerSceneGLES3::_render_post_processing(const RenderDataGLES3 *p_rend glBindFramebuffer(GL_FRAMEBUFFER, fbos[2]); glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, write_color, 0, v); - post_effects->post_copy(fbos[2], target_size, source_color, internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, srgb_white, v, true, bcs_spec_constants); + post_effects->post_copy(fbos[2], target_size, source_color, + read_depth, ssao_enabled, ssao_quality, ssao_strength, ssao_radius, + internal_size, p_render_data->luminance_multiplier, glow_buffers, glow_intensity, + srgb_white, v, true, bcs_spec_constants); } // Copy depth - GLuint read_depth = rb->get_internal_depth(); GLuint write_depth = texture_storage->render_target_get_depth(render_target); glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]); diff --git a/drivers/gles3/shaders/effects/post.glsl b/drivers/gles3/shaders/effects/post.glsl index fd79d1a6141..5b02e2c6393 100644 --- a/drivers/gles3/shaders/effects/post.glsl +++ b/drivers/gles3/shaders/effects/post.glsl @@ -10,6 +10,11 @@ USE_LUMINANCE_MULTIPLIER = false USE_BCS = false USE_COLOR_CORRECTION = false USE_1D_LUT = false +USE_SSAO_ABYSS = false +USE_SSAO_LOW = false +USE_SSAO_MED = false +USE_SSAO_HIGH = false +USE_SSAO_MEGA = false #[vertex] layout(location = 0) in vec2 vertex_attrib; @@ -86,6 +91,29 @@ vec3 apply_color_correction(vec3 color) { #endif // USE_1D_LUT #endif // USE_COLOR_CORRECTION +#if defined(USE_SSAO_ABYSS) || defined(USE_SSAO_LOW) || defined(USE_SSAO_MED) || defined(USE_SSAO_HIGH) || defined(USE_SSAO_MEGA) +#define USE_SOME_SSAO +uniform float ssao_intensity; +uniform float ssao_radius_frac; +uniform vec2 ssao_prn_UV; +#ifdef USE_MULTIVIEW +// VR will have 2 depth buffers. +uniform sampler2DArray depth_buffer_array; // texunit:3 +#else +uniform sampler2D depth_buffer; // texunit:3 +#endif +#if defined(USE_SSAO_ABYSS) +// Use the tiny 2-sample version. +#include "../s4ao_micro_inc.glsl" +#elif defined(USE_SSAO_HIGH) || defined(USE_SSAO_MEGA) +// Use the rings version for the higher qualities. +#include "../s4ao_mega_inc.glsl" +#else +// Use the more generic NxN grid version. +#include "../s4ao_inc.glsl" +#endif +#endif + in vec2 uv_interp; layout(location = 0) out vec4 frag_color; @@ -131,6 +159,11 @@ void main() { color.rgb = srgb_to_linear(color.rgb); +#if defined(USE_SOME_SSAO) + // Putting SSAO after the conversion to linear color, though it might be better before the glow. + color.rgb *= s4ao(uv_interp); // The USE_SSAO_X controls the number of samples. +#endif + color.rgb = apply_tonemapping(color.rgb, white); #ifdef USE_BCS diff --git a/drivers/gles3/shaders/s4ao_inc.glsl b/drivers/gles3/shaders/s4ao_inc.glsl new file mode 100644 index 00000000000..0aeab8d89f0 --- /dev/null +++ b/drivers/gles3/shaders/s4ao_inc.glsl @@ -0,0 +1,63 @@ +// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S) + +// The sample_width should be even, else the midpoint is at UV. +// Takes sample_width^2 samples in a grid, with the corners notched. +#if defined(USE_SSAO_LOW) +const int sample_width = 2; +#elif defined(USE_SSAO_HIGH) +const int sample_width = 6; +#else +const int sample_width = 4; +#endif +const int notch_01 = int(sample_width > 3); // Set to 1 to skip the corner samples, 0 to include them. +const float sample_mid = (float(sample_width) - 1.0) * 0.50001; // Can't be exactly 0.5 in case sample_width is odd. +#if defined(USE_SSAO_LOW) +const float inv_half_width = 1.0 / sample_mid; // The 2x2 sampling looks wider as all samples are at radius. +#else +const float inv_half_width = 1.7 / sample_mid; // Bake in the 1.7 scale for the random rotation. +#endif +const float average_samples = 1.0 / float(sample_width * sample_width - 4 * notch_01); // 1 / number_of_samples +const float ssao_falloff_frac = 0.25; +// Perform the SSAO. +float s4ao(vec2 UV) { +#ifdef USE_MULTIVIEW + float depth = texture(depth_buffer_array, vec3(UV, view)).r; +#else + float depth = texture(depth_buffer, UV).r; +#endif + float radius = max(1e-4f, depth * ssao_radius_frac); + float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac); + // Random 2D rotation per pixel (+/-45 deg, with 0 having a lower probability). + // The random cosine vector is vec2( 0.5, -0.5 to +0.5 ) and *1.7 makes the average length ~ 1. + vec2 rcos = (inv_half_width * radius) * vec2(0.5f, fract(dot(UV, ssao_prn_UV)) - 0.5f); + vec2 rsin = rcos.yx * vec2(-1, 1); // Perpendicular to the random cosine vector. + // Grab the samples and determine the occlusion. + float occlusion = 0.0f; + vec2 base_duv = -sample_mid * rsin; + for (int j = sample_width; --j >= 0;) { +#if defined(USE_SSAO_LOW) + // Low quality uses 2x2 samples, no notching. + vec2 duv = -sample_mid * rcos + base_duv; + for (int i = sample_width; --i >= 0;) { +#else + // Will uses 4x4 or 6x6 samples, with the corners notched out. + int o = /*notch_01 &*/ int((j <= 0) || (j >= (sample_width - 1))); // Notch corners of the grid. + vec2 duv = (float(o) - sample_mid) * rcos + base_duv; + for (int i = sample_width - o - o; --i >= 0;) { +#endif +#ifdef USE_MULTIVIEW + float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth; +#else + float dz = texture(depth_buffer, UV + duv).r - depth; +#endif + float validity = smoothstep(1.0f, 0.0f, dz * inv_falloff); + occlusion += normalize(vec3(duv, dz)).z * validity; // How 'directly overhead' is it? + duv += rcos; // March along the rcos direction with i. + } + base_duv += rsin; // March along the rsin direction with j. + } + // Adjust the occlusion for intensity, and # samples. + occlusion *= ssao_intensity * average_samples; + occlusion = clamp(1.0f - occlusion, 0.0f, 1.0f); + return occlusion * occlusion; +} diff --git a/drivers/gles3/shaders/s4ao_mega_inc.glsl b/drivers/gles3/shaders/s4ao_mega_inc.glsl new file mode 100644 index 00000000000..c9ae16ebbdd --- /dev/null +++ b/drivers/gles3/shaders/s4ao_mega_inc.glsl @@ -0,0 +1,50 @@ +// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S) +// The mega version uses N concentric rings of samples. + +#if defined(USE_SSAO_MEGA) +const int rings = 4; // Start with the outer ring. +const int samps[] = int[](24, 18, 12, 6); // ( 9, 6, 3 ) is a minimum, but I want better. +#else +const int rings = 3; // Start with the outer ring. +const int samps[] = int[](15, 10, 5, 1); // ( 9, 6, 3 ) is a minimum, but I want better. +#endif +const float average_samples = 1.0 / float(samps[0] + samps[1] * int(rings > 1) + samps[2] * int(rings > 2) + samps[3] * int(rings > 3)); +const float ssao_falloff_frac = 0.25; +// Perform the SSAO. +float s4ao(vec2 UV) { +#ifdef USE_MULTIVIEW + float depth = texture(depth_buffer_array, vec3(UV, view)).r; +#else + float depth = texture(depth_buffer, UV).r; +#endif + float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac); + // Random 2D rotation per pixel (0..1 -> parabola approximating a 180 deg arc) + float r01 = fract(dot(UV, ssao_prn_UV)); + vec2 rcos = vec2(r01 - 0.5f, 2.0f * (r01 - r01 * r01)) * (2.0f * depth * ssao_radius_frac); // 180 degrees. + vec2 rsin = rcos.yx * vec2(-1, 1); // Perpendicular to the random cosine vector. + // Grab the samples and determine the occlusion. + float occlusion = 0.0f; + float ring_shrink = 0.75f; // Shrink every ring. + for (int r = 0; r < rings; ++r) { + float dt = (6.283185307f) / float(samps[r]); + float t = float(r & 1) * 0.5f * dt; + for (int s = 0; s < samps[r]; ++s) { + vec2 duv = cos(t) * rcos + sin(t) * rsin; +#ifdef USE_MULTIVIEW + float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth; +#else + float dz = texture(depth_buffer, UV + duv).r - depth; +#endif + // How 'directly overhead' is it? Factor in the falloff depth. + occlusion += normalize(vec3(duv, dz)).z * smoothstep(1.0f, 0.0f, dz * inv_falloff); + t += dt; + } + // The next ring will be smaller. + rcos *= ring_shrink; + rsin *= ring_shrink; + } + // Adjust the occlusion for intensity, and # samples. + occlusion *= ssao_intensity * average_samples; + occlusion = 1.0f - clamp(occlusion, 0.0f, 1.0f); + return occlusion * occlusion; +} diff --git a/drivers/gles3/shaders/s4ao_micro_inc.glsl b/drivers/gles3/shaders/s4ao_micro_inc.glsl new file mode 100644 index 00000000000..f188159f53b --- /dev/null +++ b/drivers/gles3/shaders/s4ao_micro_inc.glsl @@ -0,0 +1,32 @@ +// S4AO (Stupid Simple Screen Space Ambient Occlusion) - Jonathan Dummer (O1S) +// This micro version uses only 3 depth samples, the midpoint and a randomly-rotated, balanced pair. + +const mediump float ssao_falloff_frac = 0.25; +// Perform the SSAO. +float s4ao(vec2 UV) { +#ifdef USE_MULTIVIEW + mediump float depth = texture(depth_buffer_array, vec3(UV, view)).r; +#else + mediump float depth = texture(depth_buffer, UV).r; +#endif + mediump float inv_falloff = 1.0f / max(1e-4f, depth * ssao_falloff_frac); + // Random 2D rotation per pixel (0..1 -> parabola approximating a 180 deg arc) + mediump float r01 = fract(dot(UV, ssao_prn_UV)); + mediump vec2 duv = vec2(r01 - 0.5f, 2.0f * (r01 - r01 * r01)) * (2.0f * depth * ssao_radius_frac); // 180 degrees. + // Grab the samples and determine the occlusion. + mediump float occlusion = 0.0f; + for (int s = 0; s < 2; ++s) { +#ifdef USE_MULTIVIEW + mediump float dz = texture(depth_buffer_array, vec3(UV + duv, view)).r - depth; +#else + mediump float dz = texture(depth_buffer, UV + duv).r - depth; +#endif + // How 'directly overhead' is it? Factor in the falloff depth. + occlusion += normalize(vec3(duv, dz)).z * mix(1.0f, 0.0f, dz * inv_falloff); + // Mirror the next sample. + duv = -duv; + } + // Adjust the occlusion for intensity, and # samples. + occlusion = 1.0f - clamp(occlusion * 0.5f * ssao_intensity, 0.0f, 1.0f); + return occlusion * occlusion; +} diff --git a/scene/resources/environment.cpp b/scene/resources/environment.cpp index 32dc79aa4e5..a726b11a6f5 100644 --- a/scene/resources/environment.cpp +++ b/scene/resources/environment.cpp @@ -1135,6 +1135,15 @@ void Environment::_validate_property(PropertyInfo &p_property) const { } } + if (OS::get_singleton()->get_current_rendering_method() != "forward_plus") { + // Hide SSAO properties that only work in Forward+. + if (p_property.name.begins_with("ssao_")) { + if ((p_property.name != "ssao_enabled") && (p_property.name != "ssao_radius") && (p_property.name != "ssao_intensity")) { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } + } + } + if (p_property.name == "background_color") { if (bg_mode != BG_COLOR && ambient_source != AMBIENT_SOURCE_COLOR) { p_property.usage = PROPERTY_USAGE_NO_EDITOR; diff --git a/servers/rendering/storage/environment_storage.cpp b/servers/rendering/storage/environment_storage.cpp index 34a36848620..881723be9d6 100644 --- a/servers/rendering/storage/environment_storage.cpp +++ b/servers/rendering/storage/environment_storage.cpp @@ -588,8 +588,8 @@ void RendererEnvironmentStorage::environment_set_ssao(RID p_env, bool p_enable, Environment *env = environment_owner.get_or_null(p_env); ERR_FAIL_NULL(env); #ifdef DEBUG_ENABLED - if (OS::get_singleton()->get_current_rendering_method() != "forward_plus" && p_enable) { - WARN_PRINT_ONCE_ED("Screen-space ambient occlusion (SSAO) can only be enabled when using the Forward+ renderer."); + if (OS::get_singleton()->get_current_rendering_method() == "mobile" && p_enable) { + WARN_PRINT_ONCE_ED("Screen-space ambient occlusion (SSAO) can only be enabled when using the Forward+ or Compatibility renderers."); } #endif env->ssao_enabled = p_enable;