Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add post light function for forward pipelines (useful in toon shading) #102708

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

HydrogenC
Copy link

@HydrogenC HydrogenC commented Feb 11, 2025

Added a post light function, allowing the user to calculate the final color from lighting data with custom logic, useful for NPR like cel shading. Passing varyings from light to post_light is allowed. Closes godotengine/godot-proposals#10527 and closes godotengine/godot-proposals#484.

Render pipeline compatibility:

The following test shader produces the result in the image, notice that the ramp is be applied after all lights are accumulated:

shader_type spatial;

void vertex() {
	// Called for every vertex the material is visible on.
	POSITION = PROJECTION_MATRIX * MODELVIEW_MATRIX * vec4(VERTEX, 1);
}

uniform sampler2D ramp : repeat_disable;
varying float energy;

void fragment() {
	// Called for every pixel the material is visible on.
	energy = 0.;
}

void post_light() {
	// Called for every pixel the material is visible on.
	float x = clamp(energy, 0., 1.);
	vec3 color = texture(ramp, vec2(x, 0.5)).rgb;
	OUT_COLOR = color;
}

void light() {
	// Called for every pixel for every light affecting the material.
	// Uncomment to replace the default light processing function with this one.
	energy += clamp(dot(NORMAL, LIGHT), 0., 1.) * ATTENUATION * LIGHT_COLOR.x / PI;
}

Showcase

P.S. : I am new to Godot source code, so I may have misunderstood how the code framework works.
Besides, the current implementation is quite casual, maybe a better design could be came up with as for where in the shader to put the post light at and which built-ins to expose to the post light function.

@HydrogenC HydrogenC requested a review from a team as a code owner February 11, 2025 13:13
@AThousandShips

This comment was marked as resolved.

@HydrogenC

This comment was marked as resolved.

@mk56-spn
Copy link

mk56-spn commented Feb 11, 2025

Out of curiosity , does this come with a notable performance cost when not being used?

@clayjohn
Copy link
Member

Great work! The trick with this has always been what to do for the compatibility renderer. We have discussed it a few times and never agreed on something that we are happy with. When using additive lighting, a "post light" function would stop working correctly. But we can't add a built-in shader function that only works on 2 of the 3 backends.

Do you have any thoughts on how the compatibility renderer could be supported?

@HydrogenC
Copy link
Author

HydrogenC commented Feb 11, 2025

Out of curiosity , does this come with a notable performance cost when not being used?

Nope, it has no effect on the generated shader source if post_light isn't defined thanks to conditional compilation.

Do you have any thoughts on how the compatibility renderer could be supported?

I did made it work on my local machine when I was testing. The only problem is that the compatibility shader uses two-stage tonemapping/gamma-correction. It tonemaps the base frag_color and the additive_light separately and add them together. If the tonemapping process could be merged into one then there's no problem with compatibility. I'll try if merging the tonemapping process into one would break existing visual effects.

@HydrogenC
Copy link
Author

Do you have any thoughts on how the compatibility renderer could be supported?

I added the support for compatibility at of cost of potential tiny visual changes of existing shader code.

Original logic:

frag_color -----exposure------tonemap-----→(+)---------→final result
                                            ↑
additive_light-----exposure---tonemap-------|

New logic:

(frag_color + additive_light)----exposure----tonemap----→final result

The exposure step is a linear transform so no difference will be introduced, but since tonemapping is non-linear, this change might have introduced tiny visual changes on color.

@clayjohn
Copy link
Member

@HydrogenC I think you have mistaken how additive lighting works. Only the first shadowed light is included in the base pass. After that the mesh is redrawn with the blend mode set to additive and only the lighting is output to screen. This blends multiple lights in several passes.

@HydrogenC
Copy link
Author

@HydrogenC I think you have mistaken how additive lighting works. Only the first shadowed light is included in the base pass. After that the mesh is redrawn with the blend mode set to additive and only the lighting is output to screen. This blends multiple lights in several passes.

Oh, I've mistaken it, thanks for your explanation. This makes the problem difficult, that we would need another additional pass after all lights are calculated to implement post_light.

@HydrogenC
Copy link
Author

HydrogenC commented Feb 12, 2025

I made a primitive implementation for compatibility which look quite consistent with other pipelines in my test case (and doesn't break support for fog and tonemapping).
image

However there's technical limitations in multi-pass light, that users will not be able to access the accumulated DIFFUSE_LIGHT or SPECULAR_LIGHT in the implementation for compatibility, because that they are not separately stored but instead added to the final image. But it's capable of post-mapping the final color. Moreover users could use self-defined varyings to accumulate lighting data and achieve the same lighting effect as in forward implementations, as a workaround. A better and more feature-complete implementation would require godotengine/godot-proposals#8050.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants