diff --git a/doc/classes/BaseMaterial3D.xml b/doc/classes/BaseMaterial3D.xml
index 76f9c20f0f7..e76bb5dc9c7 100644
--- a/doc/classes/BaseMaterial3D.xml
+++ b/doc/classes/BaseMaterial3D.xml
@@ -182,6 +182,9 @@
If [code]true[/code], the object receives no shadow that would otherwise be cast onto it.
+
+ If [code]true[/code], disables specular occlusion even if [member ProjectSettings.rendering/reflections/specular_occlusion/enabled] is [code]false[/code].
+
Distance at which the object appears fully opaque.
[b]Note:[/b] If [member distance_fade_max_distance] is less than [member distance_fade_min_distance], the behavior will be reversed. The object will start to fade away at [member distance_fade_max_distance] and will fully disappear once it reaches [member distance_fade_min_distance].
@@ -706,7 +709,10 @@
Disables receiving depth-based or volumetric fog.
-
+
+ Disables specular occlusion.
+
+
Represents the size of the [enum Flags] enum.
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 9ab9c088ed6..7713ab327c5 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -3095,6 +3095,9 @@
Lower-end override for [member rendering/reflections/sky_reflections/texture_array_reflections] on mobile devices, due to performance concerns or driver support.
+
+ If [code]true[/code], reduces reflections based on ambient light.
+
Sets the renderer that will be used by the project. Options are:
[b]forward_plus[/b] (Forward+): High-end renderer designed for desktop devices. Has a higher base overhead, but scales well with complex scenes. Not suitable for older devices or mobile.
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index e23e3b935b3..d0d297f7916 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -4225,6 +4225,9 @@ RasterizerSceneGLES3::RasterizerSceneGLES3() {
if (config->force_vertex_shading) {
global_defines += "\n#define USE_VERTEX_LIGHTING\n";
}
+ if (!config->specular_occlusion) {
+ global_defines += "\n#define SPECULAR_OCCLUSION_DISABLED\n";
+ }
material_storage->shaders.scene_shader.initialize(global_defines);
scene_globals.shader_default_version = material_storage->shaders.scene_shader.version_create();
material_storage->shaders.scene_shader.version_bind_shader(scene_globals.shader_default_version, SceneShaderGLES3::MODE_COLOR);
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 53cd6988e4d..0fa22f1958c 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -2120,8 +2120,18 @@ void main() {
#endif // USE_LIGHTMAP_CAPTURE
#endif // !DISABLE_LIGHTMAP
- ambient_light *= albedo.rgb;
ambient_light *= ao;
+#ifndef SPECULAR_OCCLUSION_DISABLED
+ float specular_occlusion = (ambient_light.r * 0.3 + ambient_light.g * 0.59 + ambient_light.b * 0.11) * 2.0; // Luminance of ambient light.
+ specular_occlusion = min(specular_occlusion * 4.0, 1.0); // This multiplication preserves speculars on bright areas.
+
+ float reflective_f = (1.0 - roughness) * metallic;
+ // 10.0 is a magic number, it gives the intended effect in most scenarios.
+ // Low enough for occlusion, high enough for reaction to lights and shadows.
+ specular_occlusion = max(min(reflective_f * specular_occlusion * 10.0, 1.0), specular_occlusion);
+ specular_light *= specular_occlusion;
+#endif // !SPECULAR_OCCLUSION_DISABLED
+ ambient_light *= albedo.rgb;
#endif // !AMBIENT_LIGHT_DISABLED
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 749947ebc3b..9816061eb48 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -185,6 +185,7 @@ Config::Config() {
#endif
force_vertex_shading = GLOBAL_GET("rendering/shading/overrides/force_vertex_shading");
+ specular_occlusion = GLOBAL_GET("rendering/reflections/specular_occlusion/enabled");
use_nearest_mip_filter = GLOBAL_GET("rendering/textures/default_filters/use_nearest_mipmap_filter");
use_depth_prepass = bool(GLOBAL_GET("rendering/driver/depth_prepass/enable"));
diff --git a/drivers/gles3/storage/config.h b/drivers/gles3/storage/config.h
index 531c74002a9..60753d2312a 100644
--- a/drivers/gles3/storage/config.h
+++ b/drivers/gles3/storage/config.h
@@ -83,6 +83,7 @@ public:
bool srgb_framebuffer_supported = false;
bool force_vertex_shading = false;
+ bool specular_occlusion = false;
bool support_anisotropic_filter = false;
float anisotropic_level = 0.0f;
diff --git a/drivers/gles3/storage/material_storage.cpp b/drivers/gles3/storage/material_storage.cpp
index 30097741d8d..75f1edb4a26 100644
--- a/drivers/gles3/storage/material_storage.cpp
+++ b/drivers/gles3/storage/material_storage.cpp
@@ -1382,6 +1382,8 @@ MaterialStorage::MaterialStorage() {
}
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
+ actions.render_mode_defines["specular_occlusion_disabled"] = "#define SPECULAR_OCCLUSION_DISABLED\n";
+
actions.default_filter = ShaderLanguage::FILTER_LINEAR_MIPMAP;
actions.default_repeat = ShaderLanguage::REPEAT_ENABLE;
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index 4e84e15e73f..206cac213de 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -873,7 +873,9 @@ void BaseMaterial3D::_update_shader() {
if (flags[FLAG_DISABLE_FOG]) {
code += ", fog_disabled";
}
-
+ if (flags[FLAG_DISABLE_SPECULAR_OCCLUSION]) {
+ code += ", specular_occlusion_disabled";
+ }
if (transparency == TRANSPARENCY_ALPHA_DEPTH_PRE_PASS) {
code += ", depth_prepass_alpha";
}
@@ -3208,6 +3210,7 @@ void BaseMaterial3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "specular_mode", PROPERTY_HINT_ENUM, "SchlickGGX,Toon,Disabled"), "set_specular_mode", "get_specular_mode");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "disable_ambient_light"), "set_flag", "get_flag", FLAG_DISABLE_AMBIENT_LIGHT);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "disable_fog"), "set_flag", "get_flag", FLAG_DISABLE_FOG);
+ ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "disable_specular_occlusion"), "set_flag", "get_flag", FLAG_DISABLE_SPECULAR_OCCLUSION);
ADD_GROUP("Vertex Color", "vertex_color");
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "vertex_color_use_as_albedo"), "set_flag", "get_flag", FLAG_ALBEDO_FROM_VERTEX_COLOR);
@@ -3466,6 +3469,7 @@ void BaseMaterial3D::_bind_methods() {
BIND_ENUM_CONSTANT(FLAG_PARTICLE_TRAILS_MODE);
BIND_ENUM_CONSTANT(FLAG_ALBEDO_TEXTURE_MSDF);
BIND_ENUM_CONSTANT(FLAG_DISABLE_FOG);
+ BIND_ENUM_CONSTANT(FLAG_DISABLE_SPECULAR_OCCLUSION);
BIND_ENUM_CONSTANT(FLAG_MAX);
BIND_ENUM_CONSTANT(DIFFUSE_BURLEY);
diff --git a/scene/resources/material.h b/scene/resources/material.h
index 4c9044c30f2..d9487769502 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -267,6 +267,7 @@ public:
FLAG_PARTICLE_TRAILS_MODE,
FLAG_ALBEDO_TEXTURE_MSDF,
FLAG_DISABLE_FOG,
+ FLAG_DISABLE_SPECULAR_OCCLUSION,
FLAG_MAX
};
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index 193a1458099..ff1e6b42b48 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -4872,6 +4872,11 @@ RenderForwardClustered::RenderForwardClustered() {
defines += "\n#define USE_VERTEX_LIGHTING\n";
}
+ bool specular_occlusion = GLOBAL_GET("rendering/reflections/specular_occlusion/enabled");
+ if (!specular_occlusion) {
+ defines += "\n#define SPECULAR_OCCLUSION_DISABLED\n";
+ }
+
{
//lightmaps
scene_state.max_lightmaps = MAX_LIGHTMAPS;
diff --git a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
index 69075ad169f..f13f364f3c1 100644
--- a/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.cpp
@@ -766,6 +766,8 @@ void SceneShaderForwardClustered::init(const String p_defines) {
actions.render_mode_defines["debug_shadow_splits"] = "#define DEBUG_DRAW_PSSM_SPLITS\n";
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
+ actions.render_mode_defines["specular_occlusion_disabled"] = "#define SPECULAR_OCCLUSION_DISABLED\n";
+
actions.base_texture_binding_index = 1;
actions.texture_layout_set = RenderForwardClustered::MATERIAL_UNIFORM_SET;
actions.base_uniform_string = "material.";
diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
index 5ab4ce570d4..f809077416b 100644
--- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
@@ -3221,6 +3221,11 @@ RenderForwardMobile::RenderForwardMobile() {
defines += "\n#define USE_VERTEX_LIGHTING\n";
}
+ bool specular_occlusion = GLOBAL_GET("rendering/reflections/specular_occlusion/enabled");
+ if (!specular_occlusion) {
+ defines += "\n#define SPECULAR_OCCLUSION_DISABLED\n";
+ }
+
{
//lightmaps
scene_state.max_lightmaps = 2;
diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
index 7ae930665c1..47250fd6aec 100644
--- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
+++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp
@@ -698,6 +698,8 @@ void SceneShaderForwardMobile::init(const String p_defines) {
actions.render_mode_defines["debug_shadow_splits"] = "#define DEBUG_DRAW_PSSM_SPLITS\n";
actions.render_mode_defines["fog_disabled"] = "#define FOG_DISABLED\n";
+ actions.render_mode_defines["specular_occlusion_disabled"] = "#define SPECULAR_OCCLUSION_DISABLED\n";
+
actions.base_texture_binding_index = 1;
actions.texture_layout_set = RenderForwardMobile::MATERIAL_UNIFORM_SET;
actions.base_uniform_string = "material.";
diff --git a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
index bb78cd1a7ac..b1812d72f8a 100644
--- a/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
@@ -1969,8 +1969,18 @@ void fragment_shader(in SceneData scene_data) {
//finalize ambient light here
{
- ambient_light *= albedo.rgb;
ambient_light *= ao;
+#ifndef SPECULAR_OCCLUSION_DISABLED
+ float specular_occlusion = (ambient_light.r * 0.3 + ambient_light.g * 0.59 + ambient_light.b * 0.11) * 2.0; // Luminance of ambient light.
+ specular_occlusion = min(specular_occlusion * 4.0, 1.0); // This multiplication preserves speculars on bright areas.
+
+ float reflective_f = (1.0 - roughness) * metallic;
+ // 10.0 is a magic number, it gives the intended effect in most scenarios.
+ // Low enough for occlusion, high enough for reaction to lights and shadows.
+ specular_occlusion = max(min(reflective_f * specular_occlusion * 10.0, 1.0), specular_occlusion);
+ specular_light *= specular_occlusion;
+#endif // SPECULAR_OCCLUSION_DISABLED
+ ambient_light *= albedo.rgb;
if (bool(implementation_data.ss_effects_flags & SCREEN_SPACE_EFFECTS_FLAGS_USE_SSIL)) {
#ifdef USE_MULTIVIEW
diff --git a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
index edbebbd14c3..83fd4bff89a 100644
--- a/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
+++ b/servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
@@ -1437,9 +1437,18 @@ void main() {
} //Reflection probes
// finalize ambient light here
-
- ambient_light *= albedo.rgb;
ambient_light *= ao;
+#ifndef SPECULAR_OCCLUSION_DISABLED
+ float specular_occlusion = (ambient_light.r * 0.3 + ambient_light.g * 0.59 + ambient_light.b * 0.11) * 2.0; // Luminance of ambient light.
+ specular_occlusion = min(specular_occlusion * 4.0, 1.0); // This multiplication preserves speculars on bright areas.
+
+ float reflective_f = (1.0 - roughness) * metallic;
+ // 10.0 is a magic number, it gives the intended effect in most scenarios.
+ // Low enough for occlusion, high enough for reaction to lights and shadows.
+ specular_occlusion = max(min(reflective_f * specular_occlusion * 10.0, 1.0), specular_occlusion);
+ specular_light *= specular_occlusion;
+#endif // USE_SPECULAR_OCCLUSION
+ ambient_light *= albedo.rgb;
#endif // !AMBIENT_LIGHT_DISABLED
diff --git a/servers/rendering/shader_types.cpp b/servers/rendering/shader_types.cpp
index 5eb113c8762..cf8c1bd6ca0 100644
--- a/servers/rendering/shader_types.cpp
+++ b/servers/rendering/shader_types.cpp
@@ -242,6 +242,7 @@ ShaderTypes::ShaderTypes() {
shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("alpha_to_coverage_and_one") });
shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("debug_shadow_splits") });
shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("fog_disabled") });
+ shader_modes[RS::SHADER_SPATIAL].modes.push_back({ PNAME("specular_occlusion_disabled") });
}
/************ CANVAS ITEM **************************/
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index f0768c1ef8b..37a9d0947a3 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -3632,6 +3632,7 @@ void RenderingServer::init() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/reflections/reflection_atlas/reflection_size", PROPERTY_HINT_RANGE, "0,4096,1"), 256);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/reflections/reflection_atlas/reflection_size.mobile", PROPERTY_HINT_RANGE, "0,2048,1"), 128);
GLOBAL_DEF(PropertyInfo(Variant::INT, "rendering/reflections/reflection_atlas/reflection_count", PROPERTY_HINT_RANGE, "0,256,1"), 64);
+ GLOBAL_DEF_RST("rendering/reflections/specular_occlusion/enabled", true);
GLOBAL_DEF("rendering/global_illumination/gi/use_half_resolution", false);