diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 33e8c6d78f6..5cea58b9a24 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1377,6 +1377,10 @@
Lower-end override for [member rendering/quality/shading/force_vertex_shading] on mobile devices, due to performance concerns or driver support.
+
+ If [code]true[/code], enables new physical light attenuation for [OmniLight]s and [SpotLight]s. This results in more realistic lighting appearance with a very small performance cost. When physical light attenuation is enabled, lights will appear to be darker as a result of the new attenuation formula. This can be compensated by adjusting the lights' energy or attenuation values.
+ Changes to this setting will only be applied upon restarting the application.
+
Size for cubemap into which the shadow is rendered before being copied into the shadow atlas. A higher number can result in higher resolution shadows when used with a higher [member rendering/quality/shadow_atlas/size]. Setting higher than a quarter of the [member rendering/quality/shadow_atlas/size] will not result in a perceptible increase in visual quality.
diff --git a/drivers/gles2/rasterizer_scene_gles2.cpp b/drivers/gles2/rasterizer_scene_gles2.cpp
index 35d87d56342..9e2b5147ab9 100644
--- a/drivers/gles2/rasterizer_scene_gles2.cpp
+++ b/drivers/gles2/rasterizer_scene_gles2.cpp
@@ -2426,6 +2426,8 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
storage->info.render.surface_switch_count++;
}
+ state.scene_shader.set_conditional(SceneShaderGLES2::USE_PHYSICAL_LIGHT_ATTENUATION, storage->config.use_physical_light_attenuation);
+
bool octahedral_compression = ((RasterizerStorageGLES2::Surface *)e->geometry)->format & VisualServer::ArrayFormat::ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION;
if (octahedral_compression != prev_octahedral_compression) {
state.scene_shader.set_conditional(SceneShaderGLES2::ENABLE_OCTAHEDRAL_COMPRESSION, octahedral_compression);
diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp
index a3ac7fc129b..28c0a65690a 100644
--- a/drivers/gles2/rasterizer_storage_gles2.cpp
+++ b/drivers/gles2/rasterizer_storage_gles2.cpp
@@ -6508,6 +6508,8 @@ void RasterizerStorageGLES2::initialize() {
GLOBAL_DEF_RST("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false);
config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling");
+ config.use_physical_light_attenuation = GLOBAL_GET("rendering/quality/shading/use_physical_light_attenuation");
+
int orphan_mode = GLOBAL_GET("rendering/2d/opengl/legacy_orphan_buffers");
switch (orphan_mode) {
default: {
diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h
index b1b4c0a8a62..3ffe9ab7099 100644
--- a/drivers/gles2/rasterizer_storage_gles2.h
+++ b/drivers/gles2/rasterizer_storage_gles2.h
@@ -58,6 +58,7 @@ public:
bool use_anisotropic_filter;
bool use_skeleton_software;
bool use_lightmap_filter_bicubic;
+ bool use_physical_light_attenuation;
int max_vertex_texture_image_units;
int max_texture_image_units;
diff --git a/drivers/gles2/shaders/scene.glsl b/drivers/gles2/shaders/scene.glsl
index 899e5cc467c..a125eb8d0fe 100644
--- a/drivers/gles2/shaders/scene.glsl
+++ b/drivers/gles2/shaders/scene.glsl
@@ -206,6 +206,15 @@ uniform highp float light_spot_attenuation;
uniform highp float light_spot_range;
uniform highp float light_spot_angle;
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+
void light_compute(
vec3 N,
vec3 L,
@@ -546,9 +555,12 @@ VERTEX_SHADER_CODE
float normalized_distance = light_length / light_range;
if (normalized_distance < 1.0) {
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ float omni_attenuation = get_omni_attenuation(light_length, 1.0 / light_range, light_attenuation);
+#else
float omni_attenuation = pow(1.0 - normalized_distance, light_attenuation);
+#endif
- vec3 attenuation = vec3(omni_attenuation);
light_att = vec3(omni_attenuation);
} else {
light_att = vec3(0.0);
@@ -565,7 +577,12 @@ VERTEX_SHADER_CODE
float normalized_distance = light_length / light_range;
if (normalized_distance < 1.0) {
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ float spot_attenuation = get_omni_attenuation(light_length, 1.0 / light_range, light_attenuation);
+#else
float spot_attenuation = pow(1.0 - normalized_distance, light_attenuation);
+#endif
+
vec3 spot_dir = light_direction;
float spot_cutoff = light_spot_angle;
@@ -1222,6 +1239,17 @@ float GTR1(float NdotH, float a) {
return (a2 - 1.0) / (M_PI * log(a2) * t);
}
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+#endif
+
void light_compute(
vec3 N,
vec3 L,
@@ -1852,7 +1880,11 @@ FRAGMENT_SHADER_CODE
float normalized_distance = light_length / light_range;
if (normalized_distance < 1.0) {
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ float omni_attenuation = get_omni_attenuation(light_length, 1.0 / light_range, light_attenuation);
+#else
float omni_attenuation = pow(1.0 - normalized_distance, light_attenuation);
+#endif
light_att = vec3(omni_attenuation);
} else {
@@ -2134,7 +2166,12 @@ FRAGMENT_SHADER_CODE
float normalized_distance = light_length / light_range;
if (normalized_distance < 1.0) {
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ float spot_attenuation = get_omni_attenuation(light_length, 1.0 / light_range, light_attenuation);
+#else
float spot_attenuation = pow(1.0 - normalized_distance, light_attenuation);
+#endif
+
vec3 spot_dir = light_direction;
float spot_cutoff = light_spot_angle;
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 34b1218514c..19af12b33d7 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -2111,6 +2111,8 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_
}
}
+ state.scene_shader.set_conditional(SceneShaderGLES3::USE_PHYSICAL_LIGHT_ATTENUATION, storage->config.use_physical_light_attenuation);
+
bool octahedral_compression = ((RasterizerStorageGLES3::Surface *)e->geometry)->format & VisualServer::ArrayFormat::ARRAY_FLAG_USE_OCTAHEDRAL_COMPRESSION;
if (octahedral_compression != prev_octahedral_compression) {
state.scene_shader.set_conditional(SceneShaderGLES3::ENABLE_OCTAHEDRAL_COMPRESSION, octahedral_compression);
diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp
index b22708eb105..0cb06f24777 100644
--- a/drivers/gles3/rasterizer_storage_gles3.cpp
+++ b/drivers/gles3/rasterizer_storage_gles3.cpp
@@ -8252,6 +8252,8 @@ void RasterizerStorageGLES3::initialize() {
GLOBAL_DEF("rendering/quality/lightmapping/use_bicubic_sampling.mobile", false);
config.use_lightmap_filter_bicubic = GLOBAL_GET("rendering/quality/lightmapping/use_bicubic_sampling");
+ config.use_physical_light_attenuation = GLOBAL_GET("rendering/quality/shading/use_physical_light_attenuation");
+
config.use_depth_prepass = bool(GLOBAL_GET("rendering/quality/depth_prepass/enable"));
if (config.use_depth_prepass) {
String vendors = GLOBAL_GET("rendering/quality/depth_prepass/disable_for_vendors");
diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h
index 6eb139aa269..bccdc6f7665 100644
--- a/drivers/gles3/rasterizer_storage_gles3.h
+++ b/drivers/gles3/rasterizer_storage_gles3.h
@@ -74,6 +74,7 @@ public:
bool use_fast_texture_filter;
bool use_anisotropic_filter;
bool use_lightmap_filter_bicubic;
+ bool use_physical_light_attenuation;
bool s3tc_supported;
bool latc_supported;
diff --git a/drivers/gles3/shaders/scene.glsl b/drivers/gles3/shaders/scene.glsl
index 607a31b1854..72d6d120f79 100644
--- a/drivers/gles3/shaders/scene.glsl
+++ b/drivers/gles3/shaders/scene.glsl
@@ -234,11 +234,27 @@ void light_compute(vec3 N, vec3 L, vec3 V, vec3 light_color, float roughness, in
}
}
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+#endif
+
void light_process_omni(int idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness, inout vec3 diffuse, inout vec3 specular) {
vec3 light_rel_vec = omni_lights[idx].light_pos_inv_radius.xyz - vertex;
float light_length = length(light_rel_vec);
+
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ vec3 light_attenuation = vec3(get_omni_attenuation(light_length, omni_lights[idx].light_pos_inv_radius.w, omni_lights[idx].light_direction_attenuation.w));
+#else
float normalized_distance = light_length * omni_lights[idx].light_pos_inv_radius.w;
vec3 light_attenuation = vec3(pow(max(1.0 - normalized_distance, 0.0), omni_lights[idx].light_direction_attenuation.w));
+#endif
light_compute(normal, normalize(light_rel_vec), eye_vec, omni_lights[idx].light_color_energy.rgb * light_attenuation, roughness, diffuse, specular);
}
@@ -246,8 +262,14 @@ void light_process_omni(int idx, vec3 vertex, vec3 eye_vec, vec3 normal, float r
void light_process_spot(int idx, vec3 vertex, vec3 eye_vec, vec3 normal, float roughness, inout vec3 diffuse, inout vec3 specular) {
vec3 light_rel_vec = spot_lights[idx].light_pos_inv_radius.xyz - vertex;
float light_length = length(light_rel_vec);
+
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ vec3 light_attenuation = vec3(get_omni_attenuation(light_length, spot_lights[idx].light_pos_inv_radius.w, spot_lights[idx].light_direction_attenuation.w));
+#else
float normalized_distance = light_length * spot_lights[idx].light_pos_inv_radius.w;
vec3 light_attenuation = vec3(pow(max(1.0 - normalized_distance, 0.001), spot_lights[idx].light_direction_attenuation.w));
+#endif
+
vec3 spot_dir = spot_lights[idx].light_direction_attenuation.xyz;
float spot_cutoff = spot_lights[idx].light_params.y;
float scos = max(dot(-normalize(light_rel_vec), spot_dir), spot_cutoff);
@@ -1250,13 +1272,28 @@ in highp float dp_clip;
#endif
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+float get_omni_attenuation(float distance, float inv_range, float decay) {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = max(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * pow(max(distance, 0.0001), -decay);
+}
+#endif
+
void light_process_omni(int idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 binormal, vec3 tangent, vec3 albedo, vec3 transmission, float roughness, float metallic, float specular, float rim, float rim_tint, float clearcoat, float clearcoat_gloss, float anisotropy, float p_blob_intensity, inout vec3 diffuse_light, inout vec3 specular_light, inout float alpha) {
vec3 light_rel_vec = omni_lights[idx].light_pos_inv_radius.xyz - vertex;
float light_length = length(light_rel_vec);
float normalized_distance = light_length * omni_lights[idx].light_pos_inv_radius.w;
float omni_attenuation;
if (normalized_distance < 1.0) {
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ omni_attenuation = get_omni_attenuation(light_length, omni_lights[idx].light_pos_inv_radius.w, omni_lights[idx].light_direction_attenuation.w);
+#else
omni_attenuation = pow(1.0 - normalized_distance, omni_lights[idx].light_direction_attenuation.w);
+#endif
} else {
omni_attenuation = 0.0;
}
@@ -1316,7 +1353,11 @@ void light_process_spot(int idx, vec3 vertex, vec3 eye_vec, vec3 normal, vec3 bi
float normalized_distance = light_length * spot_lights[idx].light_pos_inv_radius.w;
float spot_attenuation;
if (normalized_distance < 1.0) {
- spot_attenuation = pow(1.0 - normalized_distance, spot_lights[idx].light_direction_attenuation.w);
+#ifdef USE_PHYSICAL_LIGHT_ATTENUATION
+ spot_attenuation = get_omni_attenuation(light_length, spot_lights[idx].light_pos_inv_radius.w, spot_lights[idx].light_direction_attenuation.w);
+#else
+ spot_attenuation = pow(1.0 - normalized_distance, omni_lights[idx].light_direction_attenuation.w);
+#endif
} else {
spot_attenuation = 0.0;
}
diff --git a/modules/lightmapper_cpu/lightmapper_cpu.cpp b/modules/lightmapper_cpu/lightmapper_cpu.cpp
index 75cb45b0a03..48385471de8 100644
--- a/modules/lightmapper_cpu/lightmapper_cpu.cpp
+++ b/modules/lightmapper_cpu/lightmapper_cpu.cpp
@@ -704,6 +704,15 @@ _ALWAYS_INLINE_ float uniform_rand() {
return float(state) / float(UINT32_MAX);
}
+float LightmapperCPU::_get_omni_attenuation(float distance, float inv_range, float decay) const {
+ float nd = distance * inv_range;
+ nd *= nd;
+ nd *= nd; // nd^4
+ nd = MAX(1.0 - nd, 0.0);
+ nd *= nd; // nd^2
+ return nd * powf(MAX(distance, 0.0001f), -decay);
+}
+
void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) {
LightmapTexel *lightmap = (LightmapTexel *)r_lightmap;
for (unsigned int i = 0; i < lights.size(); ++i) {
@@ -734,7 +743,11 @@ void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) {
soft_shadowing_disk_size = light.size / dist;
if (light.type == LIGHT_TYPE_OMNI) {
- attenuation = powf(1.0 - dist / light.range, light.attenuation);
+ if (parameters.use_physical_light_attenuation) {
+ attenuation = _get_omni_attenuation(dist, 1.0f / light.range, light.attenuation);
+ } else {
+ attenuation = powf(1.0 - dist / light.range, light.attenuation);
+ }
} else /* (light.type == LIGHT_TYPE_SPOT) */ {
float angle = Math::acos(light.direction.dot(light_to_point));
@@ -743,7 +756,12 @@ void LightmapperCPU::_compute_direct_light(uint32_t p_idx, void *r_lightmap) {
}
float normalized_dist = dist * (1.0f / MAX(0.001f, light.range));
- float norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation);
+ float norm_light_attenuation;
+ if (parameters.use_physical_light_attenuation) {
+ norm_light_attenuation = _get_omni_attenuation(dist, 1.0f / light.range, light.attenuation);
+ } else {
+ norm_light_attenuation = Math::pow(MAX(1.0f - normalized_dist, 0.001f), light.attenuation);
+ }
float spot_cutoff = Math::cos(light.spot_angle);
float scos = MAX(light_to_point.dot(light.direction), spot_cutoff);
@@ -1279,6 +1297,7 @@ LightmapperCPU::BakeError LightmapperCPU::bake(BakeQuality p_quality, bool p_use
// Collect parameters
parameters.use_denoiser = p_use_denoiser;
+ parameters.use_physical_light_attenuation = bool(GLOBAL_GET("rendering/quality/shading/use_physical_light_attenuation"));
parameters.bias = p_bias;
parameters.bounces = p_bounces;
parameters.bounce_indirect_energy = p_bounce_indirect_energy;
diff --git a/modules/lightmapper_cpu/lightmapper_cpu.h b/modules/lightmapper_cpu/lightmapper_cpu.h
index 7f64b3f8611..bcd27cef8d0 100644
--- a/modules/lightmapper_cpu/lightmapper_cpu.h
+++ b/modules/lightmapper_cpu/lightmapper_cpu.h
@@ -85,6 +85,7 @@ class LightmapperCPU : public Lightmapper {
float bounce_indirect_energy;
int samples;
bool use_denoiser = true;
+ bool use_physical_light_attenuation = false;
Ref environment_panorama;
Basis environment_transform;
};
@@ -151,6 +152,8 @@ class LightmapperCPU : public Lightmapper {
Vector3 _fix_sample_position(const Vector3 &p_position, const Vector3 &p_texel_center, const Vector3 &p_normal, const Vector3 &p_tangent, const Vector3 &p_bitangent, const Vector2 &p_texel_size);
void _plot_triangle(const Vector2 *p_vertices, const Vector3 *p_positions, const Vector3 *p_normals, const Vector2 *p_uvs, const Ref &p_albedo_texture, const Ref &p_emission_texture, Vector2i p_size, LocalVector &r_texels, LocalVector &r_lightmap_indices);
+ float _get_omni_attenuation(float distance, float inv_range, float decay) const;
+
void _compute_direct_light(uint32_t p_idx, void *r_lightmap);
void _compute_indirect_light(uint32_t p_idx, void *r_lightmap);
diff --git a/servers/visual_server.cpp b/servers/visual_server.cpp
index 21d5a0a4d6c..53900bd9807 100644
--- a/servers/visual_server.cpp
+++ b/servers/visual_server.cpp
@@ -2636,6 +2636,8 @@ VisualServer::VisualServer() {
GLOBAL_DEF_RST("rendering/misc/mesh_storage/split_stream", false);
+ GLOBAL_DEF_RST("rendering/quality/shading/use_physical_light_attenuation", false);
+
GLOBAL_DEF("rendering/quality/depth_prepass/enable", true);
GLOBAL_DEF("rendering/quality/depth_prepass/disable_for_vendors", "PowerVR,Mali,Adreno,Apple");