From 85e080fcc074457d470e87a5cfe4805aa147ab35 Mon Sep 17 00:00:00 2001 From: Hugo Locurcio Date: Tue, 21 Sep 2021 02:58:50 +0200 Subject: [PATCH] Backport new 3D point light attenuation as an option This provides more realistic lighting with a very small performance cost. The option is available in both GLES3 and GLES2, and can be enabled in the Project Settings. This goes well with the ACES Fitted tonemapping mode that was recently added. When enabled, this also makes upgrading Godot 3.x projects to Godot 4.0 easier, since lighting in 3.x will better match how it'll look in Godot 4.0. --- doc/classes/ProjectSettings.xml | 4 ++ drivers/gles2/rasterizer_scene_gles2.cpp | 2 + drivers/gles2/rasterizer_storage_gles2.cpp | 2 + drivers/gles2/rasterizer_storage_gles2.h | 1 + drivers/gles2/shaders/scene.glsl | 39 ++++++++++++++++++- drivers/gles3/rasterizer_scene_gles3.cpp | 2 + drivers/gles3/rasterizer_storage_gles3.cpp | 2 + drivers/gles3/rasterizer_storage_gles3.h | 1 + drivers/gles3/shaders/scene.glsl | 43 ++++++++++++++++++++- modules/lightmapper_cpu/lightmapper_cpu.cpp | 23 ++++++++++- modules/lightmapper_cpu/lightmapper_cpu.h | 3 ++ servers/visual_server.cpp | 2 + 12 files changed, 120 insertions(+), 4 deletions(-) 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");