Working around Godot's 2D light limitations for a dark game

Godot Version

Godot v4.2.1 and v4.3-stable

Question

My project is dark by default, and I’ve hit a wall trying to create a specific lighting vision. Every solution I’ve come up with brings its own problems.

Goal
-A dark gameboard grid with moveable game pieces.
-The screen and gameboard itself are fully dark while the game pieces are providing light.
-Lights from the pieces interact with normal maps on both the gameboard and the other pieces.
-Differently colored lights blend colors together when overlapping.

Solution - PointLight2D and Sprite2D nodes out of the box
-CanvasModulate node to darken the canvas to black.
-A gameboard made up of tiled Sprite2Ds with a CanvasTexture (solid white diffuse texture / normal map texture with white being highest point and black being lowest).
-Game pieces are instantiated from a scene containing a Sprite2D (textured similar to the gameboard tiles) and a PointLight2D (colored depending on game piece type.)

The idea being that PointLights add their color and light information to the sprite, which would show a rounded tile (thanks to the normal map) blended with the various colors of light that hit it.

Problem 1 - Cap on 2D Lights
When things weren’t working right, I searched further and discovered Godot’s cap of 15 for lights. Once a sprite renders lights to this cap it ignores any additional lights. Since my game pieces are 64x64 pixel sprites with 320x320 pixel light textures, and these pieces place right next to each other onto a grid of 64x64 squares, the light cap is reached very easily. There doesn’t seem to be a way to select which lights are rendered, so trying to prioritize either moving lights or the closest 15 lights doesn’t seem to be an option.

The most widely recommended solutions I found was to chop up the sprites into smaller bits or reduce the radius of light textures to avoid a situation where a sprite sees too many lights. Sprites are already only 64x64 pixels, but I decided to try compromising the effect and reducing the light texture to under 192x192 pixels. This reduced the lit area, and now a tile surrounded completely by other game pieces is only hit by 9 lights. Tiles moving over the pieces have a 6 light cushion (it still hits the cap when more tiles pass over it but it’s far less obvious at all times).

It compromises the full vision I had but it still seemed workable at this point.

Problem 2 - Mix Blend Mode Doesn’t Work
Lights kept adding their brightness to each other, washing out the colors and sprites and getting very bright. Height peaks on the normal map were behaving oddly when lights hit it from both sides. Changing the blend mode to Mix instead of Add solved both these visual issues, and since brightness is pretty similar on all my lights the interpolation between colors is not an issue.

However, PointLight2Ds set to Mix blend mode only work on sprites that are in the scene when the light is instantiated. Any sprites added to the scene after a light will not render that light at all. Doing nothing but changing the blend mode back to Add fixes the problem immediately, but nothing I have tried can make it work with Mix mode. I opened a bug report:

Whether it is a bug in the engine or some unique user error, I can’t find further information about solving this issue. Ultimately this makes my preferred solution unusable for the time being.

Solution - Additive Sprites:

-Use additive sprites for lighting instead of light 2Ds.

I found this suggested as a cheaper and easier way to handle lighting.

Problem - No Normal Maps
To my knowledge there isn’t a way to use normal maps using this method. Since the normal maps are the only piece of the Sprite2D that has visual information, any method that doesn’t use them won’t look right.

Solution - Lighting via Shader:
-Use a custom shader that takes in distance to all light locations, and does the math on the fragment pixels based on those distances, light colors, and normal map values.

Problem - Don’t Know What I Don’t Know
To this point I’ve used and made shaders only minimally, so I’d have to dive in and upgrade my knowledge to get something like this going. At the end of the day I don’t know enough to understand how feasible this is. A lot of the restrictions on 2D lights seem to be from trying to simulate a 3D space in a 2D environment, and redraws to the Canvas take a lot of resources.

If I go this route won’t I be faced with the same problems as those who made the original 2D light nodes anyways? Will I spend all my time on this only to find the same hard limits already imposed on me?

Solution - Make the Project in 3D

-Just do it in 3D. Lights are cheaper so go nuts.

Problem - Would Have to Give Up on Project Vision:
I’m smitten on the stylistic choice of dark 2D sprites with normal maps lit up by light textures. No one would confuse it for realistic 3D lighting but the unique look of it is the charm for me on this project. I would need to head back to the drawing board with my artist and re-conceptualize the art direction, then ask for all new assets.

Conclusion
I haven’t been able to solve my problem, and am not sure which, if any, of the solutions I’ve came up with are worth persuing. Does anyone have any suggestions, ways they’ve solved a similar problem, or see anything I’ve completely missed? Is my best bet to call it on the 2D lights and just convert to 3D, get the project out, and move on with my life?

Thanks in advance for any thoughts!

1 Like

I had a quick look and I found, lightoccluder2d, this mightn’t help or your already using it i wouldn’t i haven’t done anything to do with light and I haven’t done much with 2d

1 Like

You pretty much hit the nail on the head regarding your write up of the lighting system. I’ve ran into this issue before as well and it’s frustrating for sure. Losing out on normal maps is a bummer

I don’t have a solution to the lighting issue but how does this sound for another potential workaround, if it fits the vision of your game?:

When two pieces and their lights get close, you can merge the two lights into one big light? As in hide/queue free the lights and replace them with a new light that mimics their combined color/other properties? When you create the new light, you can tell it how many ingredient lights it was created by, and then re-create those ingredient lights if the pieces move apart again.

This might be interesting if you have a chess board situation, if two opposing pieces are next to eachother it will create a new color. Say if team A has red lights and team B has green lights, when two pieces become adjacent it will mix the lights. Chess might be a bad analogy though since you’d probably confuse people if you had two knights next to eachother, since they can’t capture eachother but the lights are still getting mixed so that is sending mixed signals to the player. Just spitballing/brainstorming though, maybe your game has different rules of engagement

Also there is a Sprite3D node, so if you locked all the necessary rotations in 3D, you can probably recreate the 2D effect in a 3D world, using Sprite3D

1 Like

LightOccluder2D is for blocking lights and creating shadows in the 2D light system. The only light effect I want is the directional information from the normal map, which is an odd request I know.

Thanks for checking into it and replying.

I played around with a similar idea at one point; using the concept of combining lights into one when close together. The problem with doing this is that PointLight2D nodes have a center point which the light radiates outwards from. Having the calculation point for the normal maps suddenly switch from multiple points to one point in between pieces was odd and jarring.

Honestly, I really like this idea. I’ve been trying to do all sorts of madness to get the 2D renderer to light up sprites when it wasn’t really designed to do so. I’m going to try playing around with my sprites in a 3D environment and report back.

Thanks for the suggestion!

1 Like

Good luck! I’m interested to find out the results!