Select relevant 3D lights per mesh on GLES3 and Mobile renderers #107234
+117
−48
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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):

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

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^)