diff --git a/doc/classes/LightmapGI.xml b/doc/classes/LightmapGI.xml index 55c6ef5eb5de..a1ec492ffef3 100644 --- a/doc/classes/LightmapGI.xml +++ b/doc/classes/LightmapGI.xml @@ -61,6 +61,10 @@ If [code]true[/code], uses a CPU-based denoising algorithm on the generated lightmap. This eliminates most noise within the generated lightmap at the cost of longer bake times. File sizes are generally not impacted significantly by the use of a denoiser, although lossless compression may do a better job at compressing a denoised image. [b]Note:[/b] The built-in denoiser (OpenImageDenoise) may crash when denoising lightmaps in large scenes. If you encounter a crash at the end of lightmap baking, try disabling [member use_denoiser]. + + If [code]true[/code], bakes the [DirectionalLight3D]s' direct light shadows into a [i]shadowmask[/i] which is stored in the lightmap textures' alpha channel. This shadowmask is used to keep static shadows visible past the DirectionalLights' [member DirectionalLight3D.directional_shadow_max_distance] by blending the last shadow split with the shadowmask. This in turn allows you to use a lower [member DirectionalLight3D.directional_shadow_max_distance] for dynamic objects. Lower real-time shadow distances improve shadow detail and performance while making shadow acne less visible. + [b]Note:[/b] If you have multiple [DirectionalLight3D]s that have their [member Light3D.light_bake_mode] set to [constant Light3D.BAKE_DYNAMIC], then the shadowmask will only be present in areas where their shadows overlap. + diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index bcb32d77c33f..e0bb007c426e 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -662,7 +662,7 @@ LightmapperRD::BakeError LightmapperRD::_dilate(RenderingDevice *rd, Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { +LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) { if (p_step_function) { p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true); } @@ -682,6 +682,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d return bake_error; } + // If there are no valid directional lights for shadowmasking, the entire + // scene would be shadowed and this saves baking time. + if (p_use_shadowmask) { + bool vaild_shadowmask_light = false; + for (int i = 0; i < lights.size(); i++) { + if (lights[i].type == LIGHT_TYPE_DIRECTIONAL && !lights[i].static_bake) { + vaild_shadowmask_light = true; + break; + } + } + + if (!vaild_shadowmask_light) { + p_use_shadowmask = false; + WARN_PRINT("Shadowmask disabled: no directional light with their bake mode set to dynamic exists."); + } + } + #ifdef DEBUG_TEXTURES for (int i = 0; i < atlas_slices; i++) { albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png"); @@ -702,6 +719,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RID light_accum_tex2; RID light_primary_dynamic_tex; RID light_environment_tex; + RID shadowmask_tex; #define FREE_TEXTURES \ rd->free(albedo_array_tex); \ @@ -713,7 +731,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d rd->free(light_accum_tex2); \ rd->free(light_accum_tex); \ rd->free(light_primary_dynamic_tex); \ - rd->free(light_environment_tex); + rd->free(light_environment_tex); \ + rd->free(shadowmask_tex); { // create all textures @@ -745,8 +764,17 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d position_tex = rd->texture_create(tf, RD::TextureView()); unocclude_tex = rd->texture_create(tf, RD::TextureView()); - tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; tf.usage_bits = RD::TEXTURE_USAGE_COLOR_ATTACHMENT_BIT | RD::TEXTURE_USAGE_SAMPLING_BIT | RD::TEXTURE_USAGE_STORAGE_BIT | RD::TEXTURE_USAGE_CAN_COPY_FROM_BIT | RD::TEXTURE_USAGE_CAN_COPY_TO_BIT | RD::TEXTURE_USAGE_CAN_UPDATE_BIT; + tf.format = RD::DATA_FORMAT_R16_SFLOAT; + + // We use the alpha channel of the lightmap during baking, so we + // need to bake the shadowmask into a separate texture first and + // then copy it into the lightmap's alpha channel once it's done + // being used. + shadowmask_tex = rd->texture_create(tf, RD::TextureView()); + rd->texture_clear(shadowmask_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); + + tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT; light_source_tex = rd->texture_create(tf, RD::TextureView()); rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices); @@ -963,7 +991,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d Ref compute_shader; compute_shader.instantiate(); - err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : ""); + err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, vformat("%s\n%s", p_bake_sh ? "\n#define USE_SH_LIGHTMAPS\n" : "", p_use_shadowmask ? "\n#define USE_SHADOWMASK\n" : "")); if (err != OK) { FREE_TEXTURES FREE_BUFFERS @@ -1129,6 +1157,13 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d u.append_id(light_primary_dynamic_tex); uniforms.push_back(u); } + { + RD::Uniform u; + u.uniform_type = RD::UNIFORM_TYPE_IMAGE; + u.binding = 6; + u.append_id(shadowmask_tex); + uniforms.push_back(u); + } } RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1); @@ -1630,8 +1665,21 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d for (int i = 0; i < atlas_slices * (p_bake_sh ? 4 : 1); i++) { Vector s = rd->texture_get_data(light_accum_tex, i); + if (p_use_shadowmask) { // copy shadowmask into alpha + Vector sms = rd->texture_get_data(shadowmask_tex, i); + uint32_t count = sms.size() / 2; //uint16s + const uint16_t *src = (const uint16_t *)sms.ptr(); + uint16_t *dst = (uint16_t *)s.ptrw(); + for (uint32_t j = 0; j < count; j++) { + dst[j * 4 + 3] = src[j]; + } + } + Ref img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s); - img->convert(Image::FORMAT_RGBH); //remove alpha + if (!p_use_shadowmask) { + img->convert(Image::FORMAT_RGBH); //remove alpha + } + bake_textures.push_back(img); } diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index 061c9ba000c8..a28b0e8a4125 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -241,7 +241,7 @@ class LightmapperRD : public Lightmapper { virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; virtual void add_probe(const Vector3 &p_position) override; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr, float p_exposure_normalization = 1.0) override; int get_bake_texture_count() const override; Ref get_bake_texture(int p_index) const override; diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index c2557dfed382..b584d9c899eb 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -63,6 +63,9 @@ layout(set = 1, binding = 6) uniform texture2D environment; #endif #ifdef MODE_DIRECT_LIGHT layout(rgba32f, set = 1, binding = 5) uniform restrict writeonly image2DArray primary_dynamic; +#ifdef USE_SHADOWMASK +layout(r16, set = 1, binding = 6) uniform restrict writeonly image2DArray shadowmask; +#endif #endif #ifdef MODE_DILATE @@ -306,6 +309,10 @@ void main() { vec3 static_light = vec3(0.0); vec3 dynamic_light = vec3(0.0); +#ifdef USE_SHADOWMASK + float sm = 0.0; +#endif + #ifdef USE_SH_LIGHTMAPS vec4 sh_accum[4] = vec4[]( vec4(0.0, 0.0, 0.0, 1.0), @@ -421,6 +428,11 @@ void main() { } else { dynamic_light += light; +#ifdef USE_SHADOWMASK + if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) { + sm = clamp(sm + attenuation * penumbra, 0.0, 1.0); + } +#endif } } @@ -449,6 +461,10 @@ void main() { imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(static_light, 1.0)); #endif +#ifdef USE_SHADOWMASK + imageStore(shadowmask, ivec3(atlas_pos, params.atlas_slice), vec4(sm)); +#endif + #endif #ifdef MODE_BOUNCE_LIGHT diff --git a/scene/3d/lightmap_gi.cpp b/scene/3d/lightmap_gi.cpp index 3ee08fd5485f..5276a3e2d604 100644 --- a/scene/3d/lightmap_gi.cpp +++ b/scene/3d/lightmap_gi.cpp @@ -1079,7 +1079,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa } } - Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, max_texture_size, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); + Lightmapper::BakeError bake_err = lightmapper->bake(Lightmapper::BakeQuality(bake_quality), use_denoiser, bounces, bias, max_texture_size, use_shadowmask, directional, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization); if (bake_err == Lightmapper::BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES) { return BAKE_ERROR_MESHES_INVALID; @@ -1381,6 +1381,14 @@ bool LightmapGI::is_interior() const { return interior; } +void LightmapGI::set_use_shadowmask(bool p_enable) { + use_shadowmask = p_enable; +} + +bool LightmapGI::is_using_shadowmask() const { + return use_shadowmask; +} + void LightmapGI::set_environment_mode(EnvironmentMode p_mode) { environment_mode = p_mode; notify_property_list_changed(); @@ -1515,6 +1523,9 @@ void LightmapGI::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &LightmapGI::set_use_denoiser); ClassDB::bind_method(D_METHOD("is_using_denoiser"), &LightmapGI::is_using_denoiser); + ClassDB::bind_method(D_METHOD("set_use_shadowmask", "use_shadowmask"), &LightmapGI::set_use_shadowmask); + ClassDB::bind_method(D_METHOD("is_using_shadowmask"), &LightmapGI::is_using_shadowmask); + ClassDB::bind_method(D_METHOD("set_interior", "enable"), &LightmapGI::set_interior); ClassDB::bind_method(D_METHOD("is_interior"), &LightmapGI::is_interior); @@ -1532,6 +1543,7 @@ void LightmapGI::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_denoiser"), "set_use_denoiser", "is_using_denoiser"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_shadowmask"), "set_use_shadowmask", "is_using_shadowmask"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bias", PROPERTY_HINT_RANGE, "0.00001,0.1,0.00001,or_greater"), "set_bias", "get_bias"); ADD_PROPERTY(PropertyInfo(Variant::INT, "max_texture_size", PROPERTY_HINT_RANGE, "2048,16384,1"), "set_max_texture_size", "get_max_texture_size"); ADD_GROUP("Environment", "environment_"); diff --git a/scene/3d/lightmap_gi.h b/scene/3d/lightmap_gi.h index b9e33cf30044..60a146c3f6ca 100644 --- a/scene/3d/lightmap_gi.h +++ b/scene/3d/lightmap_gi.h @@ -148,6 +148,7 @@ class LightmapGI : public VisualInstance3D { float bias = 0.0005; int max_texture_size = 16384; bool interior = false; + bool use_shadowmask = true; EnvironmentMode environment_mode = ENVIRONMENT_MODE_SCENE; Ref environment_custom_sky; Color environment_custom_color = Color(1, 1, 1); @@ -244,6 +245,9 @@ class LightmapGI : public VisualInstance3D { void set_interior(bool p_interior); bool is_interior() const; + void set_use_shadowmask(bool p_enable); + bool is_using_shadowmask() const; + void set_environment_mode(EnvironmentMode p_mode); EnvironmentMode get_environment_mode() const; diff --git a/scene/3d/lightmapper.h b/scene/3d/lightmapper.h index 5a390eaede20..9be82cf58eb4 100644 --- a/scene/3d/lightmapper.h +++ b/scene/3d/lightmapper.h @@ -180,7 +180,7 @@ class Lightmapper : public RefCounted { virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0; virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) = 0; virtual void add_probe(const Vector3 &p_position) = 0; - virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0; + virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_use_shadowmask, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_step_userdata = nullptr, float p_exposure_normalization = 1.0) = 0; virtual int get_bake_texture_count() const = 0; virtual Ref get_bake_texture(int p_index) const = 0; 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 cec54a2c5f93..eb61f9894d71 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 @@ -1073,6 +1073,7 @@ void fragment_shader(in SceneData scene_data) { vec3 specular_light = vec3(0.0, 0.0, 0.0); vec3 diffuse_light = vec3(0.0, 0.0, 0.0); vec3 ambient_light = vec3(0.0, 0.0, 0.0); + float shadowmask = 1.0; #ifndef MODE_UNSHADED // Used in regular draw pass and when drawing SDFs for SDFGI and materials for VoxelGI. @@ -1209,7 +1210,14 @@ void fragment_shader(in SceneData scene_data) { if (uses_sh) { uvw.z *= 4.0; //SH textures use 4 times more data - vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb; + + // Only the alpha channel of the first lightmap is used for + // shadowmasking. The alpha channels of the other three lightmaps + // are not used. + vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0); + shadowmask = lightmap_sample.a; + + vec3 lm_light_l0 = lightmap_sample.rgb; vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb; vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb; vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb; @@ -1231,7 +1239,9 @@ void fragment_shader(in SceneData scene_data) { } else { uint idx = instances.data[instance_index].gi_offset >> 20; - ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization; + vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0); + ambient_light += lightmap_sample.rgb * lightmaps.data[idx].exposure_normalization; + shadowmask = lightmap_sample.a; } } #else @@ -1736,6 +1746,8 @@ void fragment_shader(in SceneData scene_data) { #undef BIAS_FUNC } // shadows + shadow = min(shadow, shadowmask); + if (i < 4) { shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8); } else { 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 bb9216c4fa0d..e735babafbee 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 @@ -975,6 +975,7 @@ void main() { vec3 specular_light = vec3(0.0, 0.0, 0.0); vec3 diffuse_light = vec3(0.0, 0.0, 0.0); vec3 ambient_light = vec3(0.0, 0.0, 0.0); + float shadowmask = 1.0; #ifndef MODE_UNSHADED // Used in regular draw pass and when drawing SDFs for SDFGI and materials for VoxelGI. @@ -1112,7 +1113,14 @@ void main() { if (uses_sh) { uvw.z *= 4.0; //SH textures use 4 times more data - vec3 lm_light_l0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0).rgb; + + // Only the alpha channel of the first lightmap is used for + // shadowmasking. The alpha channels of the other three lightmaps + // are not used. + vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 0.0), 0.0); + shadowmask = lightmap_sample.a; + + vec3 lm_light_l0 = lightmap_sample.rgb; vec3 lm_light_l1n1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 1.0), 0.0).rgb; vec3 lm_light_l1_0 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 2.0), 0.0).rgb; vec3 lm_light_l1p1 = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw + vec3(0.0, 0.0, 3.0), 0.0).rgb; @@ -1132,7 +1140,9 @@ void main() { } } else { - ambient_light += textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0).rgb * lightmaps.data[idx].exposure_normalization; + vec4 lightmap_sample = textureLod(sampler2DArray(lightmap_textures[ofs], material_samplers[SAMPLER_LINEAR_CLAMP]), uvw, 0.0); + ambient_light += lightmap_sample.rgb * lightmaps.data[idx].exposure_normalization; + shadowmask = lightmap_sample.a; } } @@ -1401,7 +1411,7 @@ void main() { shadow = mix(shadow, shadow2, pssm_blend); } - shadow = mix(shadow, 1.0, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance + shadow = mix(shadow, shadowmask, smoothstep(directional_lights.data[i].fade_from, directional_lights.data[i].fade_to, vertex.z)); //done with negative values for performance #undef BIAS_FUNC } @@ -1502,6 +1512,8 @@ void main() { } #endif + shadow = min(shadow, shadowmask); + if (i < 4) { shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8); } else {