godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
91.48k stars 21.26k forks source link

Meshes with dynamic lighting lose ambient lighting on edges of LightmapGI bounds #88101

Open atirut-w opened 9 months ago

atirut-w commented 9 months ago

Tested versions

System information

Godot v4.2.1.stable - Windows 10.0.22621 - Vulkan (Forward+) - dedicated AMD Radeon RX 6700 XT (Advanced Micro Devices, Inc.; 27.20.21034.37) - AMD Ryzen 7 5700X 8-Core Processor (16 Threads)

Issue description

When a dynamically lit mesh moves into the bounds of a LightmapGI, it completely lose ambient lighting (turns black) around the edge and returns to normal after completely going into or out of the edges.

https://github.com/godotengine/godot/assets/25323231/6874f7a4-87e7-4968-ad1c-caa8037d39a7

Steps to reproduce

  1. Set up a scene with lightmap GI
  2. Create a mesh with dynamic GI
  3. Move it in and out of the LightmapGI's bounds as indicated by the generated lightmap probes

Minimal reproduction project (MRP)

Lightmap.zip

Calinou commented 9 months ago

My guess is that the time-based interpolation starts with purely black samples, so it gradually fades from black when a dynamic object enters the lightmap's bounds. It should probably initialize itself with the first sample found. You can confirm this theory by increasing the Rendering > Lightmapping > Probe Capture > Update Speed setting to a higher value.

In the meantime, you can add LightmapProbe nodes in each corner of the level so that dynamic object lighting extends beyond the static meshes. Automatic probe generation will adapt itself to add probes in these areas if they're large enough.

atirut-w commented 9 months ago

You can confirm this theory by increasing the Rendering > Lightmapping > Probe Capture > Update Speed setting to a higher value.

The "black area" is actually very defined, and moving the object in that area does not fade it to lighter ambient colors.

patwork commented 8 months ago

Hi, I have created a small project containing several test scenes to verify the correct generation and display of LightmapGI and LightmapProbes.

Godot LightmapGI Tests

The scene that tests dynamic objects is located in Test 02 scene.

In addition to the issue of objects going outside the Probes area, there is another one related to the fact that dynamic objects are illuminated entirely with only one Probe, even if their size causes their parts to be in differently illuminated locations.

atirut-w commented 8 months ago

cts are illuminated entirely with only one Probe, even if their size causes their parts to be in differently illuminated locations.

How do other engines behave in similar conditions? I think sampling from multiple probes could impact performance considerably. We might need to also consider probe density if we want to have that as an option in the engine.

patwork commented 8 months ago

OK, so to test this I installed Unity (v2018 because the later ones refuse to log into the license server for some reason) and I have to admit that I am now very disappointed that lighting dynamic objects works in a similar way. Regardless of the size, the moment the center of the object comes within range of the Lightprobes, the whole object starts receiving additional lighting from them.

In the example below, all objects except the cylinder are marked as static. The light "sun" and blue light in the box also only affect the lightmaps. One lightprobe placed in the box receives blue light directly.

unity-probes

When the cylinder is lowered so that it comes "within range" of the blue lightprobe, it turns blue.

unity-probes2

unity-probes3

unity-probes4

On the plus side for Unity, it should be noted that the change of light when entering the range of lightprobes is smooth and there are no sudden changes as in Godot currently.

Unity also tried to fix the problem by introducing Light Probe Proxy Volume, where a selected dynamic object with such a component added has the ability to interpolate lighting between lightprobes.

https://docs.unity3d.com/Manual/class-LightProbeProxyVolume.html

Calinou commented 8 months ago

In addition to the issue of objects going outside the Probes area, there is another one related to the fact that dynamic objects are illuminated entirely with only one Probe, even if their size causes their parts to be in differently illuminated locations.

If you want a dynamic object to receive lighting from multiple lightmap probes, you have to split it into several smaller meshes.

If you need more precise indirect lighting on dynamic objects, you could use LightmapGI and VoxelGI/SDFGI at the same time, with dynamic objects having the Disabled bake mode.

Either way, this is proposal territory :slightly_smiling_face:

Edit: Volumetric lightmaps can be an alternative too: https://twitter.com/Guerro323/status/1774573177106059337

permelin commented 8 months ago

Dynamic objects losing ambient light at the edge is caused by the fact that the object is assigned to the lightmap the moment its AABB intersects with the AABB of the lightmap, but then when the renderer goes to find the appropriate tetrahedron of probes to use, it bails out early if the center of the object is not inside the lightmap.

The area between where the edge and the center of the object intersects with the lightmap is a no man's land.

https://github.com/godotengine/godot/blob/aef11a14274f6f9e74ad91ead1d7c07ea1dd7f5f/servers/rendering/renderer_scene_cull.cpp#L2029-L2036

patwork commented 7 months ago

@permelin given your experience with lightprobes (https://github.com/godotengine/godot/pull/90701 https://github.com/godotengine/godot/pull/90702), will you also try to address this problem?

Calinou commented 6 months ago

It seems the fix would be fairly simple: instead of checking just one point in the bounds, check for all points from the AABB and only continue; if none of them is within the bounds.

permelin commented 6 months ago

@permelin given your experience with lightprobes (https://github.com/godotengine/godot/pull/90701 https://github.com/godotengine/godot/pull/90702), will you also try to address this problem?

I hadn't planned to do so.

It seems the fix would be fairly simple: instead of checking just one point in the bounds, check for all points from the AABB and only continue; if none of them is within the bounds.

I think you could just remove this check. IIRC, this code path will not be hit unless the lightmap has been assigned to the object, and that will only happen if the lightmap and object already intersect. Quitting early is redundant.

The real problem is the blend factor a bit further down. If the center of the instance AABB is not inside the lightmap, the blend will be zero, which means no light anyway. So that algorithm must be changed.

Again, if I remember correctly, the blend is actually there to handle overlapping lightmaps. It's possible that a solution would simply be to check if there is only a single lightmap assigned to the object and if so, set the blend to 1. (Though a perfect solution would blend at the bounds with whatever ambient/indirect light is outside the lightmap for a seamless transition.)