Ultimate Toon Shader
This is a toon shader I made for Godot 4.x. If you’re interested I’ve got some presets and textures on itch
Features
- Tinting shadows.
- Customizable shadows with steps and smoothing between them!
- Patterns based on grayscale textures.
- Local Screen UV. The pattern will always face the camera, but still scales based on distance, meaning far away objects won’t look weird. (Options for mapping patterns based on UVs and Screen UV are also available)
- Customizable rim light.
Shader code
shader_type spatial;
group_uniforms Shader.Albedo;
uniform vec4 albedo_color : source_color = vec4(1.0);
uniform sampler2D albedo_texture;
group_uniforms Shader.Light;
uniform bool use_stepped = true;
uniform float steps = 3.0;
uniform float step_smoothness : hint_range(0.0, 1.0, 0.05) = 0.3;
uniform vec4 shadow_tint : source_color;
uniform float shadow_tint_amount : hint_range(0.0, 1.0, 0.1) = 0.4;
group_uniforms Shader.Specular;
uniform float specular : hint_range(0.0, 1.0, 0.05) = 0.0;
group_uniforms Shader.Normal;
uniform float normal_strength : hint_range(0.0, 1.0, 0.05) = 0.0;
uniform sampler2D normal_texture;
group_uniforms Shader.Rim;
uniform bool use_rim = true;
uniform vec4 rim_color : source_color = vec4(1.0);
uniform float rim_amount = 2.0;
uniform float rim_smoothness = 0.2;
uniform float rim_mask_shadow : hint_range(0.0, 1.0, 0.05) = 1.0;
uniform float rim_blend : hint_range(0.0, 1.0, 0.05) = 1.0;
group_uniforms Shader.Pattern;
uniform bool use_pattern = true;
uniform sampler2D pattern_texture;
uniform int pattern_uv_mode : hint_enum("UV", "Screen", "Local Screen") = 2;
uniform float pattern_tiling = 32.0;
uniform float pattern_amount : hint_range(0.0, 1.0, 0.05) = 0.5;
uniform float pattern_smoothness : hint_range(0.0, 1.0, 0.05) = 0.5;
uniform float pattern_blend : hint_range(0.0, 1.0, 0.05) = 0.2;
varying vec4 node_position;
float fresnel(float amount, vec3 normal, vec3 view)
{
return pow((1.0 - clamp(dot(normalize(normal), normalize(view)), 0.0, 1.0 )), amount);
}
vec2 pattern_uv(vec2 uv, vec2 screen_uv){
vec2 p_uv = uv;
if(pattern_uv_mode == 1) { p_uv = screen_uv; }
if(pattern_uv_mode == 2) { p_uv = (screen_uv * 2.0 - 1.0) * node_position.w - node_position.xy; }
return p_uv;
}
float shape_value(float value, vec2 uv, vec2 screen_size){
float aspect = screen_size.y / screen_size.x;
vec2 pattern_uv = uv * pattern_tiling * vec2(1.0, aspect);
float shadow_pattern = texture(pattern_texture, pattern_uv).r;
shadow_pattern = clamp(smoothstep(shadow_pattern - pattern_smoothness, shadow_pattern + pattern_smoothness, value + pattern_amount), 0.0, value + pattern_amount);
return shadow_pattern;
}
void vertex() {
node_position = (PROJECTION_MATRIX * vec4(NODE_POSITION_VIEW, 1.0));
}
void fragment() {
vec4 albedo = texture(albedo_texture, UV) * albedo_color;
vec4 normal_map = texture(normal_texture, UV);
ALBEDO = albedo.rgb;
NORMAL_MAP = mix(vec3(0.5,0.5,1.0), normal_map.rgb, normal_strength);
SPECULAR = specular;
}
void light () {
float ndotl = clamp(dot(NORMAL,LIGHT),0.0,1.0) * ATTENUATION;
float light = ndotl;
if(use_stepped){
float light_mult = light * steps;
float step_base = floor(light_mult);
float light_factor = light_mult - step_base;
light_factor = smoothstep(0.5 - step_smoothness, 0.5 + step_smoothness, light_factor);
light = (step_base + light_factor) / steps;
}
if(use_pattern){
vec2 p_uv = pattern_uv(UV, SCREEN_UV);
light = mix(light, shape_value(light, p_uv, VIEWPORT_SIZE), pattern_blend);
}
vec3 diffuse = vec3(light) * (LIGHT_COLOR / PI);
diffuse = (shadow_tint.rgb * (1.0 - (light)) * shadow_tint_amount) + diffuse * light;
if(use_rim){
float rim_value = fresnel(rim_amount, NORMAL, VIEW);
rim_value = smoothstep(0.5 - (rim_smoothness / 2.0), 0.5 + (rim_smoothness / 2.0), rim_value);
rim_value = mix(rim_value, rim_value * light, rim_mask_shadow);
diffuse += rim_color.rgb * rim_value * rim_blend;
}
DIFFUSE_LIGHT += diffuse;
SPECULAR_LIGHT += vec3(pow(light, 50.0)) * specular;
}



