Achieving Light2D with Blending using Maximum Value (instead of Add)

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By KuroKitten

I’m working on a game where 2D Shadows and Field of View cones generated by multiple overlapping “light sources” are a core part of the mechanics and experience. As such, I’ve been playing around a lot with Light2D objects, LightOccluder2D objects, and all of the various blend modes. I’ve certainly found some really cool effects and interactions, but I’ve continually been frustrated trying to get the primary lighting effect I’m looking for.

After some thinking, I’ve realized that what I really want is a blend mode where the maximum value of any combining light sources is taken as the “blended value”, instead of adding them together, or interpolating between them.

I’m still new to Godot, though, and I’m not sure what the easiest/best way to go about doing that might be. Some thoughts I’ve had might be to: Modify Godot source to add a new blend mode; Do some magic with shaders?; Render something to a separate texture, then use that as the texture for a Light2D node; or, Completely implement my own 2D lighting system using raycasting.

I’m not experienced enough with Godot to know how difficult any of those might be, or even where to start, or if there might be a cleaner solution I haven’t considered or am not aware of. So, before going too far down any rabbit holes, I wanted to reach out to the community to see if anyone might be able to point me in a good direction - hopefully maybe even with some resources to dig deeper into?

:bust_in_silhouette: Reply From: klaas

Hi,
i dont think this can be archived with the build in lighting architecture of godot.

Light processor functions work differently in 2D than they do in 3D. In CanvasItem shaders, the shader is called once for the object being drawn, and then once for each light touching that object in the scene.

So … there is no gl-blend mode GL_MAX for the 2d LIghts. I think your pretty out of luck if you dont want to modify the godot source and compile it yourself.

2D Lighting happens here
https://github.com/godotengine/godot/blob/3.3/drivers/gles/rasterizer_canvas_gles3.cpp
around line 354.

Any other attemp seems to end in very poor performance.

Got it, so eventually all calls end at glBlendFunc(), and even if I go the route of modifying and compiling source, I’m still mostly limited to what glBlendFunc() can do - short of some serious modification.

Thanks for the link to the spot in source, that actually gives me a lot of insight. I suppose I’ll have to think about whether any alternative options for glBlendFunc() will do what I want, or otherwise I’ll need to write some scripts and/or shader code to do what I want.

Edit: As follow-up for anyone who may stumble upon this question in the future, there is actually a mode in OpenGL for taking the max (or min) of two textures being blended (as Light2D’s do in Godot 3.x). If you go here in source:

For GLES2:
https://github.com/godotengine/godot/blob/3.3/drivers/gles2/rasterizer_canvas_gles2.cpp#L1835
and https://github.com/godotengine/godot/blob/3.3/drivers/gles2/rasterizer_canvas_gles2.cpp#L2228

For GLES3:
https://github.com/godotengine/godot/blob/3.3/drivers/gles3/rasterizer_canvas_gles3.cpp#L364
and https://github.com/godotengine/godot/blob/3.3/drivers/gles3/rasterizer_canvas_gles3.cpp#L1513

You can see where it’s making the determination of how to blend. Mostly, it calls these two methods: glBlendEquation(), and glBlendFunc(). I recommend you look up documentation or a video explaining these in better detail, but there are two values you can pass to glBlendEquation() to achieve a max (or min) blending: GL_MAX, and GL_MIN. Here’s a link to that documentation for more info:
glBlendEquation - OpenGL 4 Reference Pages

KuroKitten | 2021-07-23 06:19

KuroKitten, did you manage to achieve the effect you were looking for? I’m also struggling with the same issue.

Thanks for all the resources you linked to in your edit. After reading through the OpenGL docs and rebuilding Godot 3.5 from source while playing with glBlendEquation using GL_MAX, I wasn’t able to make it work. Wondering if you made any progress.

JustDadThings | 2022-04-07 00:23

For anyone in the future finding this, the following solution worked for me, on Godot 3.5:

  • The textures of your light sources need to be pure white RGB(1.0, 1.0, 1.0), but the alpha channel can be anything, and that’s what you use to fade the light.
  • All lights in the scene need to be set to Mix.
  • I used Canvas Modulate to make the scene pitch black.

I did not try using shadows, as my game did not need them.