Skip to content

Select relevant 3D lights per mesh on GLES3 and Mobile renderers #107234

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

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

jon1solution
Copy link

This PR changes the behavior of the RendererSceneCull::_scene_cull function to select the most relevant 3D lights per mesh in the Compatibility and Mobile renderers. It is a fix for Issue 107070.

Previously, if the scene had many more lights in range than could be rendered per-mesh (so > 8 for Mobile, or > Max Lights per Object for Compatibility), Godot would simply pick the first N lights and assign them to the mesh. Even increasing the limits (on Compatibility) failed to illuminate the scene as you would expect. I am sure most users who need complex 3D lighting can use the Forward+ renderer, but I am hoping to get a decent approximation running on the more limited renderers (I ran into the issue trying to fake GI in a procedural building with lots of lights).

Here is the behavior of 4.4.1 in a scene with 256 meshes and 256 omni-lights. For testing purposes, the range of each light was extended so that all meshes were in range of all lights. The result looked the same on both Mobile and Compatibility (with limits set to 256 lights per scene, 8 per-mesh to match Mobile):
image

With the code modifications in this PR, it now looks like this:
image

The original code ran through each mesh, and then each light relative to each mesh. The new code follows the same pattern but now assigns a score to each light, relative to each mesh, factoring in the distance from the light's AABB center to the mesh's AABB center, the light range, and the light energy.

The code then keeps track of the top N lights, by keeping the top N scores in a SortArray, which uses a heap internally to keep the scores sorted enough to find the worst score directly (it is always in slot [0]). If a new light is found to score better than the worst in the collection, slot 0 is replaced with the new light and the heap is updated.

In theory this is the fastest way to run through all M lights while keeping the top N, however many spatial hashing schemes could accelerate this by not having to run through all M lights for every mesh. So there is room to optimize further if this turns out to be too slow for extreme cases, however 256 3D lights in a scene might be excessive for mobile and web targets. [8^)

Removed pair_light_instances which took an intermediate collection of culled lights.
Added in clear_light_instances to remove all paired lights from the mesh, and it returns how many lights max per mesh can be paired.
Added pair_light_instance which pairs a specific light with a specific index into the pairing array.
Inside RendererSceneCull::_scene_cull I keep the top N lights per mesh.  The current implementation is not the fastest, I still need to move to a heap to efficiently keep the best lights, and SortArray seems to have the functionality I need.
SortArray uses a heap internally, which is the best option.
On my system, worst test case is 256 omnilights with 256 meshes with 64 lights/mesh, and that takes 8.9us per mesh just to compute the distance score for the lights, with an additional 5.1us per mesh to keep the top 64.
Updating comments.
Takes Max Renderable Lights into account.
The function clear_light_instances now also reports total_max_lights.
Tried processing from cull_result.lights instead of geom->lights, but they are being built up over the life of the function, so the list is incomplete when I need it.  (Also the function can be called from multiple threads)
Removed the code I was using for testing and profiling.
Changed how clear_light_instances returns the per-mesh and total light count.
Ran clang-format on my files.
Reverted useless change to .gitignore.
@jon1solution jon1solution requested a review from a team as a code owner June 6, 2025 21:13
@Calinou Calinou added bug topic:rendering topic:3d cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release labels Jun 6, 2025
@Calinou Calinou added this to the 4.5 milestone Jun 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release topic:rendering topic:3d
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants