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
	);
}
Tags
#celshader, #toonshader, godot4
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 eldskald

Complete Toon Shader

Related shaders

Complete Toon Shader

Lighting Independent Cel Shader

First Person View Model Shader (UPDATED FOR GODOT 4.3+)

guest

14 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
buster
buster
2 years ago

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

Tsar Vul
Tsar Vul
2 years ago

This is some really good work. Thanks for publishing this.

AaronAllBlacks
2 years ago

Very nice, thank you so much!

Votron
2 years ago

Hey, awesome work! Is there anyway to disable the specular completely?

tateorrtot
tateorrtot
1 year ago

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

Jean
Jean
1 year ago

Has anyone tried to implement this in a mobile game? It doesn’t render and the materials just turn gray

Sanzo
10 months ago

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?

Last edited 10 months ago by Sanzo
Zombyra
Zombyra
8 months ago

Fantastic, really good results, thanks very much

Gabriel Quintino
Gabriel Quintino
5 months ago

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);
// ——————
}

Last edited 5 months ago by Gabriel Quintino
lol
lol
2 months ago

i have no idea how to actually use this and yes i read everything and watched all your videos

bacon
bacon
1 month ago

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

ShadyShade
1 month ago
Reply to  bacon

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;