Complete Cel Shader for Godot 4
Upgrade of my old Complete Toon Shader now for Godot 4. It supports:
- Multiple lights;
- Deeper control over specular blobs and Fresnel effect, all toonified;
- 3D outlines;
- Normal maps;
- Ambient occlusion;
- Anisotropy flowcharts;
- Backlight;
- Transparency;
- Refraction;
Heightmap and subsurface scattering left out can be copied from the first version, I just left them out because I think they’re not goal of a toon shader.
It uses shader globals and shader includes, read everything carefully to set it up, just copying and pasting the code from this page won’t do it. Read the repository to set it up correctly. You can also watch the video for more information on how it works.
Shader code
// ATTENTION
// This shader uses multiple include files! Copying this file alone won't work!
// See https://github.com/eldskald/godot4-cel-shader for all the files!
shader_type spatial;
#define USE_ALPHA 0
#define USE_ALPHA_CUTOFF 0
#define USE_EMISSION 0
#define USE_REFLECTIONS 0
#define USE_NORMAL_MAP 0
#define USE_OCCLUSION 0
#define USE_ANISOTROPY 0
#define USE_BACKLIGHT 0
#define USE_REFRACTION 0
#if USE_ALPHA
render_mode depth_draw_always;
#endif
#include "includes/base-cel-shader.gdshaderinc"
#if USE_EMISSION
#include "includes/emission.gdshaderinc"
#endif
#if USE_REFLECTIONS
#include "includes/reflections.gdshaderinc"
#endif
#if USE_NORMAL_MAP
#include "includes/normal-map.gdshaderinc"
#endif
#if USE_OCCLUSION
#include "includes/occlusion.gdshaderinc"
#endif
#if USE_ANISOTROPY
#include "includes/anisotropy.gdshaderinc"
#endif
#if USE_BACKLIGHT
#include "includes/backlight.gdshaderinc"
#endif
#if USE_REFRACTION
#include "includes/refraction.gdshaderinc"
#elif !USE_REFRACTION && USE_ALPHA
#include "includes/transparency.gdshaderinc"
#endif
group_uniforms BaseProperties;
#if USE_ALPHA_CUTOFF
uniform float alpha_cutoff: hint_range(0.0, 1.0) = 0.5;
#endif
uniform vec4 color: source_color = vec4(0.7, 0.12, 0.86, 1.0);
uniform sampler2D base_texture: source_color;
uniform vec4 specular: source_color = vec4(0.3, 0.3, 0.3, 0.5);
uniform sampler2D specular_texture: hint_default_white;
uniform vec4 fresnel: source_color = vec4(0.2, 0.2, 0.2, 0.3);
uniform sampler2D fresnel_texture: hint_default_white;
group_uniforms;
varying vec3 SPECULAR_COLOR;
varying float SPECULAR_AMOUNT;
varying vec3 FRESNEL_COLOR;
varying float FRESNEL_AMOUNT;
group_uniforms Tiling;
uniform vec2 uv_scale = vec2(1,1);
uniform vec2 uv_offset = vec2(0,0);
group_uniforms;
void vertex() {
UV = UV * uv_scale.xy + uv_offset.xy;
}
void fragment() {
ALBEDO = color.rgb * texture(base_texture, UV).rgb;
#if USE_ALPHA
float alpha = color.a * texture(base_texture, UV).a;
ALBEDO *= alpha;
#elif USE_ALPHA_CUTOFF
ALPHA = color.a * texture(base_texture, UV).a;
ALPHA_SCISSOR_THRESHOLD = color.a * texture(base_texture, UV).a;
#endif
#if USE_REFRACTION && USE_ALPHA
EMISSION += refraction_fragment(alpha, NORMAL, SCREEN_UV, FRAGCOORD.z);
#elif !USE_REFRACTION && USE_ALPHA
EMISSION += transparency_fragment(alpha, SCREEN_UV);
#endif
SPECULAR_COLOR = specular.rgb * texture(specular_texture, UV).rgb;
SPECULAR_AMOUNT = specular.a * texture(specular_texture, UV).a;
FRESNEL_COLOR = fresnel.rgb * texture(fresnel_texture, UV).rgb;
FRESNEL_AMOUNT = fresnel.a * texture(fresnel_texture, UV).a;
#if USE_EMISSION
EMISSION += emission_fragment(UV);
#endif
#if USE_REFLECTIONS
Surface surf = reflections_fragment(UV);
METALLIC = surf.metallic;
ROUGHNESS = surf.roughness;
#endif
#if USE_NORMAL_MAP
NormalData normal = normal_map_fragment(UV, NORMAL, TANGENT, BINORMAL);
NORMAL = normal.vector;
NORMAL_MAP = normal.map;
NORMAL_MAP_DEPTH = normal.depth;
#endif
#if USE_OCCLUSION
OcclusionData occlusion = occlusion_fragment(UV);
AO = occlusion.ao;
AO_LIGHT_AFFECT = occlusion.ao_light_affect;
#endif
#if USE_ANISOTROPY
AnisotropyData aniso = anisotropy_fragment(UV);
ANISOTROPY_DIR = aniso.direction;
ANISOTROPY_RATIO = aniso.ratio;
#endif
#if USE_BACKLIGHT
BACKLIGHT = backlight_fragment(UV);
#endif
}
void light() {
#if USE_BACKLIGHT
DIFFUSE_LIGHT += backlight_diffuse(
ALBEDO,
LIGHT_COLOR,
LIGHT,
NORMAL,
ATTENUATION,
BACKLIGHT
);
#else
DIFFUSE_LIGHT += diffuse_light(
ALBEDO,
LIGHT_COLOR,
LIGHT,
NORMAL,
ATTENUATION
);
#endif
#if USE_ANISOTROPY
SPECULAR_LIGHT += anisotropy_specular(
LIGHT_COLOR,
SPECULAR_COLOR,
SPECULAR_AMOUNT,
NORMAL,
VIEW,
LIGHT,
ATTENUATION,
UV,
ANISOTROPY_DIR,
ANISOTROPY_RATIO
);
#else
SPECULAR_LIGHT += specular_light(
LIGHT_COLOR,
SPECULAR_COLOR,
SPECULAR_AMOUNT,
NORMAL,
VIEW,
LIGHT,
ATTENUATION
);
#endif
SPECULAR_LIGHT += fresnel_light(
LIGHT_COLOR,
FRESNEL_COLOR,
FRESNEL_AMOUNT,
NORMAL,
VIEW,
LIGHT,
ATTENUATION
);
}


I’m new to shaders. i tried this shader on an anime style character but i’m getting some kind of weird artifacts when i zoom in close. anybody know what could be causing this?
https://freeimage.host/i/HUAnjs4
This is some really good work. Thanks for publishing this.
Very nice, thank you so much!
Hey, awesome work! Is there anyway to disable the specular completely?
Make the color black.
In the demo did you write the sky shader? It looks absolutely fantastic and I want to use it for my game. If I credit you, may I use it?
tateorrtot
It’s this one:
https://godotshaders.com/shader/stylized-sky-shader-with-clouds/
It’s credited on my repo and I messed around a lot with the settings, I might have messed around with the code as well, I’m not sure. The volumetric fog shader I was playing with is also credited on my repo.
Has anyone tried to implement this in a mobile game? It doesn’t render and the materials just turn gray
hello, and thank you very much for sharing this but im getting an error, after setting up stuff correctly it says “(66) Redefinition of ‘SPECULAR_AMOUNT'” How do I fix this?
Fantastic, really good results, thanks very much
There was a visual glitch on the normal map for large flat surfaces.
I made a simple “fixed” it following this tutorial.
I put the word “fixed” in quotations because I not sure the way it looked before the fix was intentional or not, also amazing work this shader looks really good.
// Old normal-map.gdshaderinc
group_uniforms NormalMap;
uniform float normal_scale : hint_range(-16,16) = 1.0;
uniform sampler2D normal_map : hint_normal;
group_uniforms;
struct NormalData {
vec3 vector;
vec3 map;
float depth;
};
NormalData normal_map_fragment(
vec2 uv,
vec3 normal,
vec3 tangent,
vec3 binormal,
) {
vec3 map = texture(normal_map, uv).rgb;
vec3 vector = map;
vector.xy = vector.xy * 2.0 – 1.0;
// Old ——————
vector.z = sqrt(max(0.0, 1.0 – dot(vector.xy, vector.xy)));
vec3 ref_normal = normalize(mix(
normal,
tangent * vector.x + binormal * vector.y + normal * vector.z,
normal_scale
));
return NormalData(vector, map, normal_scale);
// New ——————
vector.z = sqrt(1.0 – dot(vector.xy, vector.xy));
mat3 normal_space = mat3(tangent, binormal, normal);
return NormalData(normal_space * vector, map, normal_scale);
// ——————
}
i have no idea how to actually use this and yes i read everything and watched all your videos
Hey! Looks great, but i have an issue, my characters textures become filtered after applying the shaders, any fix? I already checked in project settings and have nearest filtering for textures
Just add filter_nearest option to your base_texture in your cel-shader-base.gdshader
Like this:
uniform sampler2D base_texture: source_color, filter_nearest;