godotengine / godot

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

Max number of lights per object limit is applied globally for all instances of a MultiMesh, rather than per instance #44912

Closed alib86 closed 2 years ago

alib86 commented 3 years ago

Godot version:

3.2.3 Stable and 3.2.4 Beta 3 (Same result)

OS/device including version:

Windows 10, Nvidia RTX2060, Latest official drivers, GLES3

Issue description:

-Max number of lights per object limit is applied globally to all instances of a given MultiMesh, rather than per instance. Effects of a visible light on one instance does not get drawn on other instances (unless in range) so expected each instance to have it's own limit.

-In other words, 8 lights (limit) on one instance disables lights for all other instances of the same MultiMesh. MeshInstances with the same Mesh resource as the MultiMesh do not act this way.

-Increasing the max number of lights via project config (as you can do in 3.2.4 Beta 3, see https://github.com/godotengine/godot/pull/43606 ) can improve the situation but at a high cost of performance.

Steps to reproduce: (Given max number of lights per object is 8 - limit at rendering/limits/rendering/max_lights_per_object for 3.2.4 B3) -Add a MeshInstance with CubeMesh, -Populate MultiMesh using MeshInstance (so same Mesh resource) with a number of instances, -Set 9 lights over one of the MultiMesh instances (Choose OmniLight or SpotLight and keep all lights thesame type. Limit is per light type) -One of the lights will not be visible on that instance as you are over the limit. -Move that light over another MultiMesh instance and it still won't be visible. (Expected MultiMeshTest.zip to be visible) -Move the same light over the MeshInstance (that uses the same Mesh resource), it will be visible there.

Images of the issue: Godot1 Godot2 Godot3

Minimal reproduction project:

MultiMeshTest.zip

Note: If anyone is able to point me in the right direction, I am willing to dig through the source code and take a look, haven't been able to find where to start on my own.

clayjohn commented 3 years ago

A multimesh is a single object, no matter how many instances. All instances are drawn at once as if they are one large object. This is part of the tradeoff you make when you use a multimesh. It is much faster to draw, but the GPU treats it as one instances rather than a collection of meshes.

alib86 commented 3 years ago

@clayjohn Figured as much, though had hope for a workaround since the lights are able to differentiate between instances and draw on specific ones but are limited per the whole MultiMesh instead of the specific ones it can draw to.

Calinou commented 3 years ago

Remember not to use a single MultiMesh to represent lots of instances over a large area. Otherwise, you won't be able to benefit from view frustum culling.

Instead, it's recommended to use a "grid" of MultiMeshes so that they can be culled individually. You need to find a compromise between the number of draw calls and culling opportunities.

Zireael07 commented 3 years ago

IMHO this lights per object thing should be called out in MultiMesh documentation.

alib86 commented 3 years ago

@Calinou Thank you for the suggestion. My use case is a bit different, I am using MultiMeshes like a tilemap (or GridMap - which was very slow for large terrain) where each MultiMesh is a different type of tile, so they are arranged in an interlocked fashion. Tried dividing each tile type between a couple MultiMeshes, but the implementation with 1 tile per 1 MultiMesh has the best performance.

It's a medium sized city scene where there are multiple lamp posts per street, capping visible number of lights (32 of closest ones) gets me to 200-300 units of good view then the popping in starts as I move and turn on newer lights. Thought about ray casting to lights to do occlusion culling but that looks like it will get expensive pretty fast.

Calinou commented 3 years ago

(or GridMap - which was very slow for large terrain)

Try decreasing the GridMap quadrant size to 1 to get better performance. GridMap uses MultiMesh under the hood, but it doesn't seem to be always working correctly for some reason.

Thought about ray casting to lights to do occlusion culling but that looks like it will get expensive pretty fast.

Look into using LOD-enabled lights from godot-lod :slightly_smiling_face:

There is a pull request that reworks godot-lod to make it more efficient, you could test that too. I didn't merge it yet as I haven't been able to test it locally due to lack of time.

alib86 commented 3 years ago

@Calinou Thanks again for the suggestions, will look into godot-lod. Fiddled around with GridMaps for quite a while at the beginning but could not get anywhere, render performance aside the initialization from code (most likely the collisions) took ages. Now combining all multimeshes into one collision shape, lightning fast.

Anyway slightly deviating from the purpose here but if anyone is interested, in the past couple of hours just tried actually ray casting (4 times per sec) camera to lights using building AABBs as occlusion areas (scaled to 80% so turning the corner does not cause pop in), with 200+ lights it does actually seem to be working pretty good, keeping around 15-30 visible at once without much resource usage. Thought could further improve with an extra camera.IsPositionBehind(light) check but that does jump CPU usage from 5% to 15%.