2D Cel / Toon Shader v2 (Basic)

This is the basic level of the v2 toon shader. You can see the more advanced version here. This shader will restrict light values giving a type of cel or toon shaded look. It requires sprites to have normal maps. This shader will NOT create the drawn lines, but you could combine with a border shader to give an outline around edges of your sprites.

Shader Param:

  • First Stage – Sets the threshold for the darkest light. 

  • First Smooth – Smooths the transition between darkest and the next stage.

  • Second Stage – Sets a second light threshold for a mid value. If set to 0 this setting and Second Smooth is ignored and only two light stages are used.

  • Second Smooth – Smooths from the second stage to the lightest. 

  • Min Light – Sets minimum allowed light. 0 is complete darkness. Increasing from 0 may have a gradient effect due to the way I implement the ability to add light where there was none.

  • Mid Light – Sets the light value of the Second Stage. If Second Stage is set to 0, this setting is ignored.

  • Max Light – Sets the maximum light value allowed.

  • Obj Light Add – Adds the original light values to the cel values.


This shader is a combination of approaches I found on the internet, help from community members, and my own experimentation and development. 


Big thanks and shout outs to these people:

  • Azagaya from Laigter – Provided initial cel shading code that got the first version of the toon shader working and jump started me on this. His method for smoothing got carried over to this version and I utilized the VEC calculation to allow setting the Min Light.

  • Toasteater on Github – In my searching I found this issue on Github where toasteater outlined his method for cel shading. I learned from their method to solve the gradient issues from version 1 of the toon shader.

Use it, Make it better!

If you use this in a project let me know so I can check it out. I’m not a programmer by trade so if you know how to make this better, please do so I can use a better version!


Shader code
shader_type canvas_item;

uniform float first_stage : hint_range(0.0, 1.0) = 0.5; 
uniform float first_smooth : hint_range(0.0, 1.0) = 0.0; // Lengthens the color transition
uniform float second_stage : hint_range(0.0, 1.0) = 0.0;   // If left at 0, only level 1 is used.
uniform float second_smooth : hint_range(0.0, 1.0) = 0.0;
uniform float min_light : hint_range(0.0, 1.0) = 0.0;
uniform float mid_light : hint_range(0.0, 1.0) = 0.0;
uniform float max_light : hint_range(0.0, 1.0) = 1.0;
uniform float obj_light_add : hint_range(0.0, 1.0) = 0.0;

float light_calc(float light_strength, float would_be_strength) {
	float target_strength = light_strength + would_be_strength * obj_light_add;
	if (target_strength == 0.0) {target_strength = 0.000001;}
	if (would_be_strength == 0.0) {would_be_strength = 1.0;}
	return(target_strength / would_be_strength);

void light() {
	float level_1 = first_stage;
	float level_1_smooth = first_smooth;
	float level_2 = second_stage;
	float level_2_smooth = second_smooth;
	float mid_range_light = mid_light;
	if (mid_light == 0.0) { mid_range_light = max_light * 0.5; }
	vec3 light_normal = normalize(vec3(LIGHT_VEC, -LIGHT_HEIGHT));
	float would_be_strength = max(dot(-light_normal, NORMAL), 0.0);
	if (would_be_strength > level_1 && level_2 == 0.0 ) {
		float diff = smoothstep(level_1, (level_1 + level_1_smooth), would_be_strength) + min_light;
		if (diff >= max_light) {diff = max_light;}
		LIGHT *= light_calc(diff, would_be_strength);
	} else if (would_be_strength > level_1 && would_be_strength < level_2 && level_2 != 0.0 ) {
		float diff = smoothstep(level_1, (level_1 + level_1_smooth), would_be_strength) + min_light;
		if (diff >= mid_range_light ) {diff = mid_range_light;}
		LIGHT *= light_calc(diff, would_be_strength);
	} else if (would_be_strength >= level_2 && level_2 != 0.0 ) {
		float diff = smoothstep(level_2, (level_2 + level_2_smooth), would_be_strength) + mid_range_light;
		if (diff < mid_range_light ) {diff = mid_range_light;}
		if (diff >= max_light) {diff = max_light;}
		LIGHT *= light_calc(diff, would_be_strength);
	} else { 
		if (min_light != 0.0) { 
			LIGHT_VEC = -NORMAL.xy*length(LIGHT_VEC); 
		LIGHT *= min_light;                                                                                                                                  
2d, canvas, cel, shader, toon
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from MightyMochiGames

2D Rim Light

2D Light Z-Depth

Toon Shading for 2D Sprites v1

Related shaders

Basic Vector Sprite Upscaling

2D Cel / Toon Shader v2 +Plus

Flexible Toon Shader

Inline Feedbacks
View all comments