Anime Stylized Skin/Hairs/Clothes (4.3)
Found this in a blender model and reproduced it 1-1.
You can see that I used many of blender glsl functions directly.
Some notes:
- The shadows and reflections are fake and rely on a fake light and shadow direction.
You have total artistic freedom but it comes at the expanse of lot of variables. The shader could be greatly improved by infering the light, shadow and ink colors from the color texture/gradient. - Some of the uniforms are not used because the shader was weird and added a AO that was not used.
- I added a custom hard light function with only 2 values possible, but godot shadows are not great for small objects.
If you want to discard the light function change ALBEDO = color_res.rgb; with EMISSION = color_res.rgb.
How does it work ?
There are 3 major steps:
- Compute texture (this you have to modify often)
- Compute ink outline
- Compute fake light
- Compute fake shadows
Then it’s all mixed together.
Shader code
shader_type spatial;
render_mode ambient_light_disabled;
uniform float roughness : hint_range(0.0, 1.0, 0.5) = 0.5;
uniform sampler2D anime_texture_1 : source_color;
uniform sampler2D anime_texture_2 : source_color;
uniform sampler2D transparency_texture : source_color;
uniform vec2 transparency_scale = vec2(1., 1.);
uniform bool disable_gradient_color = false;
uniform sampler2D texture_gradient_color_1 : source_color, repeat_disable ;
uniform sampler2D texture_gradient_color_2 : source_color, repeat_disable ;
uniform vec4 base_text: source_color = vec4(1., 0.84, 0.71, 1.);
uniform vec3 ink_color: source_color = vec3(1., 0.447, 0.);
uniform float ink_opacity : hint_range(0, 1) = 0.0;
uniform float ink_intensity = 2.;
uniform vec4 fake_shadow_color: source_color = vec4(1., 0.447, 0., 1.);
uniform float fake_shadow_opacity : hint_range(0, 1) = 1.0;
uniform vec3 fake_shadow_direction = vec3(0.);
uniform vec4 fake_light_color: source_color = vec4(1., 0.89, 0.47, 1.);
uniform float fake_light_opacity : hint_range(0, 1) = 1.;
uniform vec3 fake_light_direction = vec3(0.);
uniform vec4 layer_weight_color: source_color = vec4(1., 0.55, 0.28, 1.);
uniform float layer_weight_shadow_intensity : hint_range(0, 1) = 0.7;
uniform float layer_weight_adjust = 0.0;
uniform float layer_weight_opacity : hint_range(0, 1) = 1.;
uniform vec4 color2: source_color = vec4(1., 0.55, 0.28, 1.);
uniform vec3 AO_Color: source_color = vec3(1., 0.55, 0.28);
uniform float AO_Intensity : hint_range(0, 1) = 1.0;
uniform float AO_Boost = 0.0;
uniform float Fac : hint_range(0, 1) = 1.0;
uniform vec4 ambient_light_color : source_color = vec4(1.);
// useless for now
float fresnel_dielectric_cos(float cosi, float eta)
{
// compute fresnel reflectance without explicitly computing
// the refracted direction
float c = abs(cosi);
float g = eta * eta - 1. + c * c;
if (g > 0.) {
g = sqrt(g);
float A = (g - c) / (g + c);
float B = (c * (g + c) - 1.) / (c * (g - c) + 1.);
return 0.5f * A * A * (1. + B * B);
}
return 1.0f; // TIR(no refracted component)
}
// https://forums.ogre3d.org/viewtopic.php?t=96924
float fresnel_dielectric(vec3 Incoming, vec3 Normal, float eta)
{
/* compute fresnel reflectance without explicitly computing
the refracted direction */
float c = abs(dot(Incoming, Normal));
float g = eta * eta - 1.0 + c * c;
float result;
if(g > 0.0) {
g = sqrt(g);
float A =(g - c)/(g + c);
float B =(c *(g + c)- 1.0)/(c *(g - c)+ 1.0);
result = 0.5 * A * A *(1.0 + B * B);
}
else
result = 1.0; /* TIR (no refracted component) */
return result;
}
// https://github.com/dfelinto/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_layer_weight.glsl
void node_layer_weight(mat4 projection_matrix, bool is_front_facing, float blend, vec3 N, vec3 I, out float fresnel, out float facing)
{
N = normalize(N);
/* fresnel */
float eta = max(1.0 - blend, 0.00001);
vec3 I_view = (projection_matrix[3][3] == 0.0) ? normalize(I) : vec3(0.0, 0.0, -1.0);
fresnel = fresnel_dielectric(I_view, N, (is_front_facing) ? 1.0 / eta : eta);
/* facing */
facing = abs(dot(I_view, N));
if (blend != 0.5) {
blend = clamp(blend, 0.0, 0.99999);
blend = (blend < 0.5) ? 2.0 * blend : 0.5 / (1.0 - blend);
facing = pow(facing, blend);
}
facing = 1.0 - facing;
}
// x belong to [min, max] to x belong to [a, b]
// f(x) = ((x-min)(b-a))/(max-min) + a
// max and min = input_min and input_max
// a and b = output_min and output_max
// x = value
// https://github.com/dfelinto/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_map_range.glsl
// LINE 155
float map_range_stepped(float value, float from_min, float from_max, float to_min, float to_max, float steps){
float result;
float factor = value;
factor = (value - from_min) / (from_max - from_min);
factor = (steps > 0.0f) ? floor(factor * (steps + 1.0f)) / steps : 0.0f;
result = to_min + factor * (to_max - to_min);
return result;
}
float map_range_linear(float value, float from_min, float from_max, float to_min, float to_max){
float result = to_min + ((value - from_min) / (from_max - from_min)) * (to_max - to_min);
return result;
}
// mix functions:
// https://github.com/dfelinto/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_mix_rgb.glsl
void mix_add(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
fac = clamp(fac, 0.0, 1.0);
outcol = mix(col1, col1 + col2, fac);
outcol.a = col1.a;
}
void mix_mult(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
fac = clamp(fac, 0.0, 1.0);
outcol = mix(col1, col1 * col2, fac);
outcol.a = col1.a;
}
void mix_blend(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
fac = clamp(fac, 0.0, 1.0);
outcol = mix(col1, col2, fac);
outcol.a = col1.a;
}
void mix_overlay(float fac, vec4 col1, vec4 col2, out vec4 outcol)
{
fac = clamp(fac, 0.0, 1.0);
float facm = 1.0 - fac;
outcol = col1;
if (outcol.r < 0.5) {
outcol.r *= facm + 2.0 * fac * col2.r;
}
else {
outcol.r = 1.0 - (facm + 2.0 * fac * (1.0 - col2.r)) * (1.0 - outcol.r);
}
if (outcol.g < 0.5) {
outcol.g *= facm + 2.0 * fac * col2.g;
}
else {
outcol.g = 1.0 - (facm + 2.0 * fac * (1.0 - col2.g)) * (1.0 - outcol.g);
}
if (outcol.b < 0.5) {
outcol.b *= facm + 2.0 * fac * col2.b;
}
else {
outcol.b = 1.0 - (facm + 2.0 * fac * (1.0 - col2.b)) * (1.0 - outcol.b);
}
}
// https://github.com/dfelinto/blender/blob/master/source/blender/gpu/shaders/material/gpu_shader_material_rgb_to_bw.glsl
void rgbtobw(vec4 color, out float outval)
{
vec3 factors = vec3(0.2126, 0.7152, 0.0722);
outval = dot(color.rgb, factors);
}
void fragment(){
ALBEDO = vec3(0.);
ROUGHNESS = roughness;
//METALLIC = 0.;
// variables for later
float useless;
float facing;
float fake_light_power;
vec3 light_direction;
vec3 shadow_direction;
float fake_shadow_power;
float ink_outline_power;
float fake_color_power;
vec4 color_res;
// FAKE LIGHT POWER
light_direction = normalize(VIEW + fake_light_direction);
fake_light_power = dot(NORMAL, light_direction);
fake_light_power = pow(fake_light_power, 1.0);
fake_light_power = map_range_stepped(fake_light_power, -0.2, 1.3, 0., 14., 4);
fake_light_power = 1. - fake_light_power;
fake_light_power = mix(0, fake_light_power, fake_light_opacity);
// FAKE SHADOW POWER (Properly done)
shadow_direction = normalize(VIEW + fake_shadow_direction);
fake_shadow_power = dot(NORMAL, shadow_direction);
fake_shadow_power = map_range_stepped(fake_shadow_power, 0, 1.3, 0., 14., 4);
fake_shadow_power = 1. - fake_shadow_power;
fake_shadow_power = mix(0, fake_shadow_power, fake_shadow_opacity);
// INK OUTLINE POWER (Properly done)
node_layer_weight(PROJECTION_MATRIX, FRONT_FACING, 0.5, NORMAL, VIEW, ink_outline_power, facing);
ink_outline_power = ink_outline_power * AO_Intensity;
if(ink_outline_power < 0.388){ink_outline_power = 0.;} else {ink_outline_power = 1.;}
ink_outline_power = mix(0, ink_outline_power, ink_opacity);
// TEXTURE
// texture of clothes color
float clothes_color_radiance;
vec4 clothes_color = texture(anime_texture_1, UV);
rgbtobw(clothes_color, clothes_color_radiance);
vec4 clothes_color_temp = texture(texture_gradient_color_1, vec2(clothes_color_radiance));
// fabric texturing
// computing the two color ramps from the fabric texture color
float fabric_texture_radiance;
float fabric_texture_radiance_color_ramp_1;
float fabric_texture_radiance_color_ramp_2;
vec4 fabric_texture_color = texture(anime_texture_2, UV);
rgbtobw(fabric_texture_color, fabric_texture_radiance);
vec4 temp_fabric_color_ramp_2 = texture(texture_gradient_color_2, vec2(fabric_texture_radiance));
vec4 temp_fabric_color_ramp_1 = texture(texture_gradient_color_1, vec2(fabric_texture_radiance));
rgbtobw(temp_fabric_color_ramp_2, fabric_texture_radiance_color_ramp_2);
rgbtobw(temp_fabric_color_ramp_1, fabric_texture_radiance_color_ramp_1);
// In case of true colors for texture, we use it
if(disable_gradient_color){
mix_mult(fabric_texture_radiance_color_ramp_2, clothes_color, temp_fabric_color_ramp_1, clothes_color);
} else {
mix_mult(fabric_texture_radiance_color_ramp_2, clothes_color_temp, temp_fabric_color_ramp_1, clothes_color);
}
// FAKE COLOR (Properly done)
// Res in color_res
node_layer_weight(PROJECTION_MATRIX, FRONT_FACING, layer_weight_shadow_intensity, NORMAL, VIEW, useless, facing);
float color_map_range = map_range_linear(facing, layer_weight_adjust, 1., 0., 1.);
float color_factor = mix(0., color_map_range, Fac);
vec4 temp;
mix_overlay(1., layer_weight_color, color2, temp);
mix_mult(color_factor, clothes_color, temp, color_res);
// FINAL MIX
mix_mult(ink_outline_power, color_res, base_text, color_res); // mix color with ink
mix_mult(fake_shadow_power, color_res, fake_shadow_color, color_res); // mix res with fake_shadow_color
mix_add(fake_light_power, color_res, fake_light_color, color_res); // mix res with fake_light_color
mix_mult(0.5, color_res, ambient_light_color, color_res);
ALBEDO = (color_res.rgb);
}
// LIGHT WITH HARD 2 COLORS SHADERS
global uniform sampler2D ATTENUATION_STYLIZED : source_color, repeat_disable;
void light(){
vec4 color_of_shadow_1D_gradient = texture(ATTENUATION_STYLIZED, vec2(ATTENUATION));
float radiance_of_shadow_1D_gradient;
rgbtobw(color_of_shadow_1D_gradient, radiance_of_shadow_1D_gradient);
DIFFUSE_LIGHT = radiance_of_shadow_1D_gradient * ALBEDO;
}