Notes on the light function

A post for collecting my thoughts on light shaders. I will add more to it in the future


Recieving shadows

As per the docs, the shadow value is multiplied into ATTENUATION. This can be useful for achieving a mostly unshaded look while still supporting shadows

void light () {
    DIFFUSE_LIGHT += ATTENUATION;
}


Ambient light

Ambient lighting from the Environment can be disabled via render_mode ambient_light_disabled; or IRRADIANCE = vec4(0,0,0,1);. You probably wanted that for stylization purposes, and now everything in shadow will be rendered black. To fix it you need to add some ambient light in it’s place. It would be wise to do this in fragment() instead of light()

void fragment() {
	EMISSION = ALBEDO * vec3(0.1, 0.2, 0.3);
}

 

[DRAFT] Freeing up the roughness channel

In lack of stencil buffers or g-buffers. It’s wise to make the most of what you’ve got. If your game is toon/cel shaded, keep reading. You can probably get away with losing the roughness channel. Don’t worry, you can still make shiny objects happen with a custom shader!

Here’s how it works:
– Make all objects have a roughness value of 1.0

– On any opaque object in the scene, set ROUGHNESS

– Sample the normal_roughness_texture and extract the roughness component (w)

In the screen shader, you can use 1-x to invert the mask.

As is, render_mode depth_prepass_alpha must be disabled for this to work. See the workaround below if you want to enable depth_prepass_alpha as well.

 

[DRAFT] Supporting cast shadows when screen reading

The. It can help to know if the shadow pass is being rendered, shoutout to this article for that. I’ve discovered an efficient workaround which allows the shader to discard during the alpha pre-pass but not in the shadow map.

void fragment(){ ALPHA = 0.3; } void light(){ ALPHA=1.0; }

It’s pretty insane that this works. Haha

 

Operating on the final sum

Spatial shaders provide a lot of information about the light sources, but no way to get the final shading value. You may wish to create an effect based upon the final shading of the fragment, not just the individual contribution of each light. The general approach I’ve seen is to do such effects as a post-process, but that limits us to working in screen space. Luckily, it’s pretty easy to subvert the purpose of the output, and process the final shading value in addition to each light. In the example code, I treat DIFFUSE_LIGHT like a spare variable, and only allow SPECULAR_LIGHT to contribute to the final color.

 

Sources
Spatial light built-ins
The default light function source code

Shader code
shader_type spatial;

void light() {
	// light we wish to accumulate
	DIFFUSE_LIGHT += ALBEDO * ATTENUATION * LIGHT_COLOR * max(dot(LIGHT,NORMAL), 0.0);

	// assign the result
	SPECULAR_LIGHT = round(DIFFUSE_LIGHT*16.0 - 0.5)/16.0; // posterizes the final color
	//SPECULAR_LIGHT = DIFFUSE_LIGHT; // do nothing to the result

	// cancel all contribution of the diffuse_light variable
	SPECULAR_LIGHT -= ALBEDO * DIFFUSE_LIGHT;
}
Tags
light
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from celyk

Trail dewiggle

Particle based trail

Cool 3D text

Related shaders

UCBC’s Stylized Light with Light Masking

Modulate Before Light

2D Rim Light

Subscribe
Notify of
guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Spam Rakuen
Spam Rakuen
1 year ago

I’ve the same issue explained here. So I’ve modified Godot 4.1 to solve this.

spamrakuen/godot at 4.1MOD (github.com)

Spam Rakuen
Spam Rakuen
1 year ago
Reply to  Spam Rakuen

Future Shader Templetes will allow this without any MOD.

Here is a talk of Clay John were its explained
https://m.youtube.com/watch?v=MW3IFMvDTCY

romanpapush
1 year ago

This is immensely helpful, keep ’em coming 🙌