Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix dynamic object light leaking in LightmapGI when Interior is enabled #103023

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/classes/LightmapGI.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
[b]Note:[/b] Regardless of [member generate_probes_subdiv], direct lighting on dynamic objects is always applied using [Light3D] nodes in real-time.
</member>
<member name="interior" type="bool" setter="set_interior" getter="is_interior" default="false">
If [code]true[/code], ignore environment lighting when baking lightmaps.
If [code]true[/code], considers this [LightmapGI] node to be used for indoor lighting. This means it will not consider environment lighting when blending multiple [LightmapGI] nodes at the same position. Additionally, baking lightmaps will make the lightmap probes located near the edges of the [LightmapGI] black regardless of how much lighting they receive. This prevent light leaks that make dynamic objects too bright due to the lighting being interpolated between those probes.
</member>
<member name="light_data" type="LightmapGIData" setter="set_light_data" getter="get_light_data">
The [LightmapGIData] associated to this [LightmapGI] node. This resource is automatically created after baking, and is not meant to be created manually.
Expand Down
36 changes: 33 additions & 3 deletions modules/lightmapper_rd/lightmapper_rd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
//uncomment this if you want to see textures from all the process saved
//#define DEBUG_TEXTURES

static constexpr float BOUNDS_GROW = 0.1f;

void LightmapperRD::add_mesh(const MeshData &p_mesh) {
ERR_FAIL_COND(p_mesh.albedo_on_uv2.is_null() || p_mesh.albedo_on_uv2->is_empty());
ERR_FAIL_COND(p_mesh.emission_on_uv2.is_null() || p_mesh.emission_on_uv2->is_empty());
Expand Down Expand Up @@ -500,7 +502,7 @@ void LightmapperRD::_create_acceleration_structures(RenderingDevice *rd, Size2i
Vector3 pp(p_probe_positions[i].position[0], p_probe_positions[i].position[1], p_probe_positions[i].position[2]);
bounds.expand_to(pp);
}
bounds.grow_by(0.1); //grow a bit to avoid numerical error
bounds.grow_by(BOUNDS_GROW); //grow a bit to avoid numerical error

triangles.sort(); //sort by slice
seams.sort();
Expand Down Expand Up @@ -1045,7 +1047,7 @@ LightmapperRD::BakeError LightmapperRD::_denoise(RenderingDevice *p_rd, Ref<RDSh
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_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, float p_supersampling_factor) {
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, float p_supersampling_factor, bool p_interior) {
int denoiser = GLOBAL_GET("rendering/lightmapping/denoising/denoiser");
String oidn_path = EDITOR_GET("filesystem/tools/oidn/oidn_denoise_path");

Expand Down Expand Up @@ -2297,11 +2299,39 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d
}

if (probe_positions.size() > 0) {
probe_values.resize(probe_positions.size() * 9);
// 9 colors are stored per probe for spherical harmonics.
constexpr int PROBE_COLOR_SIZE = 9;
probe_values.resize(probe_positions.size() * PROBE_COLOR_SIZE);
Vector<uint8_t> probe_data = rd->buffer_get_data(light_probe_buffer);
memcpy(probe_values.ptrw(), probe_data.ptr(), probe_data.size());
rd->free(light_probe_buffer);

if (p_interior) {
// If baking lightmaps with Interior enabled, black out probes that are located near the bounds.
// This applies to the 8 bounds that are always generated, as well as the probes added by Generate Probes Subdiv
// (some of which are located on the edge of the AABB).
// This ensures environment lighting doesn't leak out to dynamic objects and make them too bright compared to the lightmap.
const Vector3 start = bounds.get_position();
const Vector3 end = bounds.get_end();

for (int i = 0; i < probe_values.size(); i += PROBE_COLOR_SIZE) {
constexpr float THRESHOLD = BOUNDS_GROW + CMP_EPSILON;
const Vector3 position = Vector3(
probe_positions[i / PROBE_COLOR_SIZE].position[0],
probe_positions[i / PROBE_COLOR_SIZE].position[1],
probe_positions[i / PROBE_COLOR_SIZE].position[2]);

if (Math::abs(position.x - start.x) <= THRESHOLD || Math::abs(position.x - end.x) <= THRESHOLD ||
Math::abs(position.y - start.y) <= THRESHOLD || Math::abs(position.y - end.y) <= THRESHOLD ||
Math::abs(position.z - start.z) <= THRESHOLD || Math::abs(position.z - end.z) <= THRESHOLD) {
// Black out the probe if it's located near the AABB's edge.
for (int j = 0; j < PROBE_COLOR_SIZE; j++) {
probe_values.write[i + j] = Color(0, 0, 0);
}
}
}
}

#ifdef DEBUG_TEXTURES
{
Ref<Image> img2 = Image::create_from_data(probe_values.size(), 1, false, Image::FORMAT_RGBAF, probe_data);
Expand Down
2 changes: 1 addition & 1 deletion modules/lightmapper_rd/lightmapper_rd.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ class LightmapperRD : public Lightmapper {
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 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, float p_supersampling_factor = 1.0f) 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, float p_supersampling_factor = 1.0f, bool p_interior = false) override;

int get_bake_texture_count() const override;
Ref<Image> get_bake_texture(int p_index) const override;
Expand Down
2 changes: 1 addition & 1 deletion scene/3d/lightmap_gi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1268,7 +1268,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, 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, (supersampling_enabled ? supersampling_factor : 1));
Lightmapper::GenerateProbes(gen_probes), environment_image, environment_transform, _lightmap_bake_step_function, &bsud, exposure_normalization, (supersampling_enabled ? supersampling_factor : 1), interior);

if (bake_err == Lightmapper::BAKE_ERROR_TEXTURE_EXCEEDS_MAX_SIZE) {
return BAKE_ERROR_TEXTURE_SIZE_TOO_SMALL;
Expand Down
2 changes: 1 addition & 1 deletion scene/3d/lightmapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class Lightmapper : public RefCounted {
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 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, float p_supersampling_factor = 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, float p_supersampling_factor = 1.0, bool p_interior = false) = 0;

virtual int get_bake_texture_count() const = 0;
virtual Ref<Image> get_bake_texture(int p_index) const = 0;
Expand Down