• Shaders
  • How do I apply normal map lighting effects before my shader? [2D]

I wrote a simple 2D shader that turns an image grayscale and then limits how many shades of gray can be outputted to 6.

https://imgur.com/bFxJoOL

However, when I add a normal map to the sprite with the shader, and then add a 2D light to the scene, the shader no longer locks the image down to six different shades of gray.

https://imgur.com/a/MuC84FZ

I want godot to solve for the lighting effects on the normal map, and then pass those results to my shader. How do I do this?

Vertex > Fragment > Lighting this is the order of the pipeline.

In your fragment shader you are not doing to your NORMAL output what you are doing to your COLOR output thus the NORMAL output goes into the Lighting shader as is.

Which means you'll have to calculate the NORMAL yourself.

Thanks for the responses. I'm wondering, after doing some research, if I could turn my shader into a screen-reading shader on a post-processing layer of sorts.

Would that actually work, or am I just going to run into another pipeline order issue?

Here's what I would do. Take your fragment shader program, and put most of it into its own function. The function will accept UV coordinates and return a colour. In your fragment shader, you may run that same function to get the COLOR value, but you can also use it to calculate the NORMAL. Just run the function two more times with a small offset along x and y respectively. Then calculate the difference between those and the original colour value (red component only). This should give you your normal. I'll post some code later.

Or possibly all the posterization could be done straight in the lighting shader. Not sure, but it might save some on total operations done by the shader. Trouble is AFAIK, converting the default/builtin materials into editable custom shader doesn't export the lighting shader. :(

I didn't figure out how to do this in 2D, but I discovered that if I ported the scene to 3D and used a screen-reading shader it works fine.

I think I'll stick with this approach because I prefer the way Godot handles light in 3D, plus I enjoy the extra control I get from camera settings, tone mapping, etc.

3 months later

You can use a new canvas layer. Put your shader in there, change the "layer" property to a higher number than your main game (2 should be enough) and prevent your Light2D from casting onto layer two (under "Range" -> "Layer Max", set it to 1 or 0). Then your light will process before your shader.

Things like this are why CanvasLayer sticks around. It makes even Leveltransitions really easy to implement.

Write a Reply...