Browse Source

Merge pull request #85653 from BlueCube3310/lightmap-gi-shadowmask

Implement LightmapGI shadowmasks
Rémi Verschelde 8 months ago
parent
commit
0e5c337453
27 changed files with 1043 additions and 396 deletions
  1. 5 0
      doc/classes/LightmapGI.xml
  2. 14 0
      doc/classes/LightmapGIData.xml
  3. 53 5
      drivers/gles3/rasterizer_scene_gles3.cpp
  4. 111 68
      drivers/gles3/shaders/scene.glsl
  5. 27 0
      drivers/gles3/storage/light_storage.cpp
  6. 6 2
      drivers/gles3/storage/light_storage.h
  7. 142 33
      modules/lightmapper_rd/lightmapper_rd.cpp
  8. 14 9
      modules/lightmapper_rd/lightmapper_rd.h
  9. 3 0
      modules/lightmapper_rd/lm_common_inc.glsl
  10. 25 6
      modules/lightmapper_rd/lm_compute.glsl
  11. 121 11
      scene/3d/lightmap_gi.cpp
  12. 26 1
      scene/3d/lightmap_gi.h
  13. 6 5
      scene/3d/lightmapper.h
  14. 4 0
      servers/rendering/dummy/storage/light_storage.h
  15. 24 9
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
  16. 1 1
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h
  17. 23 8
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
  18. 1 1
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h
  19. 197 149
      servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl
  20. 7 2
      servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl
  21. 127 81
      servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl
  22. 7 2
      servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl
  23. 66 2
      servers/rendering/renderer_rd/storage_rd/light_storage.cpp
  24. 14 0
      servers/rendering/renderer_rd/storage_rd/light_storage.h
  25. 4 0
      servers/rendering/rendering_server_default.h
  26. 4 0
      servers/rendering/storage/light_storage.h
  27. 11 1
      servers/rendering_server.h

+ 5 - 0
doc/classes/LightmapGI.xml

@@ -69,6 +69,11 @@
 			The quality preset to use when baking lightmaps. This affects bake times, but output file sizes remain mostly identical across quality levels.
 			The quality preset to use when baking lightmaps. This affects bake times, but output file sizes remain mostly identical across quality levels.
 			To further speed up bake times, decrease [member bounces], disable [member use_denoiser] and increase the lightmap texel size on 3D scenes in the Import dock.
 			To further speed up bake times, decrease [member bounces], disable [member use_denoiser] and increase the lightmap texel size on 3D scenes in the Import dock.
 		</member>
 		</member>
+		<member name="shadowmask_mode" type="int" setter="set_shadowmask_mode" getter="get_shadowmask_mode" enum="LightmapGIData.ShadowmaskMode" default="0" experimental="">
+			The shadowmasking policy to use for directional shadows on static objects that are baked with this [LightmapGI] instance.
+			Shadowmasking allows [DirectionalLight3D] nodes to cast shadows even outside the range defined by their [member DirectionalLight3D.directional_shadow_max_distance] property. This is done by baking a texture that contains a shadowmap for the directional light, then using this texture according to the current shadowmask mode.
+			[b]Note:[/b] The shadowmask texture is only created if [member shadowmask_mode] is not [constant LightmapGIData.SHADOWMASK_MODE_NONE]. To see a difference, you need to bake lightmaps again after switching from [constant LightmapGIData.SHADOWMASK_MODE_NONE] to any other mode.
+		</member>
 		<member name="texel_scale" type="float" setter="set_texel_scale" getter="get_texel_scale" default="1.0">
 		<member name="texel_scale" type="float" setter="set_texel_scale" getter="get_texel_scale" default="1.0">
 			Scales the lightmap texel density of all meshes for the current bake. This is a multiplier that builds upon the existing lightmap texel size defined in each imported 3D scene, along with the per-mesh density multiplier (which is designed to be used when the same mesh is used at different scales). Lower values will result in faster bake times.
 			Scales the lightmap texel density of all meshes for the current bake. This is a multiplier that builds upon the existing lightmap texel size defined in each imported 3D scene, along with the per-mesh density multiplier (which is designed to be used when the same mesh is used at different scales). Lower values will result in faster bake times.
 			For example, doubling [member texel_scale] doubles the lightmap texture resolution for all objects [i]on each axis[/i], so it will [i]quadruple[/i] the texel count.
 			For example, doubling [member texel_scale] doubles the lightmap texture resolution for all objects [i]on each axis[/i], so it will [i]quadruple[/i] the texel count.

+ 14 - 0
doc/classes/LightmapGIData.xml

@@ -60,5 +60,19 @@
 		<member name="lightmap_textures" type="TextureLayered[]" setter="set_lightmap_textures" getter="get_lightmap_textures" default="[]">
 		<member name="lightmap_textures" type="TextureLayered[]" setter="set_lightmap_textures" getter="get_lightmap_textures" default="[]">
 			The lightmap atlas textures generated by the lightmapper.
 			The lightmap atlas textures generated by the lightmapper.
 		</member>
 		</member>
+		<member name="shadowmask_textures" type="TextureLayered[]" setter="set_shadowmask_textures" getter="get_shadowmask_textures" default="[]">
+			The shadowmask atlas textures generated by the lightmapper.
+		</member>
 	</members>
 	</members>
+	<constants>
+		<constant name="SHADOWMASK_MODE_NONE" value="0" enum="ShadowmaskMode">
+			Shadowmasking is disabled. No shadowmask texture will be created when baking lightmaps. Existing shadowmask textures will be removed during baking.
+		</constant>
+		<constant name="SHADOWMASK_MODE_REPLACE" value="1" enum="ShadowmaskMode">
+			Shadowmasking is enabled. Directional shadows that are outside the [member DirectionalLight3D.directional_shadow_max_distance] will be rendered using the shadowmask texture. Shadows that are inside the range will be rendered using real-time shadows exclusively. This mode allows for more precise real-time shadows up close, without the potential "smearing" effect that can occur when using lightmaps with a high texel size. The downside is that when the camera moves fast, the transition between the real-time light and shadowmask can be obvious. Also, objects that only have shadows baked in the shadowmask (and no real-time shadows) won't display any shadows up close.
+		</constant>
+		<constant name="SHADOWMASK_MODE_OVERLAY" value="2" enum="ShadowmaskMode">
+			Shadowmasking is enabled. Directional shadows will be rendered with real-time shadows overlaid on top of the shadowmask texture. This mode makes for smoother shadow transitions when the camera moves fast, at the cost of a potential smearing effect for directional shadows that are up close (due to the real-time shadow being mixed with a low-resolution shadowmask). Objects that only have shadows baked in the shadowmask (and no real-time shadows) will keep their shadows up close.
+		</constant>
+	</constants>
 </class>
 </class>

+ 53 - 5
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -2660,14 +2660,14 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
 				glBlitFramebuffer(0, 0, size.x, size.y,
 				glBlitFramebuffer(0, 0, size.x, size.y,
 						0, 0, size.x, size.y,
 						0, 0, size.x, size.y,
 						GL_COLOR_BUFFER_BIT, GL_NEAREST);
 						GL_COLOR_BUFFER_BIT, GL_NEAREST);
-				glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 5);
+				glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 6);
 				glBindTexture(GL_TEXTURE_2D, backbuffer);
 				glBindTexture(GL_TEXTURE_2D, backbuffer);
 			}
 			}
 			if (scene_state.used_depth_texture) {
 			if (scene_state.used_depth_texture) {
 				glBlitFramebuffer(0, 0, size.x, size.y,
 				glBlitFramebuffer(0, 0, size.x, size.y,
 						0, 0, size.x, size.y,
 						0, 0, size.x, size.y,
 						GL_DEPTH_BUFFER_BIT, GL_NEAREST);
 						GL_DEPTH_BUFFER_BIT, GL_NEAREST);
-				glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 6);
+				glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 7);
 				glBindTexture(GL_TEXTURE_2D, backbuffer_depth);
 				glBindTexture(GL_TEXTURE_2D, backbuffer_depth);
 			}
 			}
 		}
 		}
@@ -3245,8 +3245,28 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI;
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_OMNI;
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT;
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_SPOT;
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL;
 					spec_constants |= SceneShaderGLES3::DISABLE_LIGHT_DIRECTIONAL;
-					spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP;
 					spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE;
 					spec_constants |= SceneShaderGLES3::DISABLE_REFLECTION_PROBE;
+
+					bool disable_lightmaps = true;
+
+					// Additive directional passes may use shadowmasks, so enable lightmaps for them.
+					if (pass >= int32_t(inst->light_passes.size()) && inst->lightmap_instance.is_valid()) {
+						GLES3::LightmapInstance *li = GLES3::LightStorage::get_singleton()->get_lightmap_instance(inst->lightmap_instance);
+						GLES3::Lightmap *lm = GLES3::LightStorage::get_singleton()->get_lightmap(li->lightmap);
+
+						if (lm->shadowmask_mode != RS::SHADOWMASK_MODE_NONE) {
+							spec_constants |= SceneShaderGLES3::USE_LIGHTMAP;
+							disable_lightmaps = false;
+
+							if (lightmap_bicubic_upscale) {
+								spec_constants |= SceneShaderGLES3::LIGHTMAP_BICUBIC_FILTER;
+							}
+						}
+					}
+
+					if (disable_lightmaps) {
+						spec_constants |= SceneShaderGLES3::DISABLE_LIGHTMAP;
+					}
 				}
 				}
 
 
 				if (uses_additive_lighting) {
 				if (uses_additive_lighting) {
@@ -3341,6 +3361,33 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 						GLuint tex = GLES3::LightStorage::get_singleton()->directional_shadow_get_texture();
 						GLuint tex = GLES3::LightStorage::get_singleton()->directional_shadow_get_texture();
 						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 3);
 						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 3);
 						glBindTexture(GL_TEXTURE_2D, tex);
 						glBindTexture(GL_TEXTURE_2D, tex);
+
+						if (inst->lightmap_instance.is_valid()) {
+							// Use shadowmasks for directional light passes.
+							GLES3::LightmapInstance *li = GLES3::LightStorage::get_singleton()->get_lightmap_instance(inst->lightmap_instance);
+							GLES3::Lightmap *lm = GLES3::LightStorage::get_singleton()->get_lightmap(li->lightmap);
+
+							material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_SLICE, inst->lightmap_slice_index, shader->version, instance_variant, spec_constants);
+
+							Vector4 uv_scale(inst->lightmap_uv_scale.position.x, inst->lightmap_uv_scale.position.y, inst->lightmap_uv_scale.size.x, inst->lightmap_uv_scale.size.y);
+							material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_UV_SCALE, uv_scale, shader->version, instance_variant, spec_constants);
+
+							if (lightmap_bicubic_upscale) {
+								Vector2 light_texture_size(lm->light_texture_size.x, lm->light_texture_size.y);
+								material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_TEXTURE_SIZE, light_texture_size, shader->version, instance_variant, spec_constants);
+							}
+
+							material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::LIGHTMAP_SHADOWMASK_MODE, (uint32_t)lm->shadowmask_mode, shader->version, instance_variant, spec_constants);
+
+							if (lm->shadow_texture.is_valid()) {
+								tex = GLES3::TextureStorage::get_singleton()->texture_get_texid(lm->shadow_texture);
+							} else {
+								tex = GLES3::TextureStorage::get_singleton()->texture_get_texid(GLES3::TextureStorage::get_singleton()->texture_gl_get_default(GLES3::DEFAULT_GL_TEXTURE_2D_ARRAY_WHITE));
+							}
+
+							glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 5);
+							glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+						}
 					}
 					}
 				}
 				}
 
 
@@ -3399,6 +3446,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 							};
 							};
 							glUniformMatrix3fv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::LIGHTMAP_NORMAL_XFORM, shader->version, instance_variant, spec_constants), 1, GL_FALSE, matrix);
 							glUniformMatrix3fv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::LIGHTMAP_NORMAL_XFORM, shader->version, instance_variant, spec_constants), 1, GL_FALSE, matrix);
 						}
 						}
+
 					} else if (inst->lightmap_sh) {
 					} else if (inst->lightmap_sh) {
 						glUniform4fv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::LIGHTMAP_CAPTURES, shader->version, instance_variant, spec_constants), 9, reinterpret_cast<const GLfloat *>(inst->lightmap_sh->sh));
 						glUniform4fv(material_storage->shaders.scene_shader.version_get_uniform(SceneShaderGLES3::LIGHTMAP_CAPTURES, shader->version, instance_variant, spec_constants), 9, reinterpret_cast<const GLfloat *>(inst->lightmap_sh->sh));
 					}
 					}
@@ -3430,7 +3478,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[0], shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE1_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[0], shader->version, instance_variant, spec_constants);
 
 
-						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 7);
+						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 8);
 						glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[0]));
 						glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[0]));
 					}
 					}
 
 
@@ -3448,7 +3496,7 @@ void RasterizerSceneGLES3::_render_list_template(RenderListParameters *p_params,
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_AMBIENT_COLOR, probe->ambient_color * probe->ambient_color_energy, shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[1], shader->version, instance_variant, spec_constants);
 						material_storage->shaders.scene_shader.version_set_uniform(SceneShaderGLES3::REFPROBE2_LOCAL_MATRIX, inst->reflection_probes_local_transform_cache[1], shader->version, instance_variant, spec_constants);
 
 
-						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 8);
+						glActiveTexture(GL_TEXTURE0 + config->max_texture_image_units - 9);
 						glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[1]));
 						glBindTexture(GL_TEXTURE_CUBE_MAP, light_storage->reflection_probe_instance_get_texture(inst->reflection_probe_rid_cache[1]));
 
 
 						spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE;
 						spec_constants |= SceneShaderGLES3::SECOND_REFLECTION_PROBE;

+ 111 - 68
drivers/gles3/shaders/scene.glsl

@@ -809,10 +809,11 @@ void main() {
 2-radiance
 2-radiance
 3-shadow
 3-shadow
 4-lightmap textures
 4-lightmap textures
-5-screen
-6-depth
-7-reflection probe 1
-8-reflection probe 2
+5-shadowmask textures
+6-screen
+7-depth
+8-reflection probe 1
+9-reflection probe 2
 
 
 */
 */
 
 
@@ -887,7 +888,7 @@ uniform float refprobe1_intensity;
 uniform int refprobe1_ambient_mode;
 uniform int refprobe1_ambient_mode;
 uniform vec4 refprobe1_ambient_color;
 uniform vec4 refprobe1_ambient_color;
 
 
-uniform samplerCube refprobe1_texture; // texunit:-7
+uniform samplerCube refprobe1_texture; // texunit:-8
 
 
 #ifdef SECOND_REFLECTION_PROBE
 #ifdef SECOND_REFLECTION_PROBE
 
 
@@ -900,7 +901,7 @@ uniform float refprobe2_intensity;
 uniform int refprobe2_ambient_mode;
 uniform int refprobe2_ambient_mode;
 uniform vec4 refprobe2_ambient_color;
 uniform vec4 refprobe2_ambient_color;
 
 
-uniform samplerCube refprobe2_texture; // texunit:-8
+uniform samplerCube refprobe2_texture; // texunit:-9
 
 
 #endif // SECOND_REFLECTION_PROBE
 #endif // SECOND_REFLECTION_PROBE
 
 
@@ -1170,9 +1171,16 @@ float sample_shadow(highp sampler2DShadow shadow, float shadow_pixel_size, vec4
 #ifndef DISABLE_LIGHTMAP
 #ifndef DISABLE_LIGHTMAP
 #ifdef USE_LIGHTMAP
 #ifdef USE_LIGHTMAP
 uniform mediump sampler2DArray lightmap_textures; //texunit:-4
 uniform mediump sampler2DArray lightmap_textures; //texunit:-4
+uniform lowp sampler2DArray shadowmask_textures; //texunit:-5
 uniform lowp uint lightmap_slice;
 uniform lowp uint lightmap_slice;
 uniform highp vec4 lightmap_uv_scale;
 uniform highp vec4 lightmap_uv_scale;
 uniform float lightmap_exposure_normalization;
 uniform float lightmap_exposure_normalization;
+uniform uint lightmap_shadowmask_mode;
+
+#define SHADOWMASK_MODE_NONE uint(0)
+#define SHADOWMASK_MODE_REPLACE uint(1)
+#define SHADOWMASK_MODE_OVERLAY uint(2)
+#define SHADOWMASK_MODE_ONLY uint(3)
 
 
 #ifdef LIGHTMAP_BICUBIC_FILTER
 #ifdef LIGHTMAP_BICUBIC_FILTER
 uniform highp vec2 lightmap_texture_size;
 uniform highp vec2 lightmap_texture_size;
@@ -1189,8 +1197,8 @@ uniform mediump vec4[9] lightmap_captures;
 #endif // !DISABLE_LIGHTMAP
 #endif // !DISABLE_LIGHTMAP
 
 
 #ifdef USE_MULTIVIEW
 #ifdef USE_MULTIVIEW
-uniform highp sampler2DArray depth_buffer; // texunit:-6
-uniform highp sampler2DArray color_buffer; // texunit:-5
+uniform highp sampler2DArray depth_buffer; // texunit:-7
+uniform highp sampler2DArray color_buffer; // texunit:-6
 vec3 multiview_uv(vec2 uv) {
 vec3 multiview_uv(vec2 uv) {
 	return vec3(uv, ViewIndex);
 	return vec3(uv, ViewIndex);
 }
 }
@@ -1198,8 +1206,8 @@ ivec3 multiview_uv(ivec2 uv) {
 	return ivec3(uv, int(ViewIndex));
 	return ivec3(uv, int(ViewIndex));
 }
 }
 #else
 #else
-uniform highp sampler2D depth_buffer; // texunit:-6
-uniform highp sampler2D color_buffer; // texunit:-5
+uniform highp sampler2D depth_buffer; // texunit:-7
+uniform highp sampler2D color_buffer; // texunit:-6
 vec2 multiview_uv(vec2 uv) {
 vec2 multiview_uv(vec2 uv) {
 	return uv;
 	return uv;
 }
 }
@@ -2278,111 +2286,146 @@ void main() {
 #if !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
 #if !defined(ADDITIVE_OMNI) && !defined(ADDITIVE_SPOT)
 
 
 #ifndef SHADOWS_DISABLED
 #ifndef SHADOWS_DISABLED
+// Baked shadowmasks
+#ifdef USE_LIGHTMAP
+	float shadowmask = 1.0f;
+
+	if (lightmap_shadowmask_mode != SHADOWMASK_MODE_NONE) {
+		vec3 uvw;
+		uvw.xy = uv2 * lightmap_uv_scale.zw + lightmap_uv_scale.xy;
+		uvw.z = float(lightmap_slice);
+
+#ifdef LIGHTMAP_BICUBIC_FILTER
+		shadowmask = textureArray_bicubic(shadowmask_textures, uvw, lightmap_texture_size).x;
+#else
+		shadowmask = textureLod(shadowmask_textures, uvw, 0.0).x;
+#endif
+	}
+#endif //USE_LIGHTMAP
+
+	float directional_shadow = 1.0;
+
+#ifdef USE_LIGHTMAP
+	if (lightmap_shadowmask_mode != SHADOWMASK_MODE_ONLY) {
+#endif
 
 
 // Orthogonal shadows
 // Orthogonal shadows
 #if !defined(LIGHT_USE_PSSM2) && !defined(LIGHT_USE_PSSM4)
 #if !defined(LIGHT_USE_PSSM2) && !defined(LIGHT_USE_PSSM4)
-	float directional_shadow = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
+		directional_shadow = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
 #endif // !defined(LIGHT_USE_PSSM2) && !defined(LIGHT_USE_PSSM4)
 #endif // !defined(LIGHT_USE_PSSM2) && !defined(LIGHT_USE_PSSM4)
 
 
 // PSSM2 shadows
 // PSSM2 shadows
 #ifdef LIGHT_USE_PSSM2
 #ifdef LIGHT_USE_PSSM2
-	float depth_z = -vertex.z;
-	vec4 light_split_offsets = directional_shadows[directional_shadow_index].shadow_split_offsets;
-	//take advantage of prefetch
-	float shadow1 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
-	float shadow2 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord2);
-	float directional_shadow = 1.0;
+		float depth_z = -vertex.z;
+		vec4 light_split_offsets = directional_shadows[directional_shadow_index].shadow_split_offsets;
+		//take advantage of prefetch
+		float shadow1 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
+		float shadow2 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord2);
 
 
-	if (depth_z < light_split_offsets.y) {
+		if (depth_z < light_split_offsets.y) {
 
 
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-		float directional_shadow2 = 1.0;
-		float pssm_blend = 0.0;
-		bool use_blend = true;
+			float directional_shadow2 = 1.0;
+			float pssm_blend = 0.0;
+			bool use_blend = true;
 #endif
 #endif
-		if (depth_z < light_split_offsets.x) {
-			directional_shadow = shadow1;
+			if (depth_z < light_split_offsets.x) {
+				directional_shadow = shadow1;
 
 
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-			directional_shadow2 = shadow2;
-			pssm_blend = smoothstep(0.0, light_split_offsets.x, depth_z);
+				directional_shadow2 = shadow2;
+				pssm_blend = smoothstep(0.0, light_split_offsets.x, depth_z);
 #endif
 #endif
-		} else {
-			directional_shadow = shadow2;
+			} else {
+				directional_shadow = shadow2;
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-			use_blend = false;
+				use_blend = false;
 #endif
 #endif
-		}
+			}
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-		if (use_blend) {
-			directional_shadow = mix(directional_shadow, directional_shadow2, pssm_blend);
-		}
+			if (use_blend) {
+				directional_shadow = mix(directional_shadow, directional_shadow2, pssm_blend);
+			}
 #endif
 #endif
-	}
+		}
 
 
 #endif //LIGHT_USE_PSSM2
 #endif //LIGHT_USE_PSSM2
 // PSSM4 shadows
 // PSSM4 shadows
 #ifdef LIGHT_USE_PSSM4
 #ifdef LIGHT_USE_PSSM4
-	float depth_z = -vertex.z;
-	vec4 light_split_offsets = directional_shadows[directional_shadow_index].shadow_split_offsets;
-
-	float shadow1 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
-	float shadow2 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord2);
-	float shadow3 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord3);
-	float shadow4 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord4);
-	float directional_shadow = 1.0;
+		float depth_z = -vertex.z;
+		vec4 light_split_offsets = directional_shadows[directional_shadow_index].shadow_split_offsets;
 
 
-	if (depth_z < light_split_offsets.w) {
+		float shadow1 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord);
+		float shadow2 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord2);
+		float shadow3 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord3);
+		float shadow4 = sample_shadow(directional_shadow_atlas, directional_shadows[directional_shadow_index].shadow_atlas_pixel_size, shadow_coord4);
 
 
+		if (depth_z < light_split_offsets.w) {
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-		float directional_shadow2 = 1.0;
-		float pssm_blend = 0.0;
-		bool use_blend = true;
+			float directional_shadow2 = 1.0;
+			float pssm_blend = 0.0;
+			bool use_blend = true;
 #endif
 #endif
-		if (depth_z < light_split_offsets.y) {
-			if (depth_z < light_split_offsets.x) {
-				directional_shadow = shadow1;
+			if (depth_z < light_split_offsets.y) {
+				if (depth_z < light_split_offsets.x) {
+					directional_shadow = shadow1;
 
 
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-				directional_shadow2 = shadow2;
+					directional_shadow2 = shadow2;
 
 
-				pssm_blend = smoothstep(0.0, light_split_offsets.x, depth_z);
+					pssm_blend = smoothstep(0.0, light_split_offsets.x, depth_z);
 #endif
 #endif
-			} else {
-				directional_shadow = shadow2;
+				} else {
+					directional_shadow = shadow2;
 
 
 #ifdef LIGHT_USE_PSSM_BLEND
 #ifdef LIGHT_USE_PSSM_BLEND
-				directional_shadow2 = shadow3;
+					directional_shadow2 = shadow3;
 
 
-				pssm_blend = smoothstep(light_split_offsets.x, light_split_offsets.y, depth_z);
+					pssm_blend = smoothstep(light_split_offsets.x, light_split_offsets.y, depth_z);
 #endif
 #endif
-			}
-		} else {
-			if (depth_z < light_split_offsets.z) {
-				directional_shadow = shadow3;
+				}
+			} else {
+				if (depth_z < light_split_offsets.z) {
+					directional_shadow = shadow3;
 
 
 #if defined(LIGHT_USE_PSSM_BLEND)
 #if defined(LIGHT_USE_PSSM_BLEND)
-				directional_shadow2 = shadow4;
-				pssm_blend = smoothstep(light_split_offsets.y, light_split_offsets.z, depth_z);
+					directional_shadow2 = shadow4;
+					pssm_blend = smoothstep(light_split_offsets.y, light_split_offsets.z, depth_z);
 #endif
 #endif
 
 
-			} else {
-				directional_shadow = shadow4;
+				} else {
+					directional_shadow = shadow4;
 
 
 #if defined(LIGHT_USE_PSSM_BLEND)
 #if defined(LIGHT_USE_PSSM_BLEND)
-				use_blend = false;
+					use_blend = false;
 #endif
 #endif
+				}
 			}
 			}
-		}
 #if defined(LIGHT_USE_PSSM_BLEND)
 #if defined(LIGHT_USE_PSSM_BLEND)
-		if (use_blend) {
-			directional_shadow = mix(directional_shadow, directional_shadow2, pssm_blend);
+			if (use_blend) {
+				directional_shadow = mix(directional_shadow, directional_shadow2, pssm_blend);
+			}
+#endif
 		}
 		}
+
+#endif //LIGHT_USE_PSSM4
+
+#ifdef USE_LIGHTMAP
+		if (lightmap_shadowmask_mode == SHADOWMASK_MODE_REPLACE) {
+			directional_shadow = mix(directional_shadow, shadowmask, smoothstep(directional_shadows[directional_shadow_index].fade_from, directional_shadows[directional_shadow_index].fade_to, vertex.z));
+		} else if (lightmap_shadowmask_mode == SHADOWMASK_MODE_OVERLAY) {
+			directional_shadow = shadowmask * mix(directional_shadow, 1.0, smoothstep(directional_shadows[directional_shadow_index].fade_from, directional_shadows[directional_shadow_index].fade_to, vertex.z));
+		} else {
 #endif
 #endif
+			directional_shadow = mix(directional_shadow, 1.0, smoothstep(directional_shadows[directional_shadow_index].fade_from, directional_shadows[directional_shadow_index].fade_to, vertex.z));
+#ifdef USE_LIGHTMAP
+		}
+
+	} else { // lightmap_shadowmask_mode == SHADOWMASK_MODE_ONLY
+		directional_shadow = shadowmask;
 	}
 	}
+#endif
 
 
-#endif //LIGHT_USE_PSSM4
-	directional_shadow = mix(directional_shadow, 1.0, smoothstep(directional_shadows[directional_shadow_index].fade_from, directional_shadows[directional_shadow_index].fade_to, vertex.z));
 	directional_shadow = mix(1.0, directional_shadow, directional_lights[directional_shadow_index].shadow_opacity);
 	directional_shadow = mix(1.0, directional_shadow, directional_lights[directional_shadow_index].shadow_opacity);
 
 
 #else
 #else

+ 27 - 0
drivers/gles3/storage/light_storage.cpp

@@ -1204,6 +1204,33 @@ float LightStorage::lightmap_get_probe_capture_update_speed() const {
 	return lightmap_probe_capture_update_speed;
 	return lightmap_probe_capture_update_speed;
 }
 }
 
 
+void LightStorage::lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) {
+	Lightmap *lightmap = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL(lightmap);
+	lightmap->shadow_texture = p_shadow;
+
+	GLuint tex = GLES3::TextureStorage::get_singleton()->texture_get_texid(lightmap->shadow_texture);
+	glBindTexture(GL_TEXTURE_2D_ARRAY, tex);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+	glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
+}
+
+RS::ShadowmaskMode LightStorage::lightmap_get_shadowmask_mode(RID p_lightmap) {
+	Lightmap *lightmap = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL_V(lightmap, RS::SHADOWMASK_MODE_NONE);
+
+	return lightmap->shadowmask_mode;
+}
+
+void LightStorage::lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) {
+	Lightmap *lightmap = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL(lightmap);
+	lightmap->shadowmask_mode = p_mode;
+}
+
 /* LIGHTMAP INSTANCE */
 /* LIGHTMAP INSTANCE */
 
 
 RID LightStorage::lightmap_instance_create(RID p_lightmap) {
 RID LightStorage::lightmap_instance_create(RID p_lightmap) {

+ 6 - 2
drivers/gles3/storage/light_storage.h

@@ -177,12 +177,14 @@ struct ReflectionProbeInstance {
 
 
 struct Lightmap {
 struct Lightmap {
 	RID light_texture;
 	RID light_texture;
+	RID shadow_texture;
 	bool uses_spherical_harmonics = false;
 	bool uses_spherical_harmonics = false;
 	bool interior = false;
 	bool interior = false;
 	AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
 	AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
 	float baked_exposure = 1.0;
 	float baked_exposure = 1.0;
 	Vector2i light_texture_size;
 	Vector2i light_texture_size;
 	int32_t array_index = -1; //unassigned
 	int32_t array_index = -1; //unassigned
+	RS::ShadowmaskMode shadowmask_mode = RS::SHADOWMASK_MODE_NONE;
 	PackedVector3Array points;
 	PackedVector3Array points;
 	PackedColorArray point_sh;
 	PackedColorArray point_sh;
 	PackedInt32Array tetrahedra;
 	PackedInt32Array tetrahedra;
@@ -231,8 +233,6 @@ private:
 	mutable RID_Owner<ReflectionProbeInstance> reflection_probe_instance_owner;
 	mutable RID_Owner<ReflectionProbeInstance> reflection_probe_instance_owner;
 
 
 	/* LIGHTMAP */
 	/* LIGHTMAP */
-
-	Vector<RID> lightmap_textures;
 	float lightmap_probe_capture_update_speed = 4;
 	float lightmap_probe_capture_update_speed = 4;
 
 
 	mutable RID_Owner<Lightmap, true> lightmap_owner;
 	mutable RID_Owner<Lightmap, true> lightmap_owner;
@@ -737,6 +737,10 @@ public:
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) override;
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) override;
 	virtual float lightmap_get_probe_capture_update_speed() const override;
 	virtual float lightmap_get_probe_capture_update_speed() const override;
 
 
+	virtual void lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) override;
+	virtual RS::ShadowmaskMode lightmap_get_shadowmask_mode(RID p_lightmap) override;
+	virtual void lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) override;
+
 	/* LIGHTMAP INSTANCE */
 	/* LIGHTMAP INSTANCE */
 
 
 	LightmapInstance *get_lightmap_instance(RID p_rid) { return lightmap_instance_owner.get_or_null(p_rid); }
 	LightmapInstance *get_lightmap_instance(RID p_rid) { return lightmap_instance_owner.get_or_null(p_rid); }

+ 142 - 33
modules/lightmapper_rd/lightmapper_rd.cpp

@@ -62,7 +62,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) {
 	mesh_instances.push_back(mi);
 	mesh_instances.push_back(mi);
 }
 }
 
 
-void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) {
+void LightmapperRD::add_directional_light(const String &p_name, bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) {
 	Light l;
 	Light l;
 	l.type = LIGHT_TYPE_DIRECTIONAL;
 	l.type = LIGHT_TYPE_DIRECTIONAL;
 	l.direction[0] = p_direction.x;
 	l.direction[0] = p_direction.x;
@@ -77,9 +77,10 @@ void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direct
 	l.size = Math::tan(Math::deg_to_rad(p_angular_distance));
 	l.size = Math::tan(Math::deg_to_rad(p_angular_distance));
 	l.shadow_blur = p_shadow_blur;
 	l.shadow_blur = p_shadow_blur;
 	lights.push_back(l);
 	lights.push_back(l);
+	light_names.push_back(p_name);
 }
 }
 
 
-void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) {
+void LightmapperRD::add_omni_light(const String &p_name, bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) {
 	Light l;
 	Light l;
 	l.type = LIGHT_TYPE_OMNI;
 	l.type = LIGHT_TYPE_OMNI;
 	l.position[0] = p_position.x;
 	l.position[0] = p_position.x;
@@ -96,9 +97,10 @@ void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, con
 	l.size = p_size;
 	l.size = p_size;
 	l.shadow_blur = p_shadow_blur;
 	l.shadow_blur = p_shadow_blur;
 	lights.push_back(l);
 	lights.push_back(l);
+	light_names.push_back(p_name);
 }
 }
 
 
-void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) {
+void LightmapperRD::add_spot_light(const String &p_name, bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) {
 	Light l;
 	Light l;
 	l.type = LIGHT_TYPE_SPOT;
 	l.type = LIGHT_TYPE_SPOT;
 	l.position[0] = p_position.x;
 	l.position[0] = p_position.x;
@@ -120,6 +122,7 @@ void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, con
 	l.size = p_size;
 	l.size = p_size;
 	l.shadow_blur = p_shadow_blur;
 	l.shadow_blur = p_shadow_blur;
 	lights.push_back(l);
 	lights.push_back(l);
+	light_names.push_back(p_name);
 }
 }
 
 
 void LightmapperRD::add_probe(const Vector3 &p_position) {
 void LightmapperRD::add_probe(const Vector3 &p_position) {
@@ -826,9 +829,9 @@ LightmapperRD::BakeError LightmapperRD::_pack_l1(RenderingDevice *rd, Ref<RDShad
 	return BAKE_OK;
 	return BAKE_OK;
 }
 }
 
 
-Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name) {
+Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name, bool p_shadowmask) {
 	Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
 	Vector<uint8_t> data = p_rd->texture_get_data(p_atlas_tex, p_index);
-	Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, Image::FORMAT_RGBAH, data);
+	Ref<Image> img = Image::create_from_data(p_atlas_size.width, p_atlas_size.height, false, p_shadowmask ? Image::FORMAT_RGBA8 : Image::FORMAT_RGBAH, data);
 	img->convert(Image::FORMAT_RGBF);
 	img->convert(Image::FORMAT_RGBF);
 	Vector<uint8_t> data_float = img->get_data();
 	Vector<uint8_t> data_float = img->get_data();
 
 
@@ -848,7 +851,7 @@ Error LightmapperRD::_store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_in
 	return OK;
 	return OK;
 }
 }
 
 
-Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
+Ref<Image> LightmapperRD::_read_pfm(const String &p_name, bool p_shadowmask) {
 	Error err = OK;
 	Error err = OK;
 	Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
 	Ref<FileAccess> file = FileAccess::open(p_name, FileAccess::READ, &err);
 	ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
 	ERR_FAIL_COND_V_MSG(err, Ref<Image>(), vformat("Can't load PFM at path: '%s'.", p_name));
@@ -881,23 +884,23 @@ Ref<Image> LightmapperRD::_read_pfm(const String &p_name) {
 	}
 	}
 #endif
 #endif
 	Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
 	Ref<Image> img = Image::create_from_data(new_width, new_height, false, Image::FORMAT_RGBF, new_data);
-	img->convert(Image::FORMAT_RGBAH);
+	img->convert(p_shadowmask ? Image::FORMAT_RGBA8 : Image::FORMAT_RGBAH);
 	return img;
 	return img;
 }
 }
 
 
-LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe) {
+LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, bool p_shadowmask, const String &p_exe) {
 	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 	Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 
 
 	for (int i = 0; i < p_atlas_slices; i++) {
 	for (int i = 0; i < p_atlas_slices; i++) {
 		String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
 		String fname_norm_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_norm_%d.pfm", i));
-		_store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in);
+		_store_pfm(p_rd, p_source_normal_tex, i, p_atlas_size, fname_norm_in, false);
 
 
 		for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
 		for (int j = 0; j < (p_bake_sh ? 4 : 1); j++) {
 			int index = i * (p_bake_sh ? 4 : 1) + j;
 			int index = i * (p_bake_sh ? 4 : 1) + j;
 			String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
 			String fname_light_in = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_light_%d.pfm", index));
 			String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
 			String fname_out = EditorPaths::get_singleton()->get_cache_dir().path_join(vformat("temp_denoised_%d.pfm", index));
 
 
-			_store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in);
+			_store_pfm(p_rd, p_source_light_tex, index, p_atlas_size, fname_light_in, p_shadowmask);
 
 
 			List<String> args;
 			List<String> args;
 			args.push_back("--device");
 			args.push_back("--device");
@@ -906,7 +909,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
 			args.push_back("--filter");
 			args.push_back("--filter");
 			args.push_back("RTLightmap");
 			args.push_back("RTLightmap");
 
 
-			args.push_back("--hdr");
+			args.push_back(p_shadowmask ? "--ldr" : "--hdr");
 			args.push_back(fname_light_in);
 			args.push_back(fname_light_in);
 
 
 			args.push_back("--nrm");
 			args.push_back("--nrm");
@@ -928,7 +931,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise_oidn(RenderingDevice *p_rd, RID
 				ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat("OIDN denoiser failed, return code: %d", exitcode));
 				ERR_FAIL_V_MSG(BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES, vformat("OIDN denoiser failed, return code: %d", exitcode));
 			}
 			}
 
 
-			Ref<Image> img = _read_pfm(fname_out);
+			Ref<Image> img = _read_pfm(fname_out, p_shadowmask);
 			da->remove(fname_out);
 			da->remove(fname_out);
 
 
 			ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
 			ERR_FAIL_COND_V(img.is_null(), BAKE_ERROR_LIGHTMAP_CANT_PRE_BAKE_MESHES);
@@ -1029,7 +1032,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
 	return BAKE_OK;
 	return BAKE_OK;
 }
 }
 
 
-LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &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, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function, void *p_bake_userdata, float p_exposure_normalization) {
 	int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
 	int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
 	String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
 	String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");
 
 
@@ -1050,7 +1053,8 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 	if (p_step_function) {
 	if (p_step_function) {
 		p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
 		p_step_function(0.0, RTR("Begin Bake"), p_bake_userdata, true);
 	}
 	}
-	bake_textures.clear();
+	lightmap_textures.clear();
+	shadowmask_textures.clear();
 	int grid_size = 128;
 	int grid_size = 128;
 
 
 	/* STEP 1: Fetch material textures and compute the bounds */
 	/* STEP 1: Fetch material textures and compute the bounds */
@@ -1066,6 +1070,35 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 		return bake_error;
 		return bake_error;
 	}
 	}
 
 
+	// The index of the directional light used for shadowmasking.
+	int shadowmask_light_idx = -1;
+
+	// If there are no valid directional lights for shadowmasking, the entire
+	// scene would be shadowed and this saves baking time.
+	if (p_bake_shadowmask) {
+		int shadowmask_lights_count = 0;
+
+		for (int i = 0; i < lights.size(); i++) {
+			if (lights[i].type == LightType::LIGHT_TYPE_DIRECTIONAL && !lights[i].static_bake) {
+				if (shadowmask_light_idx < 0) {
+					shadowmask_light_idx = i;
+				}
+
+				shadowmask_lights_count += 1;
+			}
+		}
+
+		if (shadowmask_light_idx < 0) {
+			p_bake_shadowmask = false;
+			WARN_PRINT("Shadowmask disabled: no directional light with their bake mode set to dynamic exists.");
+
+		} else if (shadowmask_lights_count > 1) {
+			WARN_PRINT(
+					vformat("%d directional lights detected for shadowmask baking. Only %s will be used.",
+							shadowmask_lights_count, light_names[shadowmask_light_idx]));
+		}
+	}
+
 #ifdef DEBUG_TEXTURES
 #ifdef DEBUG_TEXTURES
 	for (int i = 0; i < atlas_slices; i++) {
 	for (int i = 0; i < atlas_slices; i++) {
 		albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png");
 		albedo_images[i]->save_png("res://0_albedo_" + itos(i) + ".png");
@@ -1119,17 +1152,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 	RID light_accum_tex;
 	RID light_accum_tex;
 	RID light_accum_tex2;
 	RID light_accum_tex2;
 	RID light_environment_tex;
 	RID light_environment_tex;
-
-#define FREE_TEXTURES             \
-	rd->free(albedo_array_tex);   \
-	rd->free(emission_array_tex); \
-	rd->free(normal_tex);         \
-	rd->free(position_tex);       \
-	rd->free(unocclude_tex);      \
-	rd->free(light_source_tex);   \
-	rd->free(light_accum_tex2);   \
-	rd->free(light_accum_tex);    \
-	rd->free(light_environment_tex);
+	RID shadowmask_tex;
+	RID shadowmask_tex2;
+
+#define FREE_TEXTURES                \
+	rd->free(albedo_array_tex);      \
+	rd->free(emission_array_tex);    \
+	rd->free(normal_tex);            \
+	rd->free(position_tex);          \
+	rd->free(unocclude_tex);         \
+	rd->free(light_source_tex);      \
+	rd->free(light_accum_tex2);      \
+	rd->free(light_accum_tex);       \
+	rd->free(light_environment_tex); \
+	if (p_bake_shadowmask) {         \
+		rd->free(shadowmask_tex);    \
+		rd->free(shadowmask_tex2);   \
+	}
 
 
 	{ // create all textures
 	{ // create all textures
 
 
@@ -1161,9 +1200,22 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 		position_tex = rd->texture_create(tf, RD::TextureView());
 		position_tex = rd->texture_create(tf, RD::TextureView());
 		unocclude_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.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;
 
 
+		// shadowmask
+		if (p_bake_shadowmask) {
+			tf.format = RD::DATA_FORMAT_R8G8B8A8_UNORM;
+
+			shadowmask_tex = rd->texture_create(tf, RD::TextureView());
+			rd->texture_clear(shadowmask_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
+
+			shadowmask_tex2 = rd->texture_create(tf, RD::TextureView());
+			rd->texture_clear(shadowmask_tex2, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
+		}
+
+		// lightmap
+		tf.format = RD::DATA_FORMAT_R16G16B16A16_SFLOAT;
+
 		light_source_tex = rd->texture_create(tf, RD::TextureView());
 		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);
 		rd->texture_clear(light_source_tex, Color(0, 0, 0, 0), 0, 1, 0, atlas_slices);
 
 
@@ -1266,6 +1318,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 	bake_parameters.exposure_normalization = p_exposure_normalization;
 	bake_parameters.exposure_normalization = p_exposure_normalization;
 	bake_parameters.bounces = p_bounces;
 	bake_parameters.bounces = p_bounces;
 	bake_parameters.bounce_indirect_energy = p_bounce_indirect_energy;
 	bake_parameters.bounce_indirect_energy = p_bounce_indirect_energy;
+	bake_parameters.shadowmask_light_idx = shadowmask_light_idx;
 
 
 	bake_parameters_buffer = rd->uniform_buffer_create(sizeof(BakeParameters));
 	bake_parameters_buffer = rd->uniform_buffer_create(sizeof(BakeParameters));
 	rd->buffer_update(bake_parameters_buffer, 0, sizeof(BakeParameters), &bake_parameters);
 	rd->buffer_update(bake_parameters_buffer, 0, sizeof(BakeParameters), &bake_parameters);
@@ -1463,6 +1516,10 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 		defines += "\n#define USE_LIGHT_TEXTURE_FOR_BOUNCES\n";
 		defines += "\n#define USE_LIGHT_TEXTURE_FOR_BOUNCES\n";
 	}
 	}
 
 
+	if (p_bake_shadowmask) {
+		defines += "\n#define USE_SHADOWMASK\n";
+	}
+
 	compute_shader.instantiate();
 	compute_shader.instantiate();
 	err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, defines);
 	err = compute_shader->parse_versions_from_text(lm_compute_shader_glsl, defines);
 	if (err != OK) {
 	if (err != OK) {
@@ -1634,6 +1691,14 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 				u.append_id(light_accum_tex);
 				u.append_id(light_accum_tex);
 				uniforms.push_back(u);
 				uniforms.push_back(u);
 			}
 			}
+
+			if (p_bake_shadowmask) {
+				RD::Uniform u;
+				u.uniform_type = RD::UNIFORM_TYPE_IMAGE;
+				u.binding = 5;
+				u.append_id(shadowmask_tex);
+				uniforms.push_back(u);
+			}
 		}
 		}
 
 
 		RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
 		RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1);
@@ -1945,7 +2010,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 			BakeError error;
 			BakeError error;
 			if (denoiser == 1) {
 			if (denoiser == 1) {
 				// OIDN (external).
 				// OIDN (external).
-				error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, oidn_path);
+				error = _denoise_oidn(rd, light_accum_tex, normal_tex, light_accum_tex, atlas_size, atlas_slices, p_bake_sh, false, oidn_path);
 			} else {
 			} else {
 				// JNLM (built-in).
 				// JNLM (built-in).
 				SWAP(light_accum_tex, light_accum_tex2);
 				SWAP(light_accum_tex, light_accum_tex2);
@@ -1955,14 +2020,39 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 				return error;
 				return error;
 			}
 			}
 		}
 		}
+
+		if (p_bake_shadowmask) {
+			BakeError error;
+			if (denoiser == 1) {
+				// OIDN (external).
+				error = _denoise_oidn(rd, shadowmask_tex, normal_tex, shadowmask_tex, atlas_size, atlas_slices, false, true, oidn_path);
+			} else {
+				// JNLM (built-in).
+				SWAP(shadowmask_tex, shadowmask_tex2);
+				error = _denoise(rd, compute_shader, compute_base_uniform_set, push_constant, shadowmask_tex2, normal_tex, shadowmask_tex, p_denoiser_strength, p_denoiser_range, atlas_size, atlas_slices, false, p_step_function, p_bake_userdata);
+			}
+			if (unlikely(error != BAKE_OK)) {
+				return error;
+			}
+		}
 	}
 	}
 
 
+	/* DILATE */
+
 	{
 	{
 		SWAP(light_accum_tex, light_accum_tex2);
 		SWAP(light_accum_tex, light_accum_tex2);
 		BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
 		BakeError error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, light_accum_tex2, light_accum_tex, atlas_size, atlas_slices * (p_bake_sh ? 4 : 1));
 		if (unlikely(error != BAKE_OK)) {
 		if (unlikely(error != BAKE_OK)) {
 			return error;
 			return error;
 		}
 		}
+
+		if (p_bake_shadowmask) {
+			SWAP(shadowmask_tex, shadowmask_tex2);
+			error = _dilate(rd, compute_shader, compute_base_uniform_set, push_constant, shadowmask_tex2, shadowmask_tex, atlas_size, atlas_slices);
+			if (unlikely(error != BAKE_OK)) {
+				return error;
+			}
+		}
 	}
 	}
 
 
 #ifdef DEBUG_TEXTURES
 #ifdef DEBUG_TEXTURES
@@ -2139,6 +2229,7 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 		img->save_exr("res://5_blendseams" + itos(i) + ".exr", false);
 		img->save_exr("res://5_blendseams" + itos(i) + ".exr", false);
 	}
 	}
 #endif
 #endif
+
 	if (p_step_function) {
 	if (p_step_function) {
 		p_step_function(0.9, RTR("Retrieving textures"), p_bake_userdata, true);
 		p_step_function(0.9, RTR("Retrieving textures"), p_bake_userdata, true);
 	}
 	}
@@ -2147,7 +2238,16 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 		Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
 		Vector<uint8_t> s = rd->texture_get_data(light_accum_tex, i);
 		Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
 		Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBAH, s);
 		img->convert(Image::FORMAT_RGBH); //remove alpha
 		img->convert(Image::FORMAT_RGBH); //remove alpha
-		bake_textures.push_back(img);
+		lightmap_textures.push_back(img);
+	}
+
+	if (p_bake_shadowmask) {
+		for (int i = 0; i < atlas_slices; i++) {
+			Vector<uint8_t> s = rd->texture_get_data(shadowmask_tex, i);
+			Ref<Image> img = Image::create_from_data(atlas_size.width, atlas_size.height, false, Image::FORMAT_RGBA8, s);
+			img->convert(Image::FORMAT_R8);
+			shadowmask_textures.push_back(img);
+		}
 	}
 	}
 
 
 	if (probe_positions.size() > 0) {
 	if (probe_positions.size() > 0) {
@@ -2180,12 +2280,21 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
 }
 }
 
 
 int LightmapperRD::get_bake_texture_count() const {
 int LightmapperRD::get_bake_texture_count() const {
-	return bake_textures.size();
+	return lightmap_textures.size();
 }
 }
 
 
 Ref<Image> LightmapperRD::get_bake_texture(int p_index) const {
 Ref<Image> LightmapperRD::get_bake_texture(int p_index) const {
-	ERR_FAIL_INDEX_V(p_index, bake_textures.size(), Ref<Image>());
-	return bake_textures[p_index];
+	ERR_FAIL_INDEX_V(p_index, lightmap_textures.size(), Ref<Image>());
+	return lightmap_textures[p_index];
+}
+
+int LightmapperRD::get_shadowmask_texture_count() const {
+	return shadowmask_textures.size();
+}
+
+Ref<Image> LightmapperRD::get_shadowmask_texture(int p_index) const {
+	ERR_FAIL_INDEX_V(p_index, shadowmask_textures.size(), Ref<Image>());
+	return shadowmask_textures[p_index];
 }
 }
 
 
 int LightmapperRD::get_bake_mesh_count() const {
 int LightmapperRD::get_bake_mesh_count() const {
@@ -2198,9 +2307,9 @@ Variant LightmapperRD::get_bake_mesh_userdata(int p_index) const {
 }
 }
 
 
 Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
 Rect2 LightmapperRD::get_bake_mesh_uv_scale(int p_index) const {
-	ERR_FAIL_COND_V(bake_textures.is_empty(), Rect2());
+	ERR_FAIL_COND_V(lightmap_textures.is_empty(), Rect2());
 	Rect2 uv_ofs;
 	Rect2 uv_ofs;
-	Vector2 atlas_size = Vector2(bake_textures[0]->get_width(), bake_textures[0]->get_height());
+	Vector2 atlas_size = Vector2(lightmap_textures[0]->get_width(), lightmap_textures[0]->get_height());
 	uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
 	uv_ofs.position = Vector2(mesh_instances[p_index].offset) / atlas_size;
 	uv_ofs.size = Vector2(mesh_instances[p_index].data.albedo_on_uv2->get_width(), mesh_instances[p_index].data.albedo_on_uv2->get_height()) / atlas_size;
 	uv_ofs.size = Vector2(mesh_instances[p_index].data.albedo_on_uv2->get_width(), mesh_instances[p_index].data.albedo_on_uv2->get_height()) / atlas_size;
 	return uv_ofs;
 	return uv_ofs;

+ 14 - 9
modules/lightmapper_rd/lightmapper_rd.h

@@ -57,7 +57,8 @@ class LightmapperRD : public Lightmapper {
 		uint32_t bounces = 0;
 		uint32_t bounces = 0;
 
 
 		float bounce_indirect_energy = 0.0f;
 		float bounce_indirect_energy = 0.0f;
-		uint32_t pad[3] = {};
+		int shadowmask_light_idx = 0;
+		uint32_t pad[2] = {};
 	};
 	};
 
 
 	struct MeshInstance {
 	struct MeshInstance {
@@ -202,6 +203,7 @@ class LightmapperRD : public Lightmapper {
 	Vector<MeshInstance> mesh_instances;
 	Vector<MeshInstance> mesh_instances;
 
 
 	Vector<Light> lights;
 	Vector<Light> lights;
+	Vector<String> light_names;
 
 
 	struct TriangleSort {
 	struct TriangleSort {
 		uint32_t cell_index = 0;
 		uint32_t cell_index = 0;
@@ -253,7 +255,8 @@ class LightmapperRD : public Lightmapper {
 		uint32_t pad = 0;
 		uint32_t pad = 0;
 	};
 	};
 
 
-	Vector<Ref<Image>> bake_textures;
+	Vector<Ref<Image>> lightmap_textures;
+	Vector<Ref<Image>> shadowmask_textures;
 	Vector<Color> probe_values;
 	Vector<Color> probe_values;
 
 
 	struct DenoiseParams {
 	struct DenoiseParams {
@@ -275,20 +278,22 @@ class LightmapperRD : public Lightmapper {
 	BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata);
 	BakeError _denoise(RenderingDevice *p_rd, Ref<RDShaderFile> &p_compute_shader, const RID &p_compute_base_uniform_set, PushConstant &p_push_constant, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, float p_denoiser_strength, int p_denoiser_range, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, BakeStepFunc p_step_function, void *p_bake_userdata);
 	BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
 	BakeError _pack_l1(RenderingDevice *rd, Ref<RDShaderFile> &compute_shader, RID &compute_base_uniform_set, PushConstant &push_constant, RID &source_light_tex, RID &dest_light_tex, const Size2i &atlas_size, int atlas_slices);
 
 
-	Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name);
-	Ref<Image> _read_pfm(const String &p_name);
-	BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, const String &p_exe);
+	Error _store_pfm(RenderingDevice *p_rd, RID p_atlas_tex, int p_index, const Size2i &p_atlas_size, const String &p_name, bool p_shadowmask);
+	Ref<Image> _read_pfm(const String &p_name, bool p_shadowmask);
+	BakeError _denoise_oidn(RenderingDevice *p_rd, RID p_source_light_tex, RID p_source_normal_tex, RID p_dest_light_tex, const Size2i &p_atlas_size, int p_atlas_slices, bool p_bake_sh, bool p_shadowmask, const String &p_exe);
 
 
 public:
 public:
 	virtual void add_mesh(const MeshData &p_mesh) override;
 	virtual void add_mesh(const MeshData &p_mesh) override;
-	virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) override;
-	virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_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_indirect_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_directional_light(const String &p_name, bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) override;
+	virtual void add_omni_light(const String &p_name, bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override;
+	virtual void add_spot_light(const String &p_name, bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_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 void add_probe(const Vector3 &p_position) override;
-	virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &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, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &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;
 	int get_bake_texture_count() const override;
 	Ref<Image> get_bake_texture(int p_index) const override;
 	Ref<Image> get_bake_texture(int p_index) const override;
+	int get_shadowmask_texture_count() const override;
+	Ref<Image> get_shadowmask_texture(int p_index) const override;
 	int get_bake_mesh_count() const override;
 	int get_bake_mesh_count() const override;
 	Variant get_bake_mesh_userdata(int p_index) const override;
 	Variant get_bake_mesh_userdata(int p_index) const override;
 	Rect2 get_bake_mesh_uv_scale(int p_index) const override;
 	Rect2 get_bake_mesh_uv_scale(int p_index) const override;

+ 3 - 0
modules/lightmapper_rd/lm_common_inc.glsl

@@ -17,6 +17,9 @@ layout(set = 0, binding = 0) uniform BakeParameters {
 	uint bounces;
 	uint bounces;
 
 
 	float bounce_indirect_energy;
 	float bounce_indirect_energy;
+	int shadowmask_light_idx;
+	uint pad0;
+	uint pad1;
 }
 }
 bake_params;
 bake_params;
 
 

+ 25 - 6
modules/lightmapper_rd/lm_compute.glsl

@@ -60,7 +60,9 @@ layout(rgba16f, set = 1, binding = 4) uniform restrict image2DArray accum_light;
 
 
 #endif
 #endif
 
 
-#ifdef MODE_BOUNCE_LIGHT
+#if defined(MODE_DIRECT_LIGHT) && defined(USE_SHADOWMASK)
+layout(rgba8, set = 1, binding = 5) uniform restrict writeonly image2DArray shadowmask;
+#elif defined(MODE_BOUNCE_LIGHT)
 layout(set = 1, binding = 5) uniform texture2D environment;
 layout(set = 1, binding = 5) uniform texture2D environment;
 #endif
 #endif
 
 
@@ -389,8 +391,9 @@ vec2 get_vogel_disk(float p_i, float p_rotation, float p_sample_count_sqrt) {
 	return vec2(cos(theta), sin(theta)) * r;
 	return vec2(cos(theta), sin(theta)) * r;
 }
 }
 
 
-void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size) {
+void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool p_soft_shadowing, out vec3 r_light, out vec3 r_light_dir, inout uint r_noise, float p_texel_size, out float r_shadow) {
 	r_light = vec3(0.0f);
 	r_light = vec3(0.0f);
+	r_shadow = 0.0f;
 
 
 	vec3 light_pos;
 	vec3 light_pos;
 	float dist;
 	float dist;
@@ -507,6 +510,7 @@ void trace_direct_light(vec3 p_position, vec3 p_normal, uint p_light_index, bool
 		}
 		}
 	}
 	}
 
 
+	r_shadow = penumbra;
 	r_light = light_data.color * light_data.energy * attenuation * penumbra;
 	r_light = light_data.color * light_data.energy * attenuation * penumbra;
 }
 }
 
 
@@ -556,7 +560,8 @@ vec3 trace_indirect_light(vec3 p_position, vec3 p_ray_dir, inout uint r_noise, f
 			for (uint i = 0; i < bake_params.light_count; i++) {
 			for (uint i = 0; i < bake_params.light_count; i++) {
 				vec3 light;
 				vec3 light;
 				vec3 light_dir;
 				vec3 light_dir;
-				trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size);
+				float shadow;
+				trace_direct_light(position, normal, i, false, light, light_dir, r_noise, p_texel_size, shadow);
 				direct_light += light * lights.data[i].indirect_energy;
 				direct_light += light * lights.data[i].indirect_energy;
 			}
 			}
 
 
@@ -614,7 +619,6 @@ void main() {
 #endif
 #endif
 
 
 #ifdef MODE_DIRECT_LIGHT
 #ifdef MODE_DIRECT_LIGHT
-
 	vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
 	vec3 normal = texelFetch(sampler2DArray(source_normal, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0).xyz;
 	if (length(normal) < 0.5) {
 	if (length(normal) < 0.5) {
 		return; //empty texel, no process
 		return; //empty texel, no process
@@ -631,6 +635,10 @@ void main() {
 	vec3 light_for_texture = vec3(0.0);
 	vec3 light_for_texture = vec3(0.0);
 	vec3 light_for_bounces = vec3(0.0);
 	vec3 light_for_bounces = vec3(0.0);
 
 
+#ifdef USE_SHADOWMASK
+	float shadowmask_value = 0.0f;
+#endif
+
 #ifdef USE_SH_LIGHTMAPS
 #ifdef USE_SH_LIGHTMAPS
 	vec4 sh_accum[4] = vec4[](
 	vec4 sh_accum[4] = vec4[](
 			vec4(0.0, 0.0, 0.0, 1.0),
 			vec4(0.0, 0.0, 0.0, 1.0),
@@ -644,7 +652,8 @@ void main() {
 	for (uint i = 0; i < bake_params.light_count; i++) {
 	for (uint i = 0; i < bake_params.light_count; i++) {
 		vec3 light;
 		vec3 light;
 		vec3 light_dir;
 		vec3 light_dir;
-		trace_direct_light(position, normal, i, true, light, light_dir, noise, texel_size_world_space);
+		float shadow;
+		trace_direct_light(position, normal, i, true, light, light_dir, noise, texel_size_world_space, shadow);
 
 
 		if (lights.data[i].static_bake) {
 		if (lights.data[i].static_bake) {
 			light_for_texture += light;
 			light_for_texture += light;
@@ -669,6 +678,12 @@ void main() {
 		}
 		}
 
 
 		light_for_bounces += light * lights.data[i].indirect_energy;
 		light_for_bounces += light * lights.data[i].indirect_energy;
+
+#ifdef USE_SHADOWMASK
+		if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL && i == bake_params.shadowmask_light_idx) {
+			shadowmask_value = max(shadowmask_value, shadow);
+		}
+#endif
 	}
 	}
 
 
 	light_for_bounces *= bake_params.exposure_normalization;
 	light_for_bounces *= bake_params.exposure_normalization;
@@ -685,6 +700,10 @@ void main() {
 	imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_texture, 1.0));
 	imageStore(accum_light, ivec3(atlas_pos, params.atlas_slice), vec4(light_for_texture, 1.0));
 #endif
 #endif
 
 
+#ifdef USE_SHADOWMASK
+	imageStore(shadowmask, ivec3(atlas_pos, params.atlas_slice), vec4(shadowmask_value, shadowmask_value, shadowmask_value, 1.0));
+#endif
+
 #endif
 #endif
 
 
 #ifdef MODE_BOUNCE_LIGHT
 #ifdef MODE_BOUNCE_LIGHT
@@ -850,7 +869,7 @@ void main() {
 
 
 #endif
 #endif
 
 
-#ifdef MODE_DILATE
+#if defined(MODE_DILATE)
 
 
 	vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0);
 	vec4 c = texelFetch(sampler2DArray(source_light, linear_sampler), ivec3(atlas_pos, params.atlas_slice), 0);
 	//sides first, as they are closer
 	//sides first, as they are closer

+ 121 - 11
scene/3d/lightmap_gi.cpp

@@ -130,6 +130,52 @@ TypedArray<TextureLayered> LightmapGIData::get_lightmap_textures() const {
 	return storage_light_textures;
 	return storage_light_textures;
 }
 }
 
 
+void LightmapGIData::set_shadowmask_textures(const TypedArray<TextureLayered> &p_data) {
+	storage_shadowmask_textures = p_data;
+
+	if (p_data.is_empty()) {
+		combined_shadowmask_texture = Ref<TextureLayered>();
+		_reset_shadowmask_textures();
+		return;
+	}
+
+	if (p_data.size() == 1) {
+		combined_shadowmask_texture = p_data[0];
+
+	} else {
+		Vector<Ref<Image>> images;
+		for (int i = 0; i < p_data.size(); i++) {
+			Ref<TextureLayered> texture = p_data[i];
+			ERR_FAIL_COND_MSG(texture.is_null(), vformat("Invalid TextureLayered at index %d.", i));
+			for (int j = 0; j < texture->get_layers(); j++) {
+				images.push_back(texture->get_layer_data(j));
+			}
+		}
+
+		Ref<Texture2DArray> combined_texture;
+		combined_texture.instantiate();
+
+		combined_texture->create_from_images(images);
+		combined_shadowmask_texture = combined_texture;
+	}
+
+	_reset_shadowmask_textures();
+}
+
+TypedArray<TextureLayered> LightmapGIData::get_shadowmask_textures() const {
+	return storage_shadowmask_textures;
+}
+
+void LightmapGIData::clear_shadowmask_textures() {
+	RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, RID());
+	storage_shadowmask_textures.clear();
+	combined_shadowmask_texture.unref();
+}
+
+bool LightmapGIData::has_shadowmask_textures() {
+	return !storage_shadowmask_textures.is_empty() && combined_shadowmask_texture.is_valid();
+}
+
 RID LightmapGIData::get_rid() const {
 RID LightmapGIData::get_rid() const {
 	return lightmap;
 	return lightmap;
 }
 }
@@ -142,6 +188,10 @@ void LightmapGIData::_reset_lightmap_textures() {
 	RS::get_singleton()->lightmap_set_textures(lightmap, combined_light_texture.is_valid() ? combined_light_texture->get_rid() : RID(), uses_spherical_harmonics);
 	RS::get_singleton()->lightmap_set_textures(lightmap, combined_light_texture.is_valid() ? combined_light_texture->get_rid() : RID(), uses_spherical_harmonics);
 }
 }
 
 
+void LightmapGIData::_reset_shadowmask_textures() {
+	RS::get_singleton()->lightmap_set_shadowmask_textures(lightmap, combined_shadowmask_texture.is_valid() ? combined_shadowmask_texture->get_rid() : RID());
+}
+
 void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) {
 void LightmapGIData::set_uses_spherical_harmonics(bool p_enable) {
 	uses_spherical_harmonics = p_enable;
 	uses_spherical_harmonics = p_enable;
 	_reset_lightmap_textures();
 	_reset_lightmap_textures();
@@ -159,6 +209,14 @@ bool LightmapGIData::_is_using_packed_directional() const {
 	return _uses_packed_directional;
 	return _uses_packed_directional;
 }
 }
 
 
+void LightmapGIData::update_shadowmask_mode(ShadowmaskMode p_mode) {
+	RS::get_singleton()->lightmap_set_shadowmask_mode(lightmap, (RS::ShadowmaskMode)p_mode);
+}
+
+LightmapGIData::ShadowmaskMode LightmapGIData::get_shadowmask_mode() const {
+	return (ShadowmaskMode)RS::get_singleton()->lightmap_get_shadowmask_mode(lightmap);
+}
+
 void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
 void LightmapGIData::set_capture_data(const AABB &p_bounds, bool p_interior, const PackedVector3Array &p_points, const PackedColorArray &p_point_sh, const PackedInt32Array &p_tetrahedra, const PackedInt32Array &p_bsp_tree, float p_baked_exposure) {
 	if (p_points.size()) {
 	if (p_points.size()) {
 		int pc = p_points.size();
 		int pc = p_points.size();
@@ -260,6 +318,9 @@ void LightmapGIData::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_lightmap_textures", "light_textures"), &LightmapGIData::set_lightmap_textures);
 	ClassDB::bind_method(D_METHOD("set_lightmap_textures", "light_textures"), &LightmapGIData::set_lightmap_textures);
 	ClassDB::bind_method(D_METHOD("get_lightmap_textures"), &LightmapGIData::get_lightmap_textures);
 	ClassDB::bind_method(D_METHOD("get_lightmap_textures"), &LightmapGIData::get_lightmap_textures);
 
 
+	ClassDB::bind_method(D_METHOD("set_shadowmask_textures", "shadowmask_textures"), &LightmapGIData::set_shadowmask_textures);
+	ClassDB::bind_method(D_METHOD("get_shadowmask_textures"), &LightmapGIData::get_shadowmask_textures);
+
 	ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
 	ClassDB::bind_method(D_METHOD("set_uses_spherical_harmonics", "uses_spherical_harmonics"), &LightmapGIData::set_uses_spherical_harmonics);
 	ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
 	ClassDB::bind_method(D_METHOD("is_using_spherical_harmonics"), &LightmapGIData::is_using_spherical_harmonics);
 
 
@@ -275,6 +336,7 @@ void LightmapGIData::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
 	ClassDB::bind_method(D_METHOD("_get_probe_data"), &LightmapGIData::_get_probe_data);
 
 
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_lightmap_textures", "get_lightmap_textures");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "lightmap_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_lightmap_textures", "get_lightmap_textures");
+	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "shadowmask_textures", PROPERTY_HINT_ARRAY_TYPE, "TextureLayered", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY), "set_shadowmask_textures", "get_shadowmask_textures");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "uses_spherical_harmonics", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_uses_spherical_harmonics", "is_using_spherical_harmonics");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
 	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
 	ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "probe_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_probe_data", "_get_probe_data");
@@ -290,6 +352,10 @@ void LightmapGIData::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_NONE), "set_light_texture", "get_light_texture");
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "light_texture", PROPERTY_HINT_RESOURCE_TYPE, "TextureLayered", PROPERTY_USAGE_NONE), "set_light_texture", "get_light_texture");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "light_textures", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_INTERNAL), "_set_light_textures_data", "_get_light_textures_data");
 #endif
 #endif
+
+	BIND_ENUM_CONSTANT(SHADOWMASK_MODE_NONE);
+	BIND_ENUM_CONSTANT(SHADOWMASK_MODE_REPLACE);
+	BIND_ENUM_CONSTANT(SHADOWMASK_MODE_OVERLAY);
 }
 }
 
 
 LightmapGIData::LightmapGIData() {
 LightmapGIData::LightmapGIData() {
@@ -738,12 +804,12 @@ void LightmapGI::_gen_new_positions_from_octree(const GenProbesOctree *p_cell, f
 	}
 	}
 }
 }
 
 
-LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_compress) const {
+LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_is_shadowmask, bool p_compress) const {
 	Vector<Ref<Image>> images;
 	Vector<Ref<Image>> images;
-	images.resize(p_lightmapper->get_bake_texture_count());
+	images.resize(p_is_shadowmask ? p_lightmapper->get_shadowmask_texture_count() : p_lightmapper->get_bake_texture_count());
 
 
 	for (int i = 0; i < images.size(); i++) {
 	for (int i = 0; i < images.size(); i++) {
-		images.set(i, p_lightmapper->get_bake_texture(i));
+		images.set(i, p_is_shadowmask ? p_lightmapper->get_shadowmask_texture(i) : p_lightmapper->get_bake_texture(i));
 	}
 	}
 
 
 	const int slice_count = images.size();
 	const int slice_count = images.size();
@@ -765,7 +831,7 @@ LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Li
 			texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
 			texture_image->blit_rect(images[i * slices_per_texture + j], Rect2i(0, 0, slice_width, slice_height), Point2i(0, slice_height * j));
 		}
 		}
 
 
-		const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + ".exr";
+		const String atlas_path = (texture_count > 1 ? p_base_name + "_" + itos(i) : p_base_name) + (p_is_shadowmask ? ".png" : ".exr");
 		const String config_path = atlas_path + ".import";
 		const String config_path = atlas_path + ".import";
 
 
 		Ref<ConfigFile> config;
 		Ref<ConfigFile> config;
@@ -790,7 +856,12 @@ LightmapGI::BakeError LightmapGI::_save_and_reimport_atlas_textures(const Ref<Li
 		config->save(config_path);
 		config->save(config_path);
 
 
 		// Save the file.
 		// Save the file.
-		Error save_err = texture_image->save_exr(atlas_path, false);
+		Error save_err;
+		if (p_is_shadowmask) {
+			save_err = texture_image->save_png(atlas_path);
+		} else {
+			save_err = texture_image->save_exr(atlas_path, false);
+		}
 
 
 		ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
 		ERR_FAIL_COND_V(save_err, LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE);
 
 
@@ -1104,20 +1175,20 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
 			if (Object::cast_to<DirectionalLight3D>(light)) {
 			if (Object::cast_to<DirectionalLight3D>(light)) {
 				DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light);
 				DirectionalLight3D *l = Object::cast_to<DirectionalLight3D>(light);
 				if (l->get_sky_mode() != DirectionalLight3D::SKY_MODE_SKY_ONLY) {
 				if (l->get_sky_mode() != DirectionalLight3D::SKY_MODE_SKY_ONLY) {
-					lightmapper->add_directional_light(light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
+					lightmapper->add_directional_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
 				}
 				}
 			} else if (Object::cast_to<OmniLight3D>(light)) {
 			} else if (Object::cast_to<OmniLight3D>(light)) {
 				OmniLight3D *l = Object::cast_to<OmniLight3D>(light);
 				OmniLight3D *l = Object::cast_to<OmniLight3D>(light);
 				if (use_physical_light_units) {
 				if (use_physical_light_units) {
 					energy *= (1.0 / (Math_PI * 4.0));
 					energy *= (1.0 / (Math_PI * 4.0));
 				}
 				}
-				lightmapper->add_omni_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
+				lightmapper->add_omni_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
 			} else if (Object::cast_to<SpotLight3D>(light)) {
 			} else if (Object::cast_to<SpotLight3D>(light)) {
 				SpotLight3D *l = Object::cast_to<SpotLight3D>(light);
 				SpotLight3D *l = Object::cast_to<SpotLight3D>(light);
 				if (use_physical_light_units) {
 				if (use_physical_light_units) {
 					energy *= (1.0 / Math_PI);
 					energy *= (1.0 / Math_PI);
 				}
 				}
-				lightmapper->add_spot_light(light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
+				lightmapper->add_spot_light(light->get_name(), light->get_bake_mode() == Light3D::BAKE_STATIC, xf.origin, -xf.basis.get_column(Vector3::AXIS_Z).normalized(), linear_color, energy, indirect_energy, l->get_param(Light3D::PARAM_RANGE), l->get_param(Light3D::PARAM_ATTENUATION), l->get_param(Light3D::PARAM_SPOT_ANGLE), l->get_param(Light3D::PARAM_SPOT_ATTENUATION), l->get_param(Light3D::PARAM_SIZE), l->get_param(Light3D::PARAM_SHADOW_BLUR));
 			}
 			}
 		}
 		}
 		for (int i = 0; i < probes_found.size(); i++) {
 		for (int i = 0; i < probes_found.size(); i++) {
@@ -1181,7 +1252,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, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, use_texture_for_bounces, 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, denoiser_strength, denoiser_range, bounces, bounce_indirect_energy, bias, max_texture_size, directional, shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE, use_texture_for_bounces, Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization);
 
 
 	if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
 	if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
 		return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
 		return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
@@ -1196,15 +1267,23 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
 	// POSTBAKE: Save Textures.
 	// POSTBAKE: Save Textures.
 
 
 	TypedArray<TextureLayered> lightmap_textures;
 	TypedArray<TextureLayered> lightmap_textures;
+	TypedArray<TextureLayered> shadowmask_textures;
 
 
 	const String texture_filename = p_image_data_path.get_basename();
 	const String texture_filename = p_image_data_path.get_basename();
+	const int shadowmask_texture_count = lightmapper->get_shadowmask_texture_count();
+	const bool save_shadowmask = shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && shadowmask_texture_count > 0;
 
 
 	// Save the lightmap atlases.
 	// Save the lightmap atlases.
-	BakeError save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename, lightmap_textures, false);
+	BakeError save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename, lightmap_textures, false, false);
 	ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
 	ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
 
 
-	// POSTBAKE: Save Light Data.
+	if (save_shadowmask) {
+		// Save the shadowmask atlases.
+		save_err = _save_and_reimport_atlas_textures(lightmapper, texture_filename + "_shadow", shadowmask_textures, true, true);
+		ERR_FAIL_COND_V(save_err != BAKE_ERROR_OK, save_err);
+	}
 
 
+	/* POSTBAKE: Save Light Data. */
 	Ref<LightmapGIData> gi_data;
 	Ref<LightmapGIData> gi_data;
 
 
 	if (get_light_data().is_valid()) {
 	if (get_light_data().is_valid()) {
@@ -1217,6 +1296,13 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
 	}
 	}
 
 
 	gi_data->set_lightmap_textures(lightmap_textures);
 	gi_data->set_lightmap_textures(lightmap_textures);
+
+	if (save_shadowmask) {
+		gi_data->set_shadowmask_textures(shadowmask_textures);
+	} else {
+		gi_data->clear_shadowmask_textures();
+	}
+
 	gi_data->set_uses_spherical_harmonics(directional);
 	gi_data->set_uses_spherical_harmonics(directional);
 	gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
 	gi_data->_set_uses_packed_directional(directional); // New SH lightmaps are packed automatically.
 
 
@@ -1375,6 +1461,7 @@ LightmapGI::BakeError LightmapGI::bake(Node *p_from_node, String p_image_data_pa
 	}
 	}
 
 
 	set_light_data(gi_data);
 	set_light_data(gi_data);
+	update_configuration_warnings();
 
 
 	return BAKE_ERROR_OK;
 	return BAKE_ERROR_OK;
 }
 }
@@ -1452,6 +1539,7 @@ void LightmapGI::set_light_data(const Ref<LightmapGIData> &p_data) {
 		if (is_inside_tree()) {
 		if (is_inside_tree()) {
 			_assign_lightmaps();
 			_assign_lightmaps();
 		}
 		}
+		light_data->update_shadowmask_mode(shadowmask_mode);
 	}
 	}
 
 
 	update_gizmos();
 	update_gizmos();
@@ -1506,6 +1594,19 @@ bool LightmapGI::is_directional() const {
 	return directional;
 	return directional;
 }
 }
 
 
+void LightmapGI::set_shadowmask_mode(LightmapGIData::ShadowmaskMode p_mode) {
+	shadowmask_mode = p_mode;
+	if (light_data.is_valid()) {
+		light_data->update_shadowmask_mode(p_mode);
+	}
+
+	update_configuration_warnings();
+}
+
+LightmapGIData::ShadowmaskMode LightmapGI::get_shadowmask_mode() const {
+	return shadowmask_mode;
+}
+
 void LightmapGI::set_use_texture_for_bounces(bool p_enable) {
 void LightmapGI::set_use_texture_for_bounces(bool p_enable) {
 	use_texture_for_bounces = p_enable;
 	use_texture_for_bounces = p_enable;
 }
 }
@@ -1625,6 +1726,11 @@ PackedStringArray LightmapGI::get_configuration_warnings() const {
 		warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
 		warnings.push_back(vformat(RTR("Lightmaps can only be baked from a GPU that supports the RenderingDevice backends.\nYour GPU (%s) does not support RenderingDevice, as it does not support Vulkan, Direct3D 12, or Metal.\nLightmap baking will not be available on this device, although rendering existing baked lightmaps will work."), RenderingServer::get_singleton()->get_video_adapter_name()));
 		return warnings;
 		return warnings;
 	}
 	}
+
+	if (shadowmask_mode != LightmapGIData::SHADOWMASK_MODE_NONE && light_data.is_valid() && !light_data->has_shadowmask_textures()) {
+		warnings.push_back(RTR("The lightmap has no baked shadowmask textures. Please rebake with the Shadowmask Mode set to anything other than None."));
+	}
+
 #elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
 #elif defined(ANDROID_ENABLED) || defined(IOS_ENABLED)
 	warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
 	warnings.push_back(vformat(RTR("Lightmaps cannot be baked on %s. Rendering existing baked lightmaps will still work."), OS::get_singleton()->get_name()));
 #else
 #else
@@ -1704,6 +1810,9 @@ void LightmapGI::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_directional", "directional"), &LightmapGI::set_directional);
 	ClassDB::bind_method(D_METHOD("set_directional", "directional"), &LightmapGI::set_directional);
 	ClassDB::bind_method(D_METHOD("is_directional"), &LightmapGI::is_directional);
 	ClassDB::bind_method(D_METHOD("is_directional"), &LightmapGI::is_directional);
 
 
+	ClassDB::bind_method(D_METHOD("set_shadowmask_mode", "mode"), &LightmapGI::set_shadowmask_mode);
+	ClassDB::bind_method(D_METHOD("get_shadowmask_mode"), &LightmapGI::get_shadowmask_mode);
+
 	ClassDB::bind_method(D_METHOD("set_use_texture_for_bounces", "use_texture_for_bounces"), &LightmapGI::set_use_texture_for_bounces);
 	ClassDB::bind_method(D_METHOD("set_use_texture_for_bounces", "use_texture_for_bounces"), &LightmapGI::set_use_texture_for_bounces);
 	ClassDB::bind_method(D_METHOD("is_using_texture_for_bounces"), &LightmapGI::is_using_texture_for_bounces);
 	ClassDB::bind_method(D_METHOD("is_using_texture_for_bounces"), &LightmapGI::is_using_texture_for_bounces);
 
 
@@ -1717,6 +1826,7 @@ void LightmapGI::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,6,1,or_greater"), "set_bounces", "get_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "bounces", PROPERTY_HINT_RANGE, "0,6,1,or_greater"), "set_bounces", "get_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bounce_indirect_energy", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_bounce_indirect_energy", "get_bounce_indirect_energy");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "directional"), "set_directional", "is_directional");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "shadowmask_mode", PROPERTY_HINT_ENUM, "None,Replace,Overlay"), "set_shadowmask_mode", "get_shadowmask_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_for_bounces"), "set_use_texture_for_bounces", "is_using_texture_for_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_texture_for_bounces"), "set_use_texture_for_bounces", "is_using_texture_for_bounces");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
 	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_denoiser"), "set_use_denoiser", "is_using_denoiser");

+ 26 - 1
scene/3d/lightmap_gi.h

@@ -43,12 +43,23 @@ class LightmapGIData : public Resource {
 	GDCLASS(LightmapGIData, Resource);
 	GDCLASS(LightmapGIData, Resource);
 	RES_BASE_EXTENSION("lmbake")
 	RES_BASE_EXTENSION("lmbake")
 
 
+public:
+	enum ShadowmaskMode {
+		SHADOWMASK_MODE_NONE,
+		SHADOWMASK_MODE_REPLACE,
+		SHADOWMASK_MODE_OVERLAY,
+		SHADOWMASK_MODE_ONLY,
+	};
+
+private:
 	// The 'merged' texture atlases actually used by the renderer.
 	// The 'merged' texture atlases actually used by the renderer.
 	Ref<TextureLayered> combined_light_texture;
 	Ref<TextureLayered> combined_light_texture;
+	Ref<TextureLayered> combined_shadowmask_texture;
 
 
 	// The temporary texture atlas arrays which are used for storage.
 	// The temporary texture atlas arrays which are used for storage.
 	// If a single atlas is too large, it's split and recombined during loading.
 	// If a single atlas is too large, it's split and recombined during loading.
 	TypedArray<TextureLayered> storage_light_textures;
 	TypedArray<TextureLayered> storage_light_textures;
+	TypedArray<TextureLayered> storage_shadowmask_textures;
 
 
 	bool uses_spherical_harmonics = false;
 	bool uses_spherical_harmonics = false;
 	bool interior = false;
 	bool interior = false;
@@ -74,6 +85,7 @@ class LightmapGIData : public Resource {
 	Dictionary _get_probe_data() const;
 	Dictionary _get_probe_data() const;
 
 
 	void _reset_lightmap_textures();
 	void _reset_lightmap_textures();
+	void _reset_shadowmask_textures();
 
 
 protected:
 protected:
 	static void _bind_methods();
 	static void _bind_methods();
@@ -101,6 +113,9 @@ public:
 	void _set_uses_packed_directional(bool p_enable);
 	void _set_uses_packed_directional(bool p_enable);
 	bool _is_using_packed_directional() const;
 	bool _is_using_packed_directional() const;
 
 
+	void update_shadowmask_mode(ShadowmaskMode p_mode);
+	ShadowmaskMode get_shadowmask_mode() const;
+
 	bool is_interior() const;
 	bool is_interior() const;
 	float get_baked_exposure() const;
 	float get_baked_exposure() const;
 
 
@@ -116,6 +131,11 @@ public:
 	void set_lightmap_textures(const TypedArray<TextureLayered> &p_data);
 	void set_lightmap_textures(const TypedArray<TextureLayered> &p_data);
 	TypedArray<TextureLayered> get_lightmap_textures() const;
 	TypedArray<TextureLayered> get_lightmap_textures() const;
 
 
+	void set_shadowmask_textures(const TypedArray<TextureLayered> &p_data);
+	TypedArray<TextureLayered> get_shadowmask_textures() const;
+	void clear_shadowmask_textures();
+	bool has_shadowmask_textures();
+
 	virtual RID get_rid() const override;
 	virtual RID get_rid() const override;
 	LightmapGIData();
 	LightmapGIData();
 	~LightmapGIData();
 	~LightmapGIData();
@@ -179,6 +199,7 @@ private:
 	float environment_custom_energy = 1.0;
 	float environment_custom_energy = 1.0;
 	bool directional = false;
 	bool directional = false;
 	bool use_texture_for_bounces = true;
 	bool use_texture_for_bounces = true;
+	LightmapGIData::ShadowmaskMode shadowmask_mode = LightmapGIData::SHADOWMASK_MODE_NONE;
 	GenerateProbes gen_probes = GENERATE_PROBES_SUBDIV_8;
 	GenerateProbes gen_probes = GENERATE_PROBES_SUBDIV_8;
 	Ref<CameraAttributes> camera_attributes;
 	Ref<CameraAttributes> camera_attributes;
 
 
@@ -249,7 +270,7 @@ private:
 	void _plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cell_size, const Vector3 *p_triangle);
 	void _plot_triangle_into_octree(GenProbesOctree *p_cell, float p_cell_size, const Vector3 *p_triangle);
 	void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds);
 	void _gen_new_positions_from_octree(const GenProbesOctree *p_cell, float p_cell_size, const Vector<Vector3> &probe_positions, LocalVector<Vector3> &new_probe_positions, HashMap<Vector3i, bool> &positions_used, const AABB &p_bounds);
 
 
-	BakeError _save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_compress = false) const;
+	BakeError _save_and_reimport_atlas_textures(const Ref<Lightmapper> p_lightmapper, const String &p_base_name, TypedArray<TextureLayered> &r_textures, bool p_is_shadowmask = false, bool p_compress = false) const;
 
 
 protected:
 protected:
 	void _validate_property(PropertyInfo &p_property) const;
 	void _validate_property(PropertyInfo &p_property) const;
@@ -275,6 +296,9 @@ public:
 	void set_directional(bool p_enable);
 	void set_directional(bool p_enable);
 	bool is_directional() const;
 	bool is_directional() const;
 
 
+	void set_shadowmask_mode(LightmapGIData::ShadowmaskMode p_mode);
+	LightmapGIData::ShadowmaskMode get_shadowmask_mode() const;
+
 	void set_use_texture_for_bounces(bool p_enable);
 	void set_use_texture_for_bounces(bool p_enable);
 	bool is_using_texture_for_bounces() const;
 	bool is_using_texture_for_bounces() const;
 
 
@@ -323,6 +347,7 @@ public:
 	LightmapGI();
 	LightmapGI();
 };
 };
 
 
+VARIANT_ENUM_CAST(LightmapGIData::ShadowmaskMode);
 VARIANT_ENUM_CAST(LightmapGI::BakeQuality);
 VARIANT_ENUM_CAST(LightmapGI::BakeQuality);
 VARIANT_ENUM_CAST(LightmapGI::GenerateProbes);
 VARIANT_ENUM_CAST(LightmapGI::GenerateProbes);
 VARIANT_ENUM_CAST(LightmapGI::BakeError);
 VARIANT_ENUM_CAST(LightmapGI::BakeError);

+ 6 - 5
scene/3d/lightmapper.h

@@ -133,7 +133,6 @@ public:
 		GENERATE_PROBES_SUBDIV_8,
 		GENERATE_PROBES_SUBDIV_8,
 		GENERATE_PROBES_SUBDIV_16,
 		GENERATE_PROBES_SUBDIV_16,
 		GENERATE_PROBES_SUBDIV_32,
 		GENERATE_PROBES_SUBDIV_32,
-
 	};
 	};
 
 
 	enum LightType {
 	enum LightType {
@@ -178,14 +177,16 @@ public:
 	};
 	};
 
 
 	virtual void add_mesh(const MeshData &p_mesh) = 0;
 	virtual void add_mesh(const MeshData &p_mesh) = 0;
-	virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) = 0;
-	virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_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_indirect_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_directional_light(const String &p_name, bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_indirect_energy, float p_angular_distance, float p_shadow_blur) = 0;
+	virtual void add_omni_light(const String &p_name, bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_indirect_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) = 0;
+	virtual void add_spot_light(const String &p_name, bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_indirect_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 void add_probe(const Vector3 &p_position) = 0;
-	virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &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, float p_denoiser_strength, int p_denoiser_range, int p_bounces, float p_bounce_indirect_energy, float p_bias, int p_max_texture_size, bool p_bake_sh, bool p_bake_shadowmask, bool p_texture_for_bounces, GenerateProbes p_generate_probes, const Ref<Image> &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 int get_bake_texture_count() const = 0;
 	virtual Ref<Image> get_bake_texture(int p_index) const = 0;
 	virtual Ref<Image> get_bake_texture(int p_index) const = 0;
+	virtual int get_shadowmask_texture_count() const = 0;
+	virtual Ref<Image> get_shadowmask_texture(int p_index) const = 0;
 	virtual int get_bake_mesh_count() const = 0;
 	virtual int get_bake_mesh_count() const = 0;
 	virtual Variant get_bake_mesh_userdata(int p_index) const = 0;
 	virtual Variant get_bake_mesh_userdata(int p_index) const = 0;
 	virtual Rect2 get_bake_mesh_uv_scale(int p_index) const = 0;
 	virtual Rect2 get_bake_mesh_uv_scale(int p_index) const = 0;

+ 4 - 0
servers/rendering/dummy/storage/light_storage.h

@@ -191,6 +191,10 @@ public:
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) override {}
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) override {}
 	virtual float lightmap_get_probe_capture_update_speed() const override { return 0; }
 	virtual float lightmap_get_probe_capture_update_speed() const override { return 0; }
 
 
+	virtual void lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) override {}
+	virtual RS::ShadowmaskMode lightmap_get_shadowmask_mode(RID p_lightmap) override { return RS::SHADOWMASK_MODE_NONE; }
+	virtual void lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) override {}
+
 	/* LIGHTMAP INSTANCE */
 	/* LIGHTMAP INSTANCE */
 
 
 	bool owns_lightmap_instance(RID p_rid) { return lightmap_instance_owner.owns(p_rid); }
 	bool owns_lightmap_instance(RID p_rid) { return lightmap_instance_owner.owns(p_rid); }

+ 24 - 9
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -1167,6 +1167,7 @@ void RenderForwardClustered::_setup_lightmaps(const RenderDataRD *p_render_data,
 
 
 		// Exposure.
 		// Exposure.
 		scene_state.lightmaps[i].exposure_normalization = 1.0;
 		scene_state.lightmaps[i].exposure_normalization = 1.0;
+		scene_state.lightmaps[i].flags = light_storage->lightmap_get_shadowmask_mode(lightmap);
 		if (p_render_data->camera_attributes.is_valid()) {
 		if (p_render_data->camera_attributes.is_valid()) {
 			float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
 			float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
 			float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
 			float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
@@ -3223,15 +3224,29 @@ RID RenderForwardClustered::_setup_render_pass_uniform_set(RenderListType p_rend
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 
 
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
-		for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
-			if (p_render_data && i < p_render_data->lightmaps->size()) {
-				RID base = light_storage->lightmap_instance_get_lightmap((*p_render_data->lightmaps)[i]);
-				RID texture = light_storage->lightmap_get_texture(base);
-				RID rd_texture = texture_storage->texture_get_rd_texture(texture);
-				u.append_id(rd_texture);
-			} else {
-				u.append_id(default_tex);
+		for (uint32_t i = 0; i < scene_state.max_lightmaps * 2; i++) {
+			uint32_t current_lightmap_index = i < scene_state.max_lightmaps ? i : i - scene_state.max_lightmaps;
+
+			if (p_render_data && current_lightmap_index < p_render_data->lightmaps->size()) {
+				RID base = light_storage->lightmap_instance_get_lightmap((*p_render_data->lightmaps)[current_lightmap_index]);
+				RID texture;
+
+				if (i < scene_state.max_lightmaps) {
+					// Lightmap
+					texture = light_storage->lightmap_get_texture(base);
+				} else {
+					// Shadowmask
+					texture = light_storage->shadowmask_get_texture(base);
+				}
+
+				if (texture.is_valid()) {
+					RID rd_texture = texture_storage->texture_get_rd_texture(texture);
+					u.append_id(rd_texture);
+					continue;
+				}
 			}
 			}
+
+			u.append_id(default_tex);
 		}
 		}
 
 
 		uniforms.push_back(u);
 		uniforms.push_back(u);
@@ -3535,7 +3550,7 @@ RID RenderForwardClustered::_setup_sdfgi_render_pass_uniform_set(RID p_albedo_te
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 
 
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
-		for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
+		for (uint32_t i = 0; i < scene_state.max_lightmaps * 2; i++) {
 			u.append_id(default_tex);
 			u.append_id(default_tex);
 		}
 		}
 
 

+ 1 - 1
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.h

@@ -237,7 +237,7 @@ private:
 		float normal_xform[12];
 		float normal_xform[12];
 		float texture_size[2];
 		float texture_size[2];
 		float exposure_normalization;
 		float exposure_normalization;
-		float pad;
+		uint32_t flags;
 	};
 	};
 
 
 	struct LightmapCaptureData {
 	struct LightmapCaptureData {

+ 23 - 8
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -477,15 +477,29 @@ RID RenderForwardMobile::_setup_render_pass_uniform_set(RenderListType p_render_
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 		u.uniform_type = RD::UNIFORM_TYPE_TEXTURE;
 
 
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
 		RID default_tex = texture_storage->texture_rd_get_default(RendererRD::TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
-		for (uint32_t i = 0; i < scene_state.max_lightmaps; i++) {
-			if (p_render_data && i < p_render_data->lightmaps->size()) {
-				RID base = light_storage->lightmap_instance_get_lightmap((*p_render_data->lightmaps)[i]);
-				RID texture = light_storage->lightmap_get_texture(base);
-				RID rd_texture = texture_storage->texture_get_rd_texture(texture);
-				u.append_id(rd_texture);
-			} else {
-				u.append_id(default_tex);
+		for (uint32_t i = 0; i < scene_state.max_lightmaps * 2; i++) {
+			uint32_t current_lightmap_index = i < scene_state.max_lightmaps ? i : i - scene_state.max_lightmaps;
+
+			if (p_render_data && current_lightmap_index < p_render_data->lightmaps->size()) {
+				RID base = light_storage->lightmap_instance_get_lightmap((*p_render_data->lightmaps)[current_lightmap_index]);
+				RID texture;
+
+				if (i < scene_state.max_lightmaps) {
+					// Lightmap
+					texture = light_storage->lightmap_get_texture(base);
+				} else {
+					// Shadowmask
+					texture = light_storage->shadowmask_get_texture(base);
+				}
+
+				if (texture.is_valid()) {
+					RID rd_texture = texture_storage->texture_get_rd_texture(texture);
+					u.append_id(rd_texture);
+					continue;
+				}
 			}
 			}
+
+			u.append_id(default_tex);
 		}
 		}
 
 
 		uniforms.push_back(u);
 		uniforms.push_back(u);
@@ -642,6 +656,7 @@ void RenderForwardMobile::_setup_lightmaps(const RenderDataRD *p_render_data, co
 
 
 		// Exposure.
 		// Exposure.
 		scene_state.lightmaps[i].exposure_normalization = 1.0;
 		scene_state.lightmaps[i].exposure_normalization = 1.0;
+		scene_state.lightmaps[i].flags = light_storage->lightmap_get_shadowmask_mode(lightmap);
 		if (p_render_data->camera_attributes.is_valid()) {
 		if (p_render_data->camera_attributes.is_valid()) {
 			float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
 			float baked_exposure = light_storage->lightmap_get_baked_exposure_normalization(lightmap);
 			float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);
 			float enf = RSG::camera_attributes->camera_attributes_get_exposure_normalization_factor(p_render_data->camera_attributes);

+ 1 - 1
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.h

@@ -184,7 +184,7 @@ private:
 		float normal_xform[12];
 		float normal_xform[12];
 		float texture_size[2];
 		float texture_size[2];
 		float exposure_normalization;
 		float exposure_normalization;
-		float pad;
+		uint32_t flags;
 	};
 	};
 
 
 	struct LightmapCaptureData {
 	struct LightmapCaptureData {

+ 197 - 149
servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered.glsl

@@ -1978,9 +1978,34 @@ void fragment_shader(in SceneData scene_data) {
 		uint shadow0 = 0;
 		uint shadow0 = 0;
 		uint shadow1 = 0;
 		uint shadow1 = 0;
 
 
+		float shadowmask = 1.0;
+
+#ifdef USE_LIGHTMAP
+		uint shadowmask_mode = LIGHTMAP_SHADOWMASK_MODE_NONE;
+
+		if (bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+			const uint ofs = instances.data[instance_index].gi_offset & 0xFFFF;
+			shadowmask_mode = lightmaps.data[ofs].flags;
+
+			if (shadowmask_mode != LIGHTMAP_SHADOWMASK_MODE_NONE) {
+				const uint slice = instances.data[instance_index].gi_offset >> 16;
+				const vec2 scaled_uv = uv2 * instances.data[instance_index].lightmap_uv_scale.zw + instances.data[instance_index].lightmap_uv_scale.xy;
+				const vec3 uvw = vec3(scaled_uv, float(slice));
+
+				if (sc_use_lightmap_bicubic_filter()) {
+					shadowmask = textureArray_bicubic(lightmap_textures[MAX_LIGHTMAP_TEXTURES + ofs], uvw, lightmaps.data[ofs].light_texture_size).x;
+				} else {
+					shadowmask = textureLod(sampler2DArray(lightmap_textures[MAX_LIGHTMAP_TEXTURES + ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).x;
+				}
+			}
+		}
+
+		if (shadowmask_mode != LIGHTMAP_SHADOWMASK_MODE_ONLY) {
+#endif // USE_LIGHTMAP
+
 #ifdef USE_VERTEX_LIGHTING
 #ifdef USE_VERTEX_LIGHTING
-		// Only process the first light's shadow for vertex lighting.
-		for (uint i = 0; i < 1; i++) {
+			// Only process the first light's shadow for vertex lighting.
+			for (uint i = 0; i < 1; i++) {
 #else
 #else
 		for (uint i = 0; i < 8; i++) {
 		for (uint i = 0; i < 8; i++) {
 			if (i >= scene_data.directional_light_count) {
 			if (i >= scene_data.directional_light_count) {
@@ -1988,20 +2013,20 @@ void fragment_shader(in SceneData scene_data) {
 			}
 			}
 #endif
 #endif
 
 
-			if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) {
-				continue; //not masked
-			}
+				if (!bool(directional_lights.data[i].mask & instances.data[instance_index].layer_mask)) {
+					continue; //not masked
+				}
 
 
-			if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
-				continue; // Statically baked light and object uses lightmap, skip
-			}
+				if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+					continue; // Statically baked light and object uses lightmap, skip
+				}
 
 
-			float shadow = 1.0;
+				float shadow = 1.0;
 
 
-			if (directional_lights.data[i].shadow_opacity > 0.001) {
-				float depth_z = -vertex.z;
-				vec3 light_dir = directional_lights.data[i].direction;
-				vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
+				if (directional_lights.data[i].shadow_opacity > 0.001) {
+					float depth_z = -vertex.z;
+					vec3 light_dir = directional_lights.data[i].direction;
+					vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
 
 
 #define BIAS_FUNC(m_var, m_idx)                                                                 \
 #define BIAS_FUNC(m_var, m_idx)                                                                 \
 	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                     \
 	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                     \
@@ -2009,195 +2034,218 @@ void fragment_shader(in SceneData scene_data) {
 	normal_bias -= light_dir * dot(light_dir, normal_bias);                                     \
 	normal_bias -= light_dir * dot(light_dir, normal_bias);                                     \
 	m_var.xyz += normal_bias;
 	m_var.xyz += normal_bias;
 
 
-				//version with soft shadows, more expensive
-				if (sc_use_directional_soft_shadows() && directional_lights.data[i].softshadow_angle > 0) {
-					uint blend_count = 0;
-					const uint blend_max = directional_lights.data[i].blend_splits ? 2 : 1;
-
-					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
-						vec4 v = vec4(vertex, 1.0);
-
-						BIAS_FUNC(v, 0)
-
-						vec4 pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
-						pssm_coord /= pssm_coord.w;
-
-						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
-						float range_begin = directional_lights.data[i].shadow_range_begin.x;
-						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
-						vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
-						shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
-						blend_count++;
-					}
-
-					if (blend_count < blend_max && depth_z < directional_lights.data[i].shadow_split_offsets.y) {
-						vec4 v = vec4(vertex, 1.0);
+					//version with soft shadows, more expensive
+					if (sc_use_directional_soft_shadows() && directional_lights.data[i].softshadow_angle > 0) {
+						uint blend_count = 0;
+						const uint blend_max = directional_lights.data[i].blend_splits ? 2 : 1;
 
 
-						BIAS_FUNC(v, 1)
+						if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+							vec4 v = vec4(vertex, 1.0);
 
 
-						vec4 pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
-						pssm_coord /= pssm_coord.w;
+							BIAS_FUNC(v, 0)
 
 
-						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
-						float range_begin = directional_lights.data[i].shadow_range_begin.y;
-						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
-						vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
-						float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
+							vec4 pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+							pssm_coord /= pssm_coord.w;
 
 
-						if (blend_count == 0) {
-							shadow = s;
-						} else {
-							//blend
-							float blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
-							shadow = mix(shadow, s, blend);
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.x;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale1 * test_radius;
+							shadow = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
+							blend_count++;
 						}
 						}
 
 
-						blend_count++;
-					}
+						if (blend_count < blend_max && depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+							vec4 v = vec4(vertex, 1.0);
 
 
-					if (blend_count < blend_max && depth_z < directional_lights.data[i].shadow_split_offsets.z) {
-						vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 1)
 
 
-						BIAS_FUNC(v, 2)
+							vec4 pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+							pssm_coord /= pssm_coord.w;
 
 
-						vec4 pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
-						pssm_coord /= pssm_coord.w;
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.y;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale2 * test_radius;
+							float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
 
 
-						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
-						float range_begin = directional_lights.data[i].shadow_range_begin.z;
-						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
-						vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
-						float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
+							if (blend_count == 0) {
+								shadow = s;
+							} else {
+								//blend
+								float blend = smoothstep(0.0, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+								shadow = mix(shadow, s, blend);
+							}
 
 
-						if (blend_count == 0) {
-							shadow = s;
-						} else {
-							//blend
-							float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
-							shadow = mix(shadow, s, blend);
+							blend_count++;
 						}
 						}
 
 
-						blend_count++;
-					}
+						if (blend_count < blend_max && depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+							vec4 v = vec4(vertex, 1.0);
 
 
-					if (blend_count < blend_max) {
-						vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 2)
 
 
-						BIAS_FUNC(v, 3)
+							vec4 pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+							pssm_coord /= pssm_coord.w;
 
 
-						vec4 pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
-						pssm_coord /= pssm_coord.w;
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.z;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale3 * test_radius;
+							float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
 
 
-						float range_pos = dot(directional_lights.data[i].direction, v.xyz);
-						float range_begin = directional_lights.data[i].shadow_range_begin.w;
-						float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
-						vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
-						float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
+							if (blend_count == 0) {
+								shadow = s;
+							} else {
+								//blend
+								float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+								shadow = mix(shadow, s, blend);
+							}
 
 
-						if (blend_count == 0) {
-							shadow = s;
-						} else {
-							//blend
-							float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
-							shadow = mix(shadow, s, blend);
+							blend_count++;
 						}
 						}
-					}
 
 
-				} else { //no soft shadows
-
-					vec4 pssm_coord;
-					float blur_factor;
-
-					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
-						vec4 v = vec4(vertex, 1.0);
-
-						BIAS_FUNC(v, 0)
-
-						pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
-						blur_factor = 1.0;
-					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
-						vec4 v = vec4(vertex, 1.0);
-
-						BIAS_FUNC(v, 1)
-
-						pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
-						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
-					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
-						vec4 v = vec4(vertex, 1.0);
-
-						BIAS_FUNC(v, 2)
+						if (blend_count < blend_max) {
+							vec4 v = vec4(vertex, 1.0);
 
 
-						pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
-						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
-					} else {
-						vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 3)
 
 
-						BIAS_FUNC(v, 3)
+							vec4 pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+							pssm_coord /= pssm_coord.w;
+
+							float range_pos = dot(directional_lights.data[i].direction, v.xyz);
+							float range_begin = directional_lights.data[i].shadow_range_begin.w;
+							float test_radius = (range_pos - range_begin) * directional_lights.data[i].softshadow_angle;
+							vec2 tex_scale = directional_lights.data[i].uv_scale4 * test_radius;
+							float s = sample_directional_soft_shadow(directional_shadow_atlas, pssm_coord.xyz, tex_scale * directional_lights.data[i].soft_shadow_scale, scene_data.taa_frame_count);
+
+							if (blend_count == 0) {
+								shadow = s;
+							} else {
+								//blend
+								float blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+								shadow = mix(shadow, s, blend);
+							}
+						}
 
 
-						pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
-						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
-					}
+					} else { //no soft shadows
 
 
-					pssm_coord /= pssm_coord.w;
+						vec4 pssm_coord;
+						float blur_factor;
 
 
-					shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count);
+						if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+							vec4 v = vec4(vertex, 1.0);
 
 
-					if (directional_lights.data[i].blend_splits) {
-						float pssm_blend;
-						float blur_factor2;
+							BIAS_FUNC(v, 0)
 
 
-						if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+							pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+							blur_factor = 1.0;
+						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
 							vec4 v = vec4(vertex, 1.0);
 							vec4 v = vec4(vertex, 1.0);
+
 							BIAS_FUNC(v, 1)
 							BIAS_FUNC(v, 1)
+
 							pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
 							pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
-							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x - directional_lights.data[i].shadow_split_offsets.x * 0.1, directional_lights.data[i].shadow_split_offsets.x, depth_z);
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
-						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+							blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
+						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
 							vec4 v = vec4(vertex, 1.0);
 							vec4 v = vec4(vertex, 1.0);
+
 							BIAS_FUNC(v, 2)
 							BIAS_FUNC(v, 2)
+
 							pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
 							pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
-							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y - directional_lights.data[i].shadow_split_offsets.y * 0.1, directional_lights.data[i].shadow_split_offsets.y, depth_z);
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
-						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+							blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
+						} else {
 							vec4 v = vec4(vertex, 1.0);
 							vec4 v = vec4(vertex, 1.0);
+
 							BIAS_FUNC(v, 3)
 							BIAS_FUNC(v, 3)
+
 							pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
 							pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
-							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.z - directional_lights.data[i].shadow_split_offsets.z * 0.1, directional_lights.data[i].shadow_split_offsets.z, depth_z);
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
-						} else {
-							pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
-							blur_factor2 = 1.0;
+							blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
 						}
 						}
 
 
 						pssm_coord /= pssm_coord.w;
 						pssm_coord /= pssm_coord.w;
 
 
-						float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count);
-						shadow = mix(shadow, shadow2, pssm_blend);
+						shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count);
+
+						if (directional_lights.data[i].blend_splits) {
+							float pssm_blend;
+							float blur_factor2;
+
+							if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+								vec4 v = vec4(vertex, 1.0);
+								BIAS_FUNC(v, 1)
+								pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+								pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x - directional_lights.data[i].shadow_split_offsets.x * 0.1, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+								// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+								blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
+							} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+								vec4 v = vec4(vertex, 1.0);
+								BIAS_FUNC(v, 2)
+								pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+								pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y - directional_lights.data[i].shadow_split_offsets.y * 0.1, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+								// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+								blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
+							} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+								vec4 v = vec4(vertex, 1.0);
+								BIAS_FUNC(v, 3)
+								pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+								pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.z - directional_lights.data[i].shadow_split_offsets.z * 0.1, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+								// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+								blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
+							} else {
+								pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+								blur_factor2 = 1.0;
+							}
+
+							pssm_coord /= pssm_coord.w;
+
+							float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * float(directional_lights.data[i].blend_splits)), pssm_coord, scene_data.taa_frame_count);
+							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
+#ifdef USE_LIGHTMAP
+					if (shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_REPLACE) {
+						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
+					} else if (shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_OVERLAY) {
+						shadow = shadowmask * 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
+					} else {
+#endif
+						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
+#ifdef USE_LIGHTMAP
+					}
+#endif
 
 
 #ifdef USE_VERTEX_LIGHTING
 #ifdef USE_VERTEX_LIGHTING
-				diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
-				specular_light *= mix(1.0, shadow, specular_light_interp.a);
+					diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
+					specular_light *= mix(1.0, shadow, specular_light_interp.a);
 #endif
 #endif
 
 
 #undef BIAS_FUNC
 #undef BIAS_FUNC
-			} // shadows
+				} // shadows
 
 
-			if (i < 4) {
-				shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
-			} else {
-				shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+				if (i < 4) {
+					shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
+				} else {
+					shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+				}
 			}
 			}
+
+#ifdef USE_LIGHTMAP
+		} else { // shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_ONLY
+
+#ifdef USE_VERTEX_LIGHTING
+			diffuse_light *= mix(1.0, shadowmask, diffuse_light_interp.a);
+			specular_light *= mix(1.0, shadowmask, specular_light_interp.a);
+#endif
+
+			shadow0 |= uint(clamp(shadowmask * 255.0, 0.0, 255.0));
 		}
 		}
+#endif // USE_LIGHTMAP
+
 #endif // SHADOWS_DISABLED
 #endif // SHADOWS_DISABLED
 
 
 #ifndef USE_VERTEX_LIGHTING
 #ifndef USE_VERTEX_LIGHTING

+ 7 - 2
servers/rendering/renderer_rd/shaders/forward_clustered/scene_forward_clustered_inc.glsl

@@ -200,11 +200,16 @@ directional_lights;
 #define LIGHTMAP_FLAG_USE_DIRECTION 1
 #define LIGHTMAP_FLAG_USE_DIRECTION 1
 #define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
 #define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
 
 
+#define LIGHTMAP_SHADOWMASK_MODE_NONE 0
+#define LIGHTMAP_SHADOWMASK_MODE_REPLACE 1
+#define LIGHTMAP_SHADOWMASK_MODE_OVERLAY 2
+#define LIGHTMAP_SHADOWMASK_MODE_ONLY 3
+
 struct Lightmap {
 struct Lightmap {
 	mat3 normal_xform;
 	mat3 normal_xform;
 	vec2 light_texture_size;
 	vec2 light_texture_size;
 	float exposure_normalization;
 	float exposure_normalization;
-	float pad;
+	uint flags;
 };
 };
 
 
 layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
 layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
@@ -349,7 +354,7 @@ layout(set = 1, binding = 5) uniform texture2D shadow_atlas;
 
 
 layout(set = 1, binding = 6) uniform texture2D directional_shadow_atlas;
 layout(set = 1, binding = 6) uniform texture2D directional_shadow_atlas;
 
 
-layout(set = 1, binding = 7) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES];
+layout(set = 1, binding = 7) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES * 2];
 
 
 layout(set = 1, binding = 8) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
 layout(set = 1, binding = 8) uniform texture3D voxel_gi_textures[MAX_VOXEL_GI_INSTANCES];
 
 

+ 127 - 81
servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile.glsl

@@ -1434,30 +1434,54 @@ void main() {
 		uint shadow0 = 0;
 		uint shadow0 = 0;
 		uint shadow1 = 0;
 		uint shadow1 = 0;
 
 
+		float shadowmask = 1.0;
+
+#ifdef USE_LIGHTMAP
+		uint shadowmask_mode = LIGHTMAP_SHADOWMASK_MODE_NONE;
+
+		if (bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+			const uint ofs = instances.data[draw_call.instance_index].gi_offset & 0xFFFF;
+			shadowmask_mode = lightmaps.data[ofs].flags;
+
+			if (shadowmask_mode != LIGHTMAP_SHADOWMASK_MODE_NONE) {
+				const uint slice = instances.data[draw_call.instance_index].gi_offset >> 16;
+				const vec2 scaled_uv = uv2 * instances.data[draw_call.instance_index].lightmap_uv_scale.zw + instances.data[draw_call.instance_index].lightmap_uv_scale.xy;
+				const vec3 uvw = vec3(scaled_uv, float(slice));
+
+				if (sc_use_lightmap_bicubic_filter()) {
+					shadowmask = textureArray_bicubic(lightmap_textures[MAX_LIGHTMAP_TEXTURES + ofs], uvw, lightmaps.data[ofs].light_texture_size).x;
+				} else {
+					shadowmask = textureLod(sampler2DArray(lightmap_textures[MAX_LIGHTMAP_TEXTURES + ofs], SAMPLER_LINEAR_CLAMP), uvw, 0.0).x;
+				}
+			}
+		}
+
+		if (shadowmask_mode != LIGHTMAP_SHADOWMASK_MODE_ONLY) {
+#endif // USE_LIGHTMAP
+
 #ifdef USE_VERTEX_LIGHTING
 #ifdef USE_VERTEX_LIGHTING
-		// Only process the first light's shadow for vertex lighting.
-		for (uint i = 0; i < 1; i++) {
+			// Only process the first light's shadow for vertex lighting.
+			for (uint i = 0; i < 1; i++) {
 #else
 #else
 		for (uint i = 0; i < sc_directional_lights(); i++) {
 		for (uint i = 0; i < sc_directional_lights(); i++) {
 #endif
 #endif
+				if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
+					continue; //not masked
+				}
 
 
-			if (!bool(directional_lights.data[i].mask & instances.data[draw_call.instance_index].layer_mask)) {
-				continue; //not masked
-			}
-
-			if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
-				continue; // Statically baked light and object uses lightmap, skip.
-			}
+				if (directional_lights.data[i].bake_mode == LIGHT_BAKE_STATIC && bool(instances.data[draw_call.instance_index].flags & INSTANCE_FLAGS_USE_LIGHTMAP)) {
+					continue; // Statically baked light and object uses lightmap, skip.
+				}
 
 
-			float shadow = 1.0;
+				float shadow = 1.0;
 
 
-			if (directional_lights.data[i].shadow_opacity > 0.001) {
-				float depth_z = -vertex.z;
+				if (directional_lights.data[i].shadow_opacity > 0.001) {
+					float depth_z = -vertex.z;
 
 
-				vec4 pssm_coord;
-				float blur_factor;
-				vec3 light_dir = directional_lights.data[i].direction;
-				vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
+					vec4 pssm_coord;
+					float blur_factor;
+					vec3 light_dir = directional_lights.data[i].direction;
+					vec3 base_normal_bias = normalize(normal_interp) * (1.0 - max(0.0, dot(light_dir, -normalize(normal_interp))));
 
 
 #define BIAS_FUNC(m_var, m_idx)                                                                 \
 #define BIAS_FUNC(m_var, m_idx)                                                                 \
 	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                     \
 	m_var.xyz += light_dir * directional_lights.data[i].shadow_bias[m_idx];                     \
@@ -1465,97 +1489,119 @@ void main() {
 	normal_bias -= light_dir * dot(light_dir, normal_bias);                                     \
 	normal_bias -= light_dir * dot(light_dir, normal_bias);                                     \
 	m_var.xyz += normal_bias;
 	m_var.xyz += normal_bias;
 
 
-				if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
-					vec4 v = vec4(vertex, 1.0);
-
-					BIAS_FUNC(v, 0)
-
-					pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
-					blur_factor = 1.0;
-				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
-					vec4 v = vec4(vertex, 1.0);
-
-					BIAS_FUNC(v, 1)
-
-					pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
-					// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-					blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
-					;
-				} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
-					vec4 v = vec4(vertex, 1.0);
-
-					BIAS_FUNC(v, 2)
-
-					pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
-					// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-					blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
-				} else {
-					vec4 v = vec4(vertex, 1.0);
-
-					BIAS_FUNC(v, 3)
-
-					pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
-					// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-					blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
-				}
-
-				pssm_coord /= pssm_coord.w;
-
-				bool blend_split = sc_directional_light_blend_split(i);
-				float blend_split_weight = blend_split ? 1.0f : 0.0f;
-				shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * blend_split_weight), pssm_coord, scene_data.taa_frame_count);
+					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+						vec4 v = vec4(vertex, 1.0);
 
 
-				if (blend_split) {
-					float pssm_blend;
-					float blur_factor2;
+						BIAS_FUNC(v, 0)
 
 
-					if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+						pssm_coord = (directional_lights.data[i].shadow_matrix1 * v);
+						blur_factor = 1.0;
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
 						vec4 v = vec4(vertex, 1.0);
 						vec4 v = vec4(vertex, 1.0);
+
 						BIAS_FUNC(v, 1)
 						BIAS_FUNC(v, 1)
+
 						pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
 						pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
-						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x - directional_lights.data[i].shadow_split_offsets.x * 0.1, directional_lights.data[i].shadow_split_offsets.x, depth_z);
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
-					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
+					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
 						vec4 v = vec4(vertex, 1.0);
 						vec4 v = vec4(vertex, 1.0);
+
 						BIAS_FUNC(v, 2)
 						BIAS_FUNC(v, 2)
+
 						pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
 						pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
-						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y - directional_lights.data[i].shadow_split_offsets.y * 0.1, directional_lights.data[i].shadow_split_offsets.y, depth_z);
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
-					} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
+					} else {
 						vec4 v = vec4(vertex, 1.0);
 						vec4 v = vec4(vertex, 1.0);
+
 						BIAS_FUNC(v, 3)
 						BIAS_FUNC(v, 3)
+
 						pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
 						pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
-						pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.z - directional_lights.data[i].shadow_split_offsets.z * 0.1, directional_lights.data[i].shadow_split_offsets.z, depth_z);
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
 						// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
-						blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
-					} else {
-						pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
-						blur_factor2 = 1.0;
+						blur_factor = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
 					}
 					}
 
 
 					pssm_coord /= pssm_coord.w;
 					pssm_coord /= pssm_coord.w;
 
 
-					float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * blend_split_weight), pssm_coord, scene_data.taa_frame_count);
-					shadow = mix(shadow, shadow2, pssm_blend);
-				}
+					bool blend_split = sc_directional_light_blend_split(i);
+					float blend_split_weight = blend_split ? 1.0f : 0.0f;
+					shadow = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor + (1.0 - blur_factor) * blend_split_weight), pssm_coord, scene_data.taa_frame_count);
+
+					if (blend_split) {
+						float pssm_blend;
+						float blur_factor2;
+
+						if (depth_z < directional_lights.data[i].shadow_split_offsets.x) {
+							vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 1)
+							pssm_coord = (directional_lights.data[i].shadow_matrix2 * v);
+							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.x - directional_lights.data[i].shadow_split_offsets.x * 0.1, directional_lights.data[i].shadow_split_offsets.x, depth_z);
+							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.y;
+						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.y) {
+							vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 2)
+							pssm_coord = (directional_lights.data[i].shadow_matrix3 * v);
+							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.y - directional_lights.data[i].shadow_split_offsets.y * 0.1, directional_lights.data[i].shadow_split_offsets.y, depth_z);
+							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.z;
+						} else if (depth_z < directional_lights.data[i].shadow_split_offsets.z) {
+							vec4 v = vec4(vertex, 1.0);
+							BIAS_FUNC(v, 3)
+							pssm_coord = (directional_lights.data[i].shadow_matrix4 * v);
+							pssm_blend = smoothstep(directional_lights.data[i].shadow_split_offsets.z - directional_lights.data[i].shadow_split_offsets.z * 0.1, directional_lights.data[i].shadow_split_offsets.z, depth_z);
+							// Adjust shadow blur with reference to the first split to reduce discrepancy between shadow splits.
+							blur_factor2 = directional_lights.data[i].shadow_split_offsets.x / directional_lights.data[i].shadow_split_offsets.w;
+						} else {
+							pssm_blend = 0.0; //if no blend, same coord will be used (divide by z will result in same value, and already cached)
+							blur_factor2 = 1.0;
+						}
+
+						pssm_coord /= pssm_coord.w;
+
+						float shadow2 = sample_directional_pcf_shadow(directional_shadow_atlas, scene_data.directional_shadow_pixel_size * directional_lights.data[i].soft_shadow_scale * (blur_factor2 + (1.0 - blur_factor2) * blend_split_weight), pssm_coord, scene_data.taa_frame_count);
+						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
+#ifdef USE_LIGHTMAP
+					if (shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_REPLACE) {
+						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
+					} else if (shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_OVERLAY) {
+						shadow = shadowmask * 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
+					} else {
+#endif
+						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
+#ifdef USE_LIGHTMAP
+					}
+#endif
 
 
 #ifdef USE_VERTEX_LIGHTING
 #ifdef USE_VERTEX_LIGHTING
-				diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
-				specular_light *= mix(1.0, shadow, specular_light_interp.a);
+					diffuse_light *= mix(1.0, shadow, diffuse_light_interp.a);
+					specular_light *= mix(1.0, shadow, specular_light_interp.a);
 #endif
 #endif
 #undef BIAS_FUNC
 #undef BIAS_FUNC
-			}
+				}
 
 
-			if (i < 4) {
-				shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
-			} else {
-				shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+				if (i < 4) {
+					shadow0 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << (i * 8);
+				} else {
+					shadow1 |= uint(clamp(shadow * 255.0, 0.0, 255.0)) << ((i - 4) * 8);
+				}
 			}
 			}
+
+#ifdef USE_LIGHTMAP
+		} else { // shadowmask_mode == LIGHTMAP_SHADOWMASK_MODE_ONLY
+
+#ifdef USE_VERTEX_LIGHTING
+			diffuse_light *= mix(1.0, shadowmask, diffuse_light_interp.a);
+			specular_light *= mix(1.0, shadowmask, specular_light_interp.a);
+#endif
+
+			shadow0 |= uint(clamp(shadowmask * 255.0, 0.0, 255.0));
 		}
 		}
+#endif // USE_LIGHTMAP
+
 #endif // SHADOWS_DISABLED
 #endif // SHADOWS_DISABLED
 
 
 #ifndef USE_VERTEX_LIGHTING
 #ifndef USE_VERTEX_LIGHTING

+ 7 - 2
servers/rendering/renderer_rd/shaders/forward_mobile/scene_forward_mobile_inc.glsl

@@ -246,11 +246,16 @@ directional_lights;
 #define LIGHTMAP_FLAG_USE_DIRECTION 1
 #define LIGHTMAP_FLAG_USE_DIRECTION 1
 #define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
 #define LIGHTMAP_FLAG_USE_SPECULAR_DIRECTION 2
 
 
+#define LIGHTMAP_SHADOWMASK_MODE_NONE 0
+#define LIGHTMAP_SHADOWMASK_MODE_REPLACE 1
+#define LIGHTMAP_SHADOWMASK_MODE_OVERLAY 2
+#define LIGHTMAP_SHADOWMASK_MODE_ONLY 3
+
 struct Lightmap {
 struct Lightmap {
 	mediump mat3 normal_xform;
 	mediump mat3 normal_xform;
 	vec2 light_texture_size;
 	vec2 light_texture_size;
 	float exposure_normalization;
 	float exposure_normalization;
-	float pad;
+	uint flags;
 };
 };
 
 
 layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
 layout(set = 0, binding = 7, std140) restrict readonly buffer Lightmaps {
@@ -330,7 +335,7 @@ layout(set = 1, binding = 4) uniform highp texture2D shadow_atlas;
 layout(set = 1, binding = 5) uniform highp texture2D directional_shadow_atlas;
 layout(set = 1, binding = 5) uniform highp texture2D directional_shadow_atlas;
 
 
 // this needs to change to providing just the lightmap we're using..
 // this needs to change to providing just the lightmap we're using..
-layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES];
+layout(set = 1, binding = 6) uniform texture2DArray lightmap_textures[MAX_LIGHTMAP_TEXTURES * 2];
 
 
 #ifdef USE_MULTIVIEW
 #ifdef USE_MULTIVIEW
 layout(set = 1, binding = 9) uniform highp texture2DArray depth_buffer;
 layout(set = 1, binding = 9) uniform highp texture2DArray depth_buffer;

+ 66 - 2
servers/rendering/renderer_rd/storage_rd/light_storage.cpp

@@ -55,12 +55,18 @@ LightStorage::LightStorage() {
 
 
 		if (textures_per_stage <= 256) {
 		if (textures_per_stage <= 256) {
 			lightmap_textures.resize(32);
 			lightmap_textures.resize(32);
+			shadowmask_textures.resize(32);
 		} else {
 		} else {
 			lightmap_textures.resize(1024);
 			lightmap_textures.resize(1024);
+			shadowmask_textures.resize(1024);
 		}
 		}
 
 
-		for (int i = 0; i < lightmap_textures.size(); i++) {
-			lightmap_textures.write[i] = texture_storage->texture_rd_get_default(TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
+		for (RID &lightmap_texture : lightmap_textures) {
+			lightmap_texture = texture_storage->texture_rd_get_default(TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
+		}
+
+		for (RID &shadowmask_texture : shadowmask_textures) {
+			shadowmask_texture = texture_storage->texture_rd_get_default(TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
 		}
 		}
 	}
 	}
 
 
@@ -2003,6 +2009,64 @@ AABB LightStorage::lightmap_get_aabb(RID p_lightmap) const {
 	return lm->bounds;
 	return lm->bounds;
 }
 }
 
 
+void LightStorage::lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) {
+	TextureStorage *texture_storage = TextureStorage::get_singleton();
+
+	Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL(lm);
+
+	// Erase lightmap users from shadow texture.
+	if (lm->shadow_texture.is_valid()) {
+		TextureStorage::Texture *t = texture_storage->get_texture(lm->shadow_texture);
+		if (t) {
+			t->lightmap_users.erase(p_lightmap);
+		}
+	}
+
+	TextureStorage::Texture *t = texture_storage->get_texture(p_shadow);
+	lm->shadow_texture = p_shadow;
+
+	RID default_2d_array = texture_storage->texture_rd_get_default(TextureStorage::DEFAULT_RD_TEXTURE_2D_ARRAY_WHITE);
+	if (!t) {
+		if (lm->array_index >= 0) {
+			shadowmask_textures.write[lm->array_index] = default_2d_array;
+			lm->array_index = -1;
+		}
+
+		return;
+	}
+
+	t->lightmap_users.insert(p_lightmap);
+
+	if (lm->array_index < 0) {
+		// Not in array, try to put in array.
+		for (int i = 0; i < shadowmask_textures.size(); i++) {
+			if (shadowmask_textures[i] == default_2d_array) {
+				lm->array_index = i;
+				break;
+			}
+		}
+	}
+
+	ERR_FAIL_COND_MSG(lm->array_index < 0, vformat("Maximum amount of shadowmasks in use (%d) has been exceeded, shadowmask will not display properly.", shadowmask_textures.size()));
+
+	shadowmask_textures.write[lm->array_index] = t->rd_texture;
+}
+
+RS::ShadowmaskMode LightStorage::lightmap_get_shadowmask_mode(RID p_lightmap) {
+	Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL_V(lm, RS::SHADOWMASK_MODE_NONE);
+
+	return lm->shadowmask_mode;
+}
+
+void LightStorage::lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) {
+	Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+	ERR_FAIL_NULL(lm);
+
+	lm->shadowmask_mode = p_mode;
+}
+
 /* LIGHTMAP INSTANCE */
 /* LIGHTMAP INSTANCE */
 
 
 RID LightStorage::lightmap_instance_create(RID p_lightmap) {
 RID LightStorage::lightmap_instance_create(RID p_lightmap) {

+ 14 - 0
servers/rendering/renderer_rd/storage_rd/light_storage.h

@@ -329,6 +329,8 @@ private:
 
 
 	struct Lightmap {
 	struct Lightmap {
 		RID light_texture;
 		RID light_texture;
+		RID shadow_texture;
+		RS::ShadowmaskMode shadowmask_mode = RS::SHADOWMASK_MODE_NONE;
 		bool uses_spherical_harmonics = false;
 		bool uses_spherical_harmonics = false;
 		bool interior = false;
 		bool interior = false;
 		AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
 		AABB bounds = AABB(Vector3(), Vector3(1, 1, 1));
@@ -356,6 +358,8 @@ private:
 
 
 	mutable RID_Owner<Lightmap, true> lightmap_owner;
 	mutable RID_Owner<Lightmap, true> lightmap_owner;
 
 
+	Vector<RID> shadowmask_textures;
+
 	/* LIGHTMAP INSTANCE */
 	/* LIGHTMAP INSTANCE */
 
 
 	struct LightmapInstance {
 	struct LightmapInstance {
@@ -985,6 +989,10 @@ public:
 
 
 	Dependency *lightmap_get_dependency(RID p_lightmap) const;
 	Dependency *lightmap_get_dependency(RID p_lightmap) const;
 
 
+	virtual void lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) override;
+	virtual RS::ShadowmaskMode lightmap_get_shadowmask_mode(RID p_lightmap) override;
+	virtual void lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) override;
+
 	virtual float lightmap_get_probe_capture_update_speed() const override {
 	virtual float lightmap_get_probe_capture_update_speed() const override {
 		return lightmap_probe_capture_update_speed;
 		return lightmap_probe_capture_update_speed;
 	}
 	}
@@ -1027,6 +1035,12 @@ public:
 		return lightmap_textures;
 		return lightmap_textures;
 	}
 	}
 
 
+	_FORCE_INLINE_ RID shadowmask_get_texture(RID p_lightmap) const {
+		const Lightmap *lm = lightmap_owner.get_or_null(p_lightmap);
+		ERR_FAIL_NULL_V(lm, RID());
+		return lm->shadow_texture;
+	}
+
 	/* LIGHTMAP INSTANCE */
 	/* LIGHTMAP INSTANCE */
 
 
 	bool owns_lightmap_instance(RID p_rid) { return lightmap_instance_owner.owns(p_rid); }
 	bool owns_lightmap_instance(RID p_rid) { return lightmap_instance_owner.owns(p_rid); }

+ 4 - 0
servers/rendering/rendering_server_default.h

@@ -489,6 +489,10 @@ public:
 	FUNC1RC(PackedInt32Array, lightmap_get_probe_capture_bsp_tree, RID)
 	FUNC1RC(PackedInt32Array, lightmap_get_probe_capture_bsp_tree, RID)
 	FUNC1(lightmap_set_probe_capture_update_speed, float)
 	FUNC1(lightmap_set_probe_capture_update_speed, float)
 
 
+	FUNC2(lightmap_set_shadowmask_textures, RID, RID)
+	FUNC1R(ShadowmaskMode, lightmap_get_shadowmask_mode, RID)
+	FUNC2(lightmap_set_shadowmask_mode, RID, ShadowmaskMode)
+
 	/* Shadow Atlas */
 	/* Shadow Atlas */
 	FUNC0R(RID, shadow_atlas_create)
 	FUNC0R(RID, shadow_atlas_create)
 	FUNC3(shadow_atlas_set_size, RID, int, bool)
 	FUNC3(shadow_atlas_set_size, RID, int, bool)

+ 4 - 0
servers/rendering/storage/light_storage.h

@@ -175,6 +175,10 @@ public:
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
 	virtual float lightmap_get_probe_capture_update_speed() const = 0;
 	virtual float lightmap_get_probe_capture_update_speed() const = 0;
 
 
+	virtual void lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) = 0;
+	virtual RS::ShadowmaskMode lightmap_get_shadowmask_mode(RID p_lightmap) = 0;
+	virtual void lightmap_set_shadowmask_mode(RID p_lightmap, RS::ShadowmaskMode p_mode) = 0;
+
 	/* LIGHTMAP INSTANCE */
 	/* LIGHTMAP INSTANCE */
 
 
 	virtual RID lightmap_instance_create(RID p_lightmap) = 0;
 	virtual RID lightmap_instance_create(RID p_lightmap) = 0;

+ 11 - 1
servers/rendering_server.h

@@ -709,6 +709,13 @@ public:
 
 
 	/* LIGHTMAP */
 	/* LIGHTMAP */
 
 
+	enum ShadowmaskMode {
+		SHADOWMASK_MODE_NONE,
+		SHADOWMASK_MODE_REPLACE,
+		SHADOWMASK_MODE_OVERLAY,
+		SHADOWMASK_MODE_ONLY,
+	};
+
 	virtual RID lightmap_create() = 0;
 	virtual RID lightmap_create() = 0;
 
 
 	virtual void lightmap_set_textures(RID p_lightmap, RID p_light, bool p_uses_spherical_haromics) = 0;
 	virtual void lightmap_set_textures(RID p_lightmap, RID p_light, bool p_uses_spherical_haromics) = 0;
@@ -722,9 +729,12 @@ public:
 	virtual PackedInt32Array lightmap_get_probe_capture_bsp_tree(RID p_lightmap) const = 0;
 	virtual PackedInt32Array lightmap_get_probe_capture_bsp_tree(RID p_lightmap) const = 0;
 
 
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
 	virtual void lightmap_set_probe_capture_update_speed(float p_speed) = 0;
-
 	virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
 	virtual void lightmaps_set_bicubic_filter(bool p_enable) = 0;
 
 
+	virtual void lightmap_set_shadowmask_textures(RID p_lightmap, RID p_shadow) = 0;
+	virtual ShadowmaskMode lightmap_get_shadowmask_mode(RID p_lightmap) = 0;
+	virtual void lightmap_set_shadowmask_mode(RID p_lightmap, ShadowmaskMode p_mode) = 0;
+
 	/* PARTICLES API */
 	/* PARTICLES API */
 
 
 	virtual RID particles_create() = 0;
 	virtual RID particles_create() = 0;