Browse Source

Merge pull request #46932 from JFonS/fix_lm_capture_env

[3.2] Batch of lightmapper fixes and minor improvements
Rémi Verschelde 4 years ago
parent
commit
3f246ebeed

+ 3 - 1
doc/classes/BakedLightmap.xml

@@ -5,7 +5,6 @@
 	</brief_description>
 	<description>
 		Baked lightmaps are an alternative workflow for adding indirect (or baked) lighting to a scene. Unlike the [GIProbe] approach, baked lightmaps work fine on low-end PCs and mobile devices as they consume almost no resources in run-time.
-		[b]Note:[/b] This node has many known bugs and will be [url=https://godotengine.org/article/godot-40-will-get-new-modernized-lightmapper]rewritten for Godot 4.0[/url]. See [url=https://github.com/godotengine/godot/issues/30929]GitHub issue #30929[/url].
 	</description>
 	<tutorials>
 		<link>https://docs.godotengine.org/en/3.2/tutorials/3d/baked_lightmaps.html</link>
@@ -63,6 +62,9 @@
 		<member name="environment_custom_sky_rotation_degrees" type="Vector3" setter="set_environment_custom_sky_rotation_degrees" getter="get_environment_custom_sky_rotation_degrees">
 			The rotation of the baked custom sky.
 		</member>
+		<member name="environment_min_light" type="Color" setter="set_environment_min_light" getter="get_environment_min_light" default="Color( 0, 0, 0, 1 )">
+			Minimum ambient light for all the lightmap texels. This doesn't take into account any occlusion from the scene's geometry, it simply ensures a minimum amount of light on all the lightmap texels. Can be used for artistic control on shadow color.
+		</member>
 		<member name="environment_mode" type="int" setter="set_environment_mode" getter="get_environment_mode" enum="BakedLightmap.EnvironmentMode" default="0">
 			Decides which environment to use during baking.
 		</member>

+ 4 - 0
doc/classes/BakedLightmapData.xml

@@ -66,6 +66,10 @@
 		<member name="cell_subdiv" type="int" setter="set_cell_subdiv" getter="get_cell_subdiv" default="1">
 		</member>
 		<member name="energy" type="float" setter="set_energy" getter="get_energy" default="1.0">
+			Global energy multiplier for baked and dynamic capture objects.
+		</member>
+		<member name="interior" type="bool" setter="set_interior" getter="is_interior" default="false">
+			Controls whether dynamic capture objects receive environment lighting or not.
 		</member>
 		<member name="octree" type="PoolByteArray" setter="set_octree" getter="get_octree" default="PoolByteArray(  )">
 		</member>

+ 20 - 0
doc/classes/VisualServer.xml

@@ -2323,6 +2323,15 @@
 				Returns the cell transform for this lightmap capture's octree.
 			</description>
 		</method>
+		<method name="lightmap_capture_is_interior" qualifiers="const">
+			<return type="bool">
+			</return>
+			<argument index="0" name="capture" type="RID">
+			</argument>
+			<description>
+				Returns [code]true[/code] if capture is in "interior" mode.
+			</description>
+		</method>
 		<method name="lightmap_capture_set_bounds">
 			<return type="void">
 			</return>
@@ -2345,6 +2354,17 @@
 				Sets the energy multiplier for this lightmap capture. Equivalent to [member BakedLightmapData.energy].
 			</description>
 		</method>
+		<method name="lightmap_capture_set_interior">
+			<return type="void">
+			</return>
+			<argument index="0" name="capture" type="RID">
+			</argument>
+			<argument index="1" name="interior" type="bool">
+			</argument>
+			<description>
+				Sets the "interior" mode for this lightmap capture. Equivalent to [member BakedLightmapData.interior].
+			</description>
+		</method>
 		<method name="lightmap_capture_set_octree">
 			<return type="void">
 			</return>

+ 2 - 0
drivers/dummy/rasterizer_dummy.h

@@ -655,6 +655,8 @@ public:
 	int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const { return 0; }
 	void lightmap_capture_set_energy(RID p_capture, float p_energy) {}
 	float lightmap_capture_get_energy(RID p_capture) const { return 0.0; }
+	void lightmap_capture_set_interior(RID p_capture, bool p_interior) {}
+	bool lightmap_capture_is_interior(RID p_capture) const { return false; }
 	const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const {
 		const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
 		ERR_FAIL_COND_V(!capture, NULL);

+ 0 - 1
drivers/gles2/rasterizer_scene_gles2.cpp

@@ -2598,7 +2598,6 @@ void RasterizerSceneGLES2::_render_render_list(RenderList::Element **p_elements,
 
 		if (use_lightmap_capture) { //this is per instance, must be set always if present
 			glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES2::LIGHTMAP_CAPTURES), 12, (const GLfloat *)e->instance->lightmap_capture_data.ptr());
-			state.scene_shader.set_uniform(SceneShaderGLES2::LIGHTMAP_CAPTURE_SKY, false);
 		}
 
 		_render_geometry(e);

+ 29 - 0
drivers/gles2/rasterizer_storage_gles2.cpp

@@ -4521,6 +4521,10 @@ void RasterizerStorageGLES2::lightmap_capture_set_energy(RID p_capture, float p_
 	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
 	ERR_FAIL_COND(!capture);
 	capture->energy = p_energy;
+
+	if (!capture->update_list.in_list()) {
+		capture_update_list.add(&capture->update_list);
+	}
 }
 
 float RasterizerStorageGLES2::lightmap_capture_get_energy(RID p_capture) const {
@@ -4530,6 +4534,30 @@ float RasterizerStorageGLES2::lightmap_capture_get_energy(RID p_capture) const {
 	return capture->energy;
 }
 
+void RasterizerStorageGLES2::lightmap_capture_set_interior(RID p_capture, bool p_interior) {
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->interior = p_interior;
+
+	if (!capture->update_list.in_list()) {
+		capture_update_list.add(&capture->update_list);
+	}
+}
+
+bool RasterizerStorageGLES2::lightmap_capture_is_interior(RID p_capture) const {
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, false);
+	return capture->interior;
+}
+
+void RasterizerStorageGLES2::update_dirty_captures() {
+	while (capture_update_list.first()) {
+		LightmapCapture *capture = capture_update_list.first()->self();
+		capture->instance_change_notify(false, true);
+		capture_update_list.remove(capture_update_list.first());
+	}
+}
+
 const PoolVector<RasterizerStorage::LightmapCaptureOctree> *RasterizerStorageGLES2::lightmap_capture_get_octree_ptr(RID p_capture) const {
 	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
 	ERR_FAIL_COND_V(!capture, NULL);
@@ -6361,6 +6389,7 @@ void RasterizerStorageGLES2::update_dirty_resources() {
 	update_dirty_materials();
 	update_dirty_skeletons();
 	update_dirty_multimeshes();
+	update_dirty_captures();
 }
 
 RasterizerStorageGLES2::RasterizerStorageGLES2() {

+ 14 - 1
drivers/gles2/rasterizer_storage_gles2.h

@@ -1096,12 +1096,22 @@ public:
 		Transform cell_xform;
 		int cell_subdiv;
 		float energy;
-		LightmapCapture() {
+		bool interior;
+
+		SelfList<LightmapCapture> update_list;
+
+		LightmapCapture() :
+				update_list(this) {
 			energy = 1.0;
 			cell_subdiv = 1;
+			interior = false;
 		}
 	};
 
+	SelfList<LightmapCapture>::List capture_update_list;
+
+	void update_dirty_captures();
+
 	mutable RID_Owner<LightmapCapture> lightmap_capture_data_owner;
 
 	virtual RID lightmap_capture_create();
@@ -1115,6 +1125,9 @@ public:
 	virtual int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const;
 	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy);
 	virtual float lightmap_capture_get_energy(RID p_capture) const;
+	virtual void lightmap_capture_set_interior(RID p_capture, bool p_interior);
+	virtual bool lightmap_capture_is_interior(RID p_capture) const;
+
 	virtual const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const;
 
 	/* PARTICLES */

+ 3 - 4
drivers/gles2/shaders/scene.glsl

@@ -956,8 +956,6 @@ vec4 texture2D_bicubic(sampler2D tex, vec2 uv) {
 
 #ifdef USE_LIGHTMAP_CAPTURE
 uniform mediump vec4 lightmap_captures[12];
-uniform bool lightmap_capture_sky;
-
 #endif
 
 #ifdef USE_RADIANCE_MAP
@@ -1781,8 +1779,9 @@ FRAGMENT_SHADER_CODE
 
 		captured /= sum;
 
-		if (lightmap_capture_sky) {
-			ambient_light = mix(ambient_light, captured.rgb, captured.a);
+		// Alpha channel is used to indicate if dynamic objects keep the environment lighting
+		if (lightmap_captures[0].a > 0.5) {
+			ambient_light += captured.rgb;
 		} else {
 			ambient_light = captured.rgb;
 		}

+ 0 - 1
drivers/gles3/rasterizer_scene_gles3.cpp

@@ -1941,7 +1941,6 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform
 	} else if (!e->instance->lightmap_capture_data.empty()) {
 
 		glUniform4fv(state.scene_shader.get_uniform_location(SceneShaderGLES3::LIGHTMAP_CAPTURES), 12, (const GLfloat *)e->instance->lightmap_capture_data.ptr());
-		state.scene_shader.set_uniform(SceneShaderGLES3::LIGHTMAP_CAPTURE_SKY, false);
 
 	} else if (e->instance->lightmap.is_valid()) {
 		RasterizerStorageGLES3::Texture *lightmap = storage->texture_owner.getornull(e->instance->lightmap);

+ 28 - 0
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -6385,6 +6385,10 @@ void RasterizerStorageGLES3::lightmap_capture_set_energy(RID p_capture, float p_
 	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
 	ERR_FAIL_COND(!capture);
 	capture->energy = p_energy;
+
+	if (!capture->update_list.in_list()) {
+		capture_update_list.add(&capture->update_list);
+	}
 }
 
 float RasterizerStorageGLES3::lightmap_capture_get_energy(RID p_capture) const {
@@ -6394,12 +6398,35 @@ float RasterizerStorageGLES3::lightmap_capture_get_energy(RID p_capture) const {
 	return capture->energy;
 }
 
+void RasterizerStorageGLES3::lightmap_capture_set_interior(RID p_capture, bool p_interior) {
+	LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND(!capture);
+	capture->interior = p_interior;
+	if (!capture->update_list.in_list()) {
+		capture_update_list.add(&capture->update_list);
+	}
+}
+
+bool RasterizerStorageGLES3::lightmap_capture_is_interior(RID p_capture) const {
+	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
+	ERR_FAIL_COND_V(!capture, false);
+	return capture->interior;
+}
+
 const PoolVector<RasterizerStorage::LightmapCaptureOctree> *RasterizerStorageGLES3::lightmap_capture_get_octree_ptr(RID p_capture) const {
 	const LightmapCapture *capture = lightmap_capture_data_owner.getornull(p_capture);
 	ERR_FAIL_COND_V(!capture, NULL);
 	return &capture->octree;
 }
 
+void RasterizerStorageGLES3::update_dirty_captures() {
+	while (capture_update_list.first()) {
+		LightmapCapture *capture = capture_update_list.first()->self();
+		capture->instance_change_notify(false, true);
+		capture_update_list.remove(capture_update_list.first());
+	}
+}
+
 ///////
 
 RID RasterizerStorageGLES3::particles_create() {
@@ -8591,6 +8618,7 @@ void RasterizerStorageGLES3::update_dirty_resources() {
 	update_dirty_shaders();
 	update_dirty_materials();
 	update_particles();
+	update_dirty_captures();
 }
 
 RasterizerStorageGLES3::RasterizerStorageGLES3() {

+ 13 - 1
drivers/gles3/rasterizer_storage_gles3.h

@@ -1152,6 +1152,8 @@ public:
 
 	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy);
 	virtual float lightmap_capture_get_energy(RID p_capture) const;
+	virtual void lightmap_capture_set_interior(RID p_capture, bool p_interior);
+	virtual bool lightmap_capture_is_interior(RID p_capture) const;
 
 	virtual const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const;
 
@@ -1162,12 +1164,22 @@ public:
 		Transform cell_xform;
 		int cell_subdiv;
 		float energy;
-		LightmapCapture() {
+		bool interior;
+
+		SelfList<LightmapCapture> update_list;
+
+		LightmapCapture() :
+				update_list(this) {
 			energy = 1.0;
 			cell_subdiv = 1;
+			interior = false;
 		}
 	};
 
+	SelfList<LightmapCapture>::List capture_update_list;
+
+	void update_dirty_captures();
+
 	mutable RID_Owner<LightmapCapture> lightmap_capture_data_owner;
 
 	/* PARTICLES */

+ 3 - 4
drivers/gles3/shaders/scene.glsl

@@ -1548,8 +1548,6 @@ vec4 textureArray_bicubic(sampler2DArray tex, vec3 uv) {
 
 #ifdef USE_LIGHTMAP_CAPTURE
 uniform mediump vec4[12] lightmap_captures;
-uniform bool lightmap_capture_sky;
-
 #endif
 
 #ifdef USE_GI_PROBES
@@ -1964,8 +1962,9 @@ FRAGMENT_SHADER_CODE
 
 		captured /= sum;
 
-		if (lightmap_capture_sky) {
-			ambient_light = mix(ambient_light, captured.rgb, captured.a);
+		// Alpha channel is used to indicate if dynamic objects keep the environment lighting
+		if (lightmap_captures[0].a > 0.5) {
+			ambient_light += captured.rgb;
 		} else {
 			ambient_light = captured.rgb;
 		}

+ 3 - 0
modules/gridmap/doc_classes/GridMap.xml

@@ -230,6 +230,9 @@
 		<member name="mesh_library" type="MeshLibrary" setter="set_mesh_library" getter="get_mesh_library">
 			The assigned [MeshLibrary].
 		</member>
+		<member name="use_in_baked_light" type="bool" setter="set_use_in_baked_light" getter="get_use_in_baked_light" default="false">
+			Controls whether this GridMap will be baked in a [BakedLightmap] or not.
+		</member>
 	</members>
 	<signals>
 		<signal name="cell_size_changed">

+ 18 - 0
modules/gridmap/grid_map.cpp

@@ -210,6 +210,14 @@ Ref<MeshLibrary> GridMap::get_mesh_library() const {
 	return mesh_library;
 }
 
+void GridMap::set_use_in_baked_light(bool p_use_baked_light) {
+	use_in_baked_light = p_use_baked_light;
+}
+
+bool GridMap::get_use_in_baked_light() const {
+	return use_in_baked_light;
+}
+
 void GridMap::set_cell_size(const Vector3 &p_size) {
 	ERR_FAIL_COND(p_size.x < 0.001 || p_size.y < 0.001 || p_size.z < 0.001);
 	cell_size = p_size;
@@ -864,7 +872,11 @@ void GridMap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("clear_baked_meshes"), &GridMap::clear_baked_meshes);
 	ClassDB::bind_method(D_METHOD("make_baked_meshes", "gen_lightmap_uv", "lightmap_uv_texel_size"), &GridMap::make_baked_meshes, DEFVAL(false), DEFVAL(0.1));
 
+	ClassDB::bind_method(D_METHOD("set_use_in_baked_light", "use_in_baked_light"), &GridMap::set_use_in_baked_light);
+	ClassDB::bind_method(D_METHOD("get_use_in_baked_light"), &GridMap::get_use_in_baked_light);
+
 	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, "MeshLibrary"), "set_mesh_library", "get_mesh_library");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_in_baked_light"), "set_use_in_baked_light", "get_use_in_baked_light");
 	ADD_GROUP("Cell", "cell_");
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "cell_size"), "set_cell_size", "get_cell_size");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_octant_size", PROPERTY_HINT_RANGE, "1,1024,1"), "set_octant_size", "get_octant_size");
@@ -1066,6 +1078,10 @@ void GridMap::make_baked_meshes(bool p_gen_lightmap_uv, float p_lightmap_uv_texe
 
 Array GridMap::get_bake_meshes() {
 
+	if (!use_in_baked_light) {
+		return Array();
+	}
+
 	if (!baked_meshes.size()) {
 		make_baked_meshes(true);
 	}
@@ -1107,6 +1123,8 @@ GridMap::GridMap() {
 	navigation = NULL;
 	set_notify_transform(true);
 	recreating_octants = false;
+
+	use_in_baked_light = false;
 }
 
 GridMap::~GridMap() {

+ 4 - 0
modules/gridmap/grid_map.h

@@ -158,6 +158,7 @@ class GridMap : public Spatial {
 	Vector3::Axis clip_axis;
 
 	Ref<MeshLibrary> mesh_library;
+	bool use_in_baked_light;
 
 	Map<OctantKey, Octant *> octant_map;
 	Map<IndexKey, Cell> cell_map;
@@ -230,6 +231,9 @@ public:
 	void set_mesh_library(const Ref<MeshLibrary> &p_mesh_library);
 	Ref<MeshLibrary> get_mesh_library() const;
 
+	void set_use_in_baked_light(bool p_use_baked_light);
+	bool get_use_in_baked_light() const;
+
 	void set_cell_size(const Vector3 &p_size);
 	Vector3 get_cell_size() const;
 

+ 7 - 0
modules/raycast/lightmap_raycaster.cpp

@@ -33,6 +33,7 @@
 // From Embree.
 #include <math/vec2.h>
 #include <math/vec3.h>
+#include <xmmintrin.h>
 
 using namespace embree;
 
@@ -185,12 +186,18 @@ void embree_error_handler(void *p_user_data, RTCError p_code, const char *p_str)
 }
 
 LightmapRaycasterEmbree::LightmapRaycasterEmbree() {
+	_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON);
+	_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON);
+
 	embree_device = rtcNewDevice(nullptr);
 	rtcSetDeviceErrorFunction(embree_device, &embree_error_handler, nullptr);
 	embree_scene = rtcNewScene(embree_device);
 }
 
 LightmapRaycasterEmbree::~LightmapRaycasterEmbree() {
+	_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_OFF);
+	_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_OFF);
+
 	if (embree_scene != nullptr)
 		rtcReleaseScene(embree_scene);
 	if (embree_device != nullptr)

+ 48 - 16
scene/3d/baked_lightmap.cpp

@@ -87,6 +87,17 @@ float BakedLightmapData::get_energy() const {
 	return energy;
 }
 
+void BakedLightmapData::set_interior(bool p_interior) {
+
+	interior = p_interior;
+	VS::get_singleton()->lightmap_capture_set_interior(baked_light, interior);
+}
+
+bool BakedLightmapData::is_interior() const {
+
+	return interior;
+}
+
 void BakedLightmapData::add_user(const NodePath &p_path, const Ref<Resource> &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance) {
 
 	ERR_FAIL_COND_MSG(p_lightmap.is_null(), "It's not a reference to a valid Texture object.");
@@ -230,6 +241,9 @@ void BakedLightmapData::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_energy", "energy"), &BakedLightmapData::set_energy);
 	ClassDB::bind_method(D_METHOD("get_energy"), &BakedLightmapData::get_energy);
 
+	ClassDB::bind_method(D_METHOD("set_interior", "interior"), &BakedLightmapData::set_interior);
+	ClassDB::bind_method(D_METHOD("is_interior"), &BakedLightmapData::is_interior);
+
 	ClassDB::bind_method(D_METHOD("add_user", "path", "lightmap", "lightmap_slice", "lightmap_uv_rect", "instance"), &BakedLightmapData::add_user);
 	ClassDB::bind_method(D_METHOD("get_user_count"), &BakedLightmapData::get_user_count);
 	ClassDB::bind_method(D_METHOD("get_user_path", "user_idx"), &BakedLightmapData::get_user_path);
@@ -241,6 +255,7 @@ void BakedLightmapData::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM, "cell_space_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_space_transform", "get_cell_space_transform");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "cell_subdiv", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_cell_subdiv", "get_cell_subdiv");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "energy", PROPERTY_HINT_RANGE, "0,16,0.01,or_greater"), "set_energy", "get_energy");
+	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interior"), "set_interior", "is_interior");
 	ADD_PROPERTY(PropertyInfo(Variant::POOL_BYTE_ARRAY, "octree", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR), "set_octree", "get_octree");
 	ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "user_data", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_user_data", "_get_user_data");
 }
@@ -250,6 +265,7 @@ BakedLightmapData::BakedLightmapData() {
 	baked_light = VS::get_singleton()->lightmap_capture_create();
 	energy = 1;
 	cell_subdiv = 1;
+	interior = false;
 }
 
 BakedLightmapData::~BakedLightmapData() {
@@ -464,7 +480,7 @@ void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightm
 		}
 
 		Ref<Texture> albedo_texture;
-		Color albedo_add = Color(0, 0, 0, 0);
+		Color albedo_add = Color(1, 1, 1, 1);
 		Color albedo_mul = Color(1, 1, 1, 1);
 
 		Ref<Texture> emission_texture;
@@ -477,6 +493,7 @@ void BakedLightmap::_get_material_images(const MeshesFound &p_found_mesh, Lightm
 
 			if (albedo_texture.is_valid()) {
 				albedo_mul = mat->get_albedo();
+				albedo_add = Color(0, 0, 0, 0);
 			} else {
 				albedo_add = mat->get_albedo();
 			}
@@ -531,25 +548,27 @@ void BakedLightmap::_save_image(String &r_base_path, Ref<Image> r_img, bool p_us
 
 	bool hdr_grayscale = use_hdr && !use_color;
 
-	if (p_use_srgb || hdr_grayscale) {
-		r_img->lock();
-		for (int i = 0; i < r_img->get_height(); i++) {
-			for (int j = 0; j < r_img->get_width(); j++) {
-				Color c = r_img->get_pixel(j, i);
+	r_img->lock();
+	for (int i = 0; i < r_img->get_height(); i++) {
+		for (int j = 0; j < r_img->get_width(); j++) {
+			Color c = r_img->get_pixel(j, i);
 
-				if (hdr_grayscale) {
-					c = Color(c.get_v(), 0.0f, 0.0f);
-				}
+			c.r = MAX(c.r, environment_min_light.r);
+			c.g = MAX(c.g, environment_min_light.g);
+			c.b = MAX(c.b, environment_min_light.b);
 
-				if (p_use_srgb) {
-					c = c.to_srgb();
-				}
+			if (hdr_grayscale) {
+				c = Color(c.get_v(), 0.0f, 0.0f);
+			}
 
-				r_img->set_pixel(j, i, c);
+			if (p_use_srgb) {
+				c = c.to_srgb();
 			}
+
+			r_img->set_pixel(j, i, c);
 		}
-		r_img->unlock();
 	}
+	r_img->unlock();
 
 	if (!use_color) {
 		if (use_hdr) {
@@ -792,7 +811,6 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 			case ENVIRONMENT_MODE_CUSTOM_SKY: {
 				if (environment_custom_sky.is_valid()) {
 					environment_image = _get_irradiance_from_sky(environment_custom_sky, Vector2i(128, 64));
-					print_line(vformat("env -> %s", environment_custom_sky_rotation_degrees));
 					environment_xform.set_euler(environment_custom_sky_rotation_degrees * Math_PI / 180.0);
 				}
 
@@ -804,12 +822,13 @@ BakedLightmap::BakeError BakedLightmap::bake(Node *p_from_node, String p_data_sa
 				c.r *= environment_custom_energy;
 				c.g *= environment_custom_energy;
 				c.b *= environment_custom_energy;
+				environment_image->lock();
 				for (int i = 0; i < 128; i++) {
 					for (int j = 0; j < 64; j++) {
 						environment_image->set_pixel(i, j, c);
 					}
 				}
-
+				environment_image->unlock();
 			} break;
 		}
 	}
@@ -1409,6 +1428,14 @@ float BakedLightmap::get_environment_custom_energy() const {
 	return environment_custom_energy;
 }
 
+void BakedLightmap::set_environment_min_light(Color p_min_light) {
+	environment_min_light = p_min_light;
+}
+
+Color BakedLightmap::get_environment_min_light() const {
+	return environment_min_light;
+}
+
 void BakedLightmap::set_bounces(int p_bounces) {
 	ERR_FAIL_COND(p_bounces < 0 || p_bounces > 16);
 	bounces = p_bounces;
@@ -1486,6 +1513,9 @@ void BakedLightmap::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_environment_custom_energy", "energy"), &BakedLightmap::set_environment_custom_energy);
 	ClassDB::bind_method(D_METHOD("get_environment_custom_energy"), &BakedLightmap::get_environment_custom_energy);
 
+	ClassDB::bind_method(D_METHOD("set_environment_min_light", "min_light"), &BakedLightmap::set_environment_min_light);
+	ClassDB::bind_method(D_METHOD("get_environment_min_light"), &BakedLightmap::get_environment_min_light);
+
 	ClassDB::bind_method(D_METHOD("set_use_denoiser", "use_denoiser"), &BakedLightmap::set_use_denoiser);
 	ClassDB::bind_method(D_METHOD("is_using_denoiser"), &BakedLightmap::is_using_denoiser);
 
@@ -1545,6 +1575,7 @@ void BakedLightmap::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "environment_custom_sky_rotation_degrees", PROPERTY_HINT_NONE), "set_environment_custom_sky_rotation_degrees", "get_environment_custom_sky_rotation_degrees");
 	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "environment_custom_color", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_custom_color", "get_environment_custom_color");
 	ADD_PROPERTY(PropertyInfo(Variant::REAL, "environment_custom_energy", PROPERTY_HINT_RANGE, "0,64,0.01"), "set_environment_custom_energy", "get_environment_custom_energy");
+	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "environment_min_light", PROPERTY_HINT_COLOR_NO_ALPHA), "set_environment_min_light", "get_environment_min_light");
 
 	ADD_GROUP("Capture", "capture_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "capture_enabled"), "set_capture_enabled", "get_capture_enabled");
@@ -1595,6 +1626,7 @@ BakedLightmap::BakedLightmap() {
 	environment_mode = ENVIRONMENT_MODE_DISABLED;
 	environment_custom_color = Color(0.2, 0.7, 1.0);
 	environment_custom_energy = 1.0;
+	environment_min_light = Color(0.0, 0.0, 0.0);
 
 	use_denoiser = true;
 	use_hdr = true;

+ 8 - 0
scene/3d/baked_lightmap.h

@@ -44,6 +44,7 @@ class BakedLightmapData : public Resource {
 	RID baked_light;
 	AABB bounds;
 	float energy;
+	bool interior;
 	int cell_subdiv;
 	Transform cell_space_xform;
 
@@ -83,6 +84,9 @@ public:
 	void set_energy(float p_energy);
 	float get_energy() const;
 
+	void set_interior(bool p_interior);
+	bool is_interior() const;
+
 	void add_user(const NodePath &p_path, const Ref<Resource> &p_lightmap, int p_lightmap_slice, const Rect2 &p_lightmap_uv_rect, int p_instance);
 	int get_user_count() const;
 	NodePath get_user_path(int p_user) const;
@@ -169,6 +173,7 @@ private:
 	Vector3 environment_custom_sky_rotation_degrees;
 	Color environment_custom_color;
 	float environment_custom_energy;
+	Color environment_min_light;
 
 	BakeQuality capture_quality;
 	float capture_propagation;
@@ -247,6 +252,9 @@ public:
 	void set_environment_custom_energy(float p_energy);
 	float get_environment_custom_energy() const;
 
+	void set_environment_min_light(Color p_min_light);
+	Color get_environment_min_light() const;
+
 	void set_use_denoiser(bool p_enable);
 	bool is_using_denoiser() const;
 

+ 2 - 0
servers/visual/rasterizer.h

@@ -514,6 +514,8 @@ public:
 	virtual int lightmap_capture_get_octree_cell_subdiv(RID p_capture) const = 0;
 	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy) = 0;
 	virtual float lightmap_capture_get_energy(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_interior(RID p_capture, bool p_interior) = 0;
+	virtual bool lightmap_capture_is_interior(RID p_capture) const = 0;
 	virtual const PoolVector<LightmapCaptureOctree> *lightmap_capture_get_octree_ptr(RID p_capture) const = 0;
 
 	/* PARTICLES */

+ 3 - 0
servers/visual/visual_server_raster.h

@@ -396,6 +396,9 @@ public:
 	BIND2(lightmap_capture_set_energy, RID, float)
 	BIND1RC(float, lightmap_capture_get_energy, RID)
 
+	BIND2(lightmap_capture_set_interior, RID, bool)
+	BIND1RC(bool, lightmap_capture_is_interior, RID)
+
 	/* PARTICLES */
 
 	BIND0R(RID, particles_create)

+ 10 - 0
servers/visual/visual_server_scene.cpp

@@ -1085,6 +1085,13 @@ void VisualServerScene::_update_instance(Instance *p_instance) {
 		VSG::storage->particles_set_emission_transform(p_instance->base, p_instance->transform);
 	}
 
+	if (p_instance->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE) {
+		InstanceLightmapCaptureData *capture = static_cast<InstanceLightmapCaptureData *>(p_instance->base_data);
+		for (List<InstanceLightmapCaptureData::PairInfo>::Element *E = capture->geometries.front(); E; E = E->next()) {
+			_instance_queue_update(E->get().geometry, false, true);
+		}
+	}
+
 	if (p_instance->aabb.has_no_surface()) {
 		return;
 	}
@@ -1441,6 +1448,7 @@ void VisualServerScene::_update_instance_lightmap_captures(Instance *p_instance)
 	for (int i = 0; i < 12; i++)
 		new (&p_instance->lightmap_capture_data.ptrw()[i]) Color;
 
+	bool interior = true;
 	//this could use some sort of blending..
 	for (List<Instance *>::Element *E = geom->lightmap_captures.front(); E; E = E->next()) {
 		const PoolVector<RasterizerStorage::LightmapCaptureOctree> *octree = VSG::storage->lightmap_capture_get_octree_ptr(E->get()->base);
@@ -1456,6 +1464,7 @@ void VisualServerScene::_update_instance_lightmap_captures(Instance *p_instance)
 		Vector3 pos = to_cell_xform.xform(p_instance->transform.origin);
 
 		const float capture_energy = VSG::storage->lightmap_capture_get_energy(E->get()->base);
+		interior = interior && VSG::storage->lightmap_capture_is_interior(E->get()->base);
 
 		for (int i = 0; i < 12; i++) {
 
@@ -1467,6 +1476,7 @@ void VisualServerScene::_update_instance_lightmap_captures(Instance *p_instance)
 			p_instance->lightmap_capture_data.write[i] += capture;
 		}
 	}
+	p_instance->lightmap_capture_data.write[0].a = interior ? 0.0f : 1.0f;
 }
 
 bool VisualServerScene::_light_instance_update_shadow(Instance *p_instance, const Transform p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_orthogonal, RID p_shadow_atlas, Scenario *p_scenario) {

+ 2 - 0
servers/visual/visual_server_wrap_mt.h

@@ -330,6 +330,8 @@ public:
 	FUNC1RC(int, lightmap_capture_get_octree_cell_subdiv, RID)
 	FUNC2(lightmap_capture_set_energy, RID, float)
 	FUNC1RC(float, lightmap_capture_get_energy, RID)
+	FUNC2(lightmap_capture_set_interior, RID, bool)
+	FUNC1RC(bool, lightmap_capture_is_interior, RID)
 
 	/* PARTICLES */
 

+ 2 - 0
servers/visual_server.cpp

@@ -1830,6 +1830,8 @@ void VisualServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("lightmap_capture_get_octree", "capture"), &VisualServer::lightmap_capture_get_octree);
 	ClassDB::bind_method(D_METHOD("lightmap_capture_set_energy", "capture", "energy"), &VisualServer::lightmap_capture_set_energy);
 	ClassDB::bind_method(D_METHOD("lightmap_capture_get_energy", "capture"), &VisualServer::lightmap_capture_get_energy);
+	ClassDB::bind_method(D_METHOD("lightmap_capture_set_interior", "capture", "interior"), &VisualServer::lightmap_capture_set_interior);
+	ClassDB::bind_method(D_METHOD("lightmap_capture_is_interior", "capture"), &VisualServer::lightmap_capture_is_interior);
 #endif
 	ClassDB::bind_method(D_METHOD("particles_create"), &VisualServer::particles_create);
 	ClassDB::bind_method(D_METHOD("particles_set_emitting", "particles", "emitting"), &VisualServer::particles_set_emitting);

+ 2 - 0
servers/visual_server.h

@@ -558,6 +558,8 @@ public:
 	virtual PoolVector<uint8_t> lightmap_capture_get_octree(RID p_capture) const = 0;
 	virtual void lightmap_capture_set_energy(RID p_capture, float p_energy) = 0;
 	virtual float lightmap_capture_get_energy(RID p_capture) const = 0;
+	virtual void lightmap_capture_set_interior(RID p_capture, bool p_interior) = 0;
+	virtual bool lightmap_capture_is_interior(RID p_capture) const = 0;
 
 	/* PARTICLES API */