Add support for global lights
JME lights only affect children of the nodes to which they are attached. This makes sense from a performance/optimization standpoint, but it does not reflect the behavior of blender and other renderers.
This PR adds support for global lights ie. lights that affect all the spatials up to the rootNode.
Lights can be marked as global by setting to true the "global" boolean flag on the lights' constructors . The flag is false by by default, so existing scene won't be affected.
This implementation exploits the jme scenegraph update logic to make global lights possible without any change to the renderer, this is an overview of how it works:
- A new refresh flag
RF_GLOBAL_LIGHTSis added. - This flag is always propagated upstream up to the rootNode
- The rootNode seeing this flag will do a deep traversal of its children to find all the global lights to add to its own world light list.
- As a side effect, when the children are be updated, immediately after, they will pull the world lights list of their parent (first one being the rootNode with the global lights) propagating the global lights down the scene graph.
Very cool! I have often wanted lights to behave exactly like this. This avoids a lot of messing about and booking keeping with lights attached to the root node but positioned elsewhere
Thanks for this! This is definitely something I wish I had.
JME lights only affect children of the nodes to which they are attached. This makes sense from a performance/optimization standpoint.
Come to think of it, I think this may actually be a performance problem. JME currently uploads a new light buffer for each geometry, so if there are 5 global lights and 100 geometries, roughly 8kb of light data is being sent to the gpu every frame. It would probably be much faster to upload all lights once using a UBO or SSBO (maybe using a byte/short buffer for selecting local lights in-shader, or forget local lights altogether). Plus, we're also recompiling all shaders whenever a light is added or removed, so SSBOs would fix that, too.
It depends. If you have a lot of lights affecting everything in the scene (or that are global with this pr) with modern OpenGL, having a big light buffer helps a lot.
But if you have lights affecting small parts of the scene you will end up processing a lot more lights than what you need in every fragment.
In lowend gpus or old opengl with no UBO support, the current jme approach is much better.
So I'd say it depends on how you build the scene and what your target gl version and platform is. The current approach allows for more optimization, but is probably outdated with modern expectations and necessities.
Plus, we're also recompiling all shaders whenever a light is added or removed, so SSBOs would fix that, too.
I think this is the case only if you change the light batch size in every frame in single pass lighting, and never the case in multipass lighting. So, if you set the light batch, to like 32, and you never change it, it will compile only once. And if you have 33 lights it will render it in two passes.
@richardTingle check the last commit, i think i've implemented your suggestion. It is not fully tested yet, but It should work™
@riccardobl Nice! I'm going to write some screenshot tests for global lights and that will double as some testing
I also agree this would be a great addition.
I've previously had to use a hacky workaround for global lights in my custom editor, where I flag lights as "global" with userData and then move them to the rootNode if they have that userData. But then they also need moved back and forth anytime the scene is saved/loaded to make sure the light is actually attached to the scene thats being saved (and not lost in the rootNode).
So I'm looking forward to this PR for sure! It will definitely be a useful feature.
@riccardobl I'm struggling to get this working. I've created the beginnings of a test at https://github.com/richardTingle/jmonkeyengine/blob/globalights-tests/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/GlobalLights.java
I create two nodes with a red local light and a green global light. Two white cubes are added, one a sibling of the lights, the other on a different branch. I expect the sibling to be illuminated by both and the non sibling to only be illuminated by the global light. But the global light doesn't seem to affect either cube.
I've experimented and I think that's because it is attached from the beginning. I get different behaviour if I add it later
But it still doesn't illuminate the other cube
Thanks for the test case, i'll look into it. There might be some overlook on my side in the flag propagation
I think it is fixed now, basically the problem was that the previous behavior had the the refresh flag being propagated up the branch, but it didn't take into consideration that if the scene has multiple branches the flag must be propagated down to all the other branches too