How to make a "Light Only" shader that only uses color from specific light mask

Godot Version



I have this light masking demo. I use this technique in my game, but unfortunately it limits some things. I can’t apply any other light to nodes with the “Light Only” material, because then it will mask them and make them visible.
Is there a way to make a shader that uses, for example, Light Mask 1 for transparency and masking, and Light Mask 2 for color?
Or perhaps a way to mask the color light with the masking lights? (Applying a “LightOnly” shader to a lightsource doesn’t work)

In this picture i have a red light that i would want to apply just the color over the already masked image.

Yes, you can just make a shader that replaces the final alpha with the mask, just have the two separate uniforms in and apply the mask last… unless I am misunderstanding something?

How would i get all the references though?
For example i will have a radial light that is attached to the player’s body and a cone/fov light that is attached to the head of the player. Both of those lights will be the masks.
Then there will be a bunch of lights on the level, some dynamic/moving, some static.
And the geometry of the level which will be always visible

In a perfect world i would somehow have 3 render layers. 1 - Stuff that is not masked and always visible; 2 - Masks; 3 - Stuff that should be masked.
Making this on a small scale, with one or two objects is not a problem. The problem is how do i make this work with big levels where there is a bunch of objects and lights.

Bake the static lights to a static mask. In 3D this is what the GI node does, basically. Do some math to slice your masks so you don’t have just one giant texture per level. Dynamic lights are not a simple thing to do, but it’s not black magic. You somehow pass the data of the lights to the shader and calculate how each one affects each fragment. You can limit this to nearby/in-frustrum lights for performance depending on your requirements. Any document out there about lighting in shaders will help you as godot just uses the very widespread GLSL shader language.
Instead of 3 render passes, you can have a single shader that figures out all three in the right order. First you render the stuff that should be masked, then you apply the mask to it, then you draw the stuff that’s never masked. This stack of three color values is just a blend function away. Probably something like max(Vector4(maskable.rgb, mask.a), unmaskable) or maybe something more nuanced to include alpha in the unmasked objects and a background.

Hmm, i will try this.
Would i apply this shader to a viewport node or something?

As any shader, you can apply it to any surface you want. If you look at the Remote tab while running you can see the Viewport node godot uses to render to the screen. You can add your own to the stack or any other fanciness like overriding the _draw() of a 2D Node or using a quad mesh.

Depending on the size of the scenes you need to render, it may be easier to just use a subviewport to render either the light mask or the color mask, and integrate that into your scene. From what I see this is a one-view puzzle game and not a complete world where people move around, so it should be cheap to run and relatively simple to setup (simple compared to a game where your player moves around in the world)

Say you put the visibility mask into this subviewport, you’d just place these masks as sprites rather than lights, then take the texture from the subviewport, and pass it as a uniform for your shader.

You can do the same with the color mask, or use the lights directly in the main scene.

Lights have no ID information or any meta information other than the color and attenuation. Something else you can do is to pass data through the light color, for example red channel for color ID and then use the red value to read from a gradient texture, but this is way more complicated and error prone, so i think it’s easier if you use subviewports.

Unfortunately this will be a relatively big world where the player and the enemies move around. The image in the post is from the official godot light masking demo.

This approach sounds similar to this video:
I’ve tried replicating it, but faced the problem that the viewport and the mask sprite size was too small for what i want. Plus the whole thing with duplicating the entire level a bunch of times.

I will soon try the shader method, maybe with 3 view layers.

Oh well. Seems like this issue turned to be a lot bigger than i thought. I will unfortunately have to abandon this idea of combining light with light masking for now. Maybe in the future updates it will become easier to do this.