SimpleToonHair

Simple toon hair shader project for Godot engine, project need engine version 4.1 Shader supporting diffuse and shadow texture.

Blender version in here: https://d3lnzed.gumroad.com/l/unyroh

Inspired by GloriaTheAnimator’s Ultimate Hair Shader for blender

Using shader noise function from: https://github.com/stegu/webgl-noise

Hair model from VRoid Studio

Shader code
shader_type spatial;

uniform sampler2D _base_color: source_color, hint_default_black;
uniform float _sharpness: hint_range(0.0, 0.99) = 0.0;
uniform float _thinness: hint_range(0.0, 0.99) = 0.0;
uniform float _density: hint_range(0.0, 200) = 15.7;
uniform float _smoothness: hint_range(-1.0, 0.0) = -0.9;
uniform float _blur: hint_range(0.0, 1.0) = 0.6;
uniform float _height: hint_range(-0.3, 0.4) = 0.0;
uniform float _transparency_0: hint_range(0.0, 1.0) = 0.0;
uniform float _spaces: hint_range(-50.0, 0.0) = -25.6;
uniform float _detail: hint_range(0.0, 5.0) = 1.0;
uniform float _transparency_1: hint_range(0.0, 100.0) = 4.1;
uniform float _overlay_shadow: hint_range(0.0, 1.0) = 0.0;
uniform float _overlaypure_color: hint_range(0.0, 1.0) = 0.0;
uniform float _overlay_hue: hint_range(0.0, 1.0) = 0.55;
uniform float _brightness: hint_range(0.0, 2.0) = 0.4;
uniform float _overlay_saturation: hint_range(0.0, 2.0) = 0.9;
uniform sampler2D _shadow_color: source_color, hint_default_black;
uniform float _shadow_size: hint_range(0.1, 1.0) = 0.57;
uniform float _shadow_opacity: hint_range(0.0, 1.0) = 1.0;
uniform float _shadow_transition: hint_range(0.001, 1.0) = 0.001;
uniform float _smooth_shadow: hint_range(0.0, 1.0) = 0.0;
uniform float _rotate_90_degrees: hint_range(0.0, 1.0) = 0.0;
uniform sampler2D _normal_map: hint_normal;
uniform float _normal_strength: hint_range(0.0, 20.0) = 0.15;

uniform sampler2D _highlight_gradient_1;
uniform sampler2D _highlight_gradient_2;

varying float highlight_ramp;
varying vec4 hsv_overlay;
varying vec4 shadow_color;

vec3 mod289(vec3 x)
{
	return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 mod289_vec4(vec4 x)
{
	return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x)
{
	return mod289_vec4(((x*34.0)+10.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
	return 1.79284291400159 - 0.85373472095314 * r;
}

vec3 fade(vec3 t) {
	return t*t*t*(t*(t*6.0-15.0)+10.0);
}

// Classic Perlin noise
float cnoise(vec3 P)
{
	vec3 Pi0 = floor(P); // Integer part for indexing
	vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1
	Pi0 = mod289(Pi0);
	Pi1 = mod289(Pi1);
	vec3 Pf0 = fract(P); // Fractional part for interpolation
	vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
	vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
	vec4 iy = vec4(Pi0.yy, Pi1.yy);
	vec4 iz0 = Pi0.zzzz;
	vec4 iz1 = Pi1.zzzz;

	vec4 ixy = permute(permute(ix) + iy);
	vec4 ixy0 = permute(ixy + iz0);
	vec4 ixy1 = permute(ixy + iz1);

	vec4 gx0 = ixy0 * (1.0 / 7.0);
	vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
	gx0 = fract(gx0);
	vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
	vec4 sz0 = step(gz0, vec4(0.0));
	gx0 -= sz0 * (step(0.0, gx0) - 0.5);
	gy0 -= sz0 * (step(0.0, gy0) - 0.5);

	vec4 gx1 = ixy1 * (1.0 / 7.0);
	vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
	gx1 = fract(gx1);
	vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
	vec4 sz1 = step(gz1, vec4(0.0));
	gx1 -= sz1 * (step(0.0, gx1) - 0.5);
	gy1 -= sz1 * (step(0.0, gy1) - 0.5);

	vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
	vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
	vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
	vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
	vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
	vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
	vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
	vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

	vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
	g000 *= norm0.x;
	g010 *= norm0.y;
	g100 *= norm0.z;
	g110 *= norm0.w;
	vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
	g001 *= norm1.x;
	g011 *= norm1.y;
	g101 *= norm1.z;
	g111 *= norm1.w;

	float n000 = dot(g000, Pf0);
	float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
	float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
	float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
	float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
	float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
	float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
	float n111 = dot(g111, Pf1);

	vec3 fade_xyz = fade(Pf0);
	vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
	vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
	float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 
	return 2.2 * n_xyz;
}

// Classic Perlin noise, periodic variant
float pnoise(vec3 P, vec3 rep)
{
	vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period
	vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period
	Pi0 = mod289(Pi0);
	Pi1 = mod289(Pi1);
	vec3 Pf0 = fract(P); // Fractional part for interpolation
	vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0
	vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);
	vec4 iy = vec4(Pi0.yy, Pi1.yy);
	vec4 iz0 = Pi0.zzzz;
	vec4 iz1 = Pi1.zzzz;

	vec4 ixy = permute(permute(ix) + iy);
	vec4 ixy0 = permute(ixy + iz0);
	vec4 ixy1 = permute(ixy + iz1);

	vec4 gx0 = ixy0 * (1.0 / 7.0);
	vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;
	gx0 = fract(gx0);
	vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);
	vec4 sz0 = step(gz0, vec4(0.0));
	gx0 -= sz0 * (step(0.0, gx0) - 0.5);
	gy0 -= sz0 * (step(0.0, gy0) - 0.5);

	vec4 gx1 = ixy1 * (1.0 / 7.0);
	vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;
	gx1 = fract(gx1);
	vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);
	vec4 sz1 = step(gz1, vec4(0.0));
	gx1 -= sz1 * (step(0.0, gx1) - 0.5);
	gy1 -= sz1 * (step(0.0, gy1) - 0.5);

	vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);
	vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);
	vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);
	vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);
	vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);
	vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);
	vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);
	vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);

	vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));
	g000 *= norm0.x;
	g010 *= norm0.y;
	g100 *= norm0.z;
	g110 *= norm0.w;
	vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));
	g001 *= norm1.x;
	g011 *= norm1.y;
	g101 *= norm1.z;
	g111 *= norm1.w;

	float n000 = dot(g000, Pf0);
	float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));
	float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));
	float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));
	float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));
	float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));
	float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));
	float n111 = dot(g111, Pf1);

	vec3 fade_xyz = fade(Pf0);
	vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);
	vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);
	float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x); 
	return 2.2 * n_xyz;
}

vec3 rotate_around_axis(vec3 p, vec3 axis, float angle)
{
	float costheta = cos(angle);
	float sintheta = sin(angle);
	vec3 r;

	r.x = ((costheta + (1.0 - costheta) * axis.x * axis.x) * p.x) +
		(((1.0 - costheta) * axis.x * axis.y - axis.z * sintheta) * p.y) +
		(((1.0 - costheta) * axis.x * axis.z + axis.y * sintheta) * p.z);

	r.y = (((1.0 - costheta) * axis.x * axis.y + axis.z * sintheta) * p.x) +
		((costheta + (1.0 - costheta) * axis.y * axis.y) * p.y) +
		(((1.0 - costheta) * axis.y * axis.z - axis.x * sintheta) * p.z);

	r.z = (((1.0 - costheta) * axis.x * axis.z - axis.y * sintheta) * p.x) +
		(((1.0 - costheta) * axis.y * axis.z + axis.x * sintheta) * p.y) +
		((costheta + (1.0 - costheta) * axis.z * axis.z) * p.z);

	return r;
}

vec3 vector_rotate_axis_angle(vec3 vector_in, vec3 center, vec3 axis, float angle, vec3 rotation, float invert)
{
	return (length(axis) != 0.0) ?
			rotate_around_axis(vector_in - center, normalize(axis), angle * invert) + center :
			vector_in;
}

// Noise
uint rot(uint x, uint k)
{
	return (((x) << (k)) | ((x) >> (32u - (k))));
}

void final(inout uint a, inout uint b, inout uint c)
{
	c ^= b;
	c -= rot(b, 14u);
	a ^= c;
	a -= rot(c, 11u);
	b ^= a;
	b -= rot(a, 25u);
	c ^= b;
	c -= rot(b, 16u);
	a ^= c;
	a -= rot(c, 4u);
	b ^= a;
	b -= rot(a, 14u);
	c ^= b;
	c -= rot(b, 24u);
}

uint hash_uint2(uint kx, uint ky)
{
	uint a, b, c;
	a = b = c = uint(0xdeadbeef) + (2u << 2u) + 13u;

	b += ky;
	a += kx;
	final(a, b, c);

	return c;
}

float hash_uint2_to_float(uint kx, uint ky)
{
	return float(hash_uint2(kx, ky)) / float(0xFFFFFFFF);
}

float hash_vec2_to_float(vec2 k)
{
	return hash_uint2_to_float(floatBitsToUint(k.x), floatBitsToUint(k.y));
}

vec3 random_vec3_offset(float seed)
{
	return vec3(100.0 + hash_vec2_to_float(vec2(seed, 0.0)) * 100.0,
				100.0 + hash_vec2_to_float(vec2(seed, 1.0)) * 100.0,
				100.0 + hash_vec2_to_float(vec2(seed, 2.0)) * 100.0);
}

float noise_scale3(float result)
{
	return 0.9820 * result;
}

float snoise(vec3 p)
{
	float r = pnoise(p, vec3(0.0));
	return (isinf(r)) ? 0.0 : noise_scale3(r);
}

float noise(vec3 p)
{
	return 0.5 * snoise(p) + 0.5;
}

float fractal_noise(vec3 p, float octaves, float roughness)
{
	float fscale = 1.0;
	float amp = 1.0;
	float maxamp = 0.0;
	float sum = 0.0;
	octaves = clamp(octaves, 0.0, 15.0);
	int n = int(octaves);
	for (int i = 0; i <= n; i++) {
		float t = noise(fscale * p);
		sum += t * amp;
		maxamp += amp;
		amp *= clamp(roughness, 0.0, 1.0);
		fscale *= 2.0;
	}
	float rmd = octaves - floor(octaves);
	if (rmd != 0.0) {
		float t = noise(fscale * p);
		float sum2 = sum + t * amp;
		sum /= maxamp;
		sum2 /= maxamp + amp;
		return (1.0 - rmd) * sum + rmd * sum2;
	}
	else {
		return sum / maxamp;
	}
}

vec4 noise_texture_3d(vec3 co,
	float scale,
	float detail,
	float roughness)
{
	vec3 p = co * scale;
	vec3 rep = vec3(0.0);

	float value = fractal_noise(p, detail, roughness);
	return vec4(value,
				fractal_noise(p + random_vec3_offset(3.0), detail, roughness),
				fractal_noise(p + random_vec3_offset(4.0), detail, roughness),
				1.0);
}
// Noise end

vec4 brightness_contrast(vec4 col, float brightness, float contrast)
{
	float a = 1.0 + contrast;
	float b = brightness - contrast * 0.5;

	vec4 outcol;
	outcol.r = max(a * col.r + b, 0.0);
	outcol.g = max(a * col.g + b, 0.0);
	outcol.b = max(a * col.b + b, 0.0);
	outcol.a = col.a;
	return outcol;
}

float rgbtobw(vec4 color)
{
	vec3 factors = vec3(0.2126, 0.7152, 0.0722);
	return dot(color.rgb, factors);
}

vec3 normal_map_mix(float strength, vec3 newnormal, in vec3 orinormal)
{
	return normalize(mix(orinormal, newnormal, max(strength, 0.0)));
}

float map_range(float value, float from_left, float from_right, float to_left, float to_right) {
	float clamp_value = clamp(value, from_left, from_right);
	return (clamp_value - from_left) / (from_right - from_left) * (to_right - to_left) + to_left;
}

void rgb_to_hsv(vec4 rgb, out vec4 outcol)
{
	float cmax, cmin, h, s, v, cdelta;
	vec3 c;

	cmax = max(rgb[0], max(rgb[1], rgb[2]));
	cmin = min(rgb[0], min(rgb[1], rgb[2]));
	cdelta = cmax - cmin;

	v = cmax;
	if (cmax != 0.0) {
		s = cdelta / cmax;
	}
	else {
		s = 0.0;
		h = 0.0;
	}

	if (s == 0.0) {
		h = 0.0;
	}
	else {
		c = (vec3(cmax) - rgb.xyz) / cdelta;

		if (rgb.x == cmax) {
			h = c[2] - c[1];
		}
		else if (rgb.y == cmax) {
			h = 2.0 + c[0] - c[2];
		}
		else {
			h = 4.0 + c[1] - c[0];
		}

		h /= 6.0;

		if (h < 0.0) {
			h += 1.0;
		}
	}

	outcol = vec4(h, s, v, rgb.w);
}

void hsv_to_rgb(vec4 hsv, out vec4 outcol)
{
	float i, f, p, q, t, h, s, v;
	vec3 rgb;

	h = hsv[0];
	s = hsv[1];
	v = hsv[2];

	if (s == 0.0) {
		rgb = vec3(v, v, v);
	}
	else {
		if (h == 1.0) {
			h = 0.0;
		}

		h *= 6.0;
		i = floor(h);
		f = h - i;
		rgb = vec3(f, f, f);
		p = v * (1.0 - s);
		q = v * (1.0 - (s * f));
		t = v * (1.0 - (s * (1.0 - f)));

		if (i == 0.0) {
			rgb = vec3(v, t, p);
		}
		else if (i == 1.0) {
			rgb = vec3(q, v, p);
		}
		else if (i == 2.0) {
			rgb = vec3(p, v, t);
		}
		else if (i == 3.0) {
			rgb = vec3(p, q, v);
		}
		else if (i == 4.0) {
			rgb = vec3(t, p, v);
		}
		else {
			rgb = vec3(v, p, q);
		}
	}

	outcol = vec4(rgb, hsv.w);
}

vec4 composite_hue_saturation_value(
	vec4 color, float hue, float saturation, float value, float factor)
{
	vec4 hsv;
	rgb_to_hsv(color, hsv);

	hsv.x = fract(hsv.x + hue + 0.5);
	hsv.y = hsv.y * saturation;
	hsv.z = hsv.z * value;

	vec4 result;
	hsv_to_rgb(hsv, result);
	result.rgb = max(result.rgb, vec3(0.0));

	return mix(color, result, factor);
}


//void vertex() {
//}

void fragment() {
	float temp_f0, temp_f1, temp_f2;
	vec4 color_0, color_1;
	
	// highlight
	vec2 uv_fy = vec2(UV.x, 1.0 - UV.y);
	vec3 rot_uv;
	rot_uv = vector_rotate_axis_angle(vec3(uv_fy, 0.0), vec3(0.0), vec3(0.0, 0.0, 1.0), 90, vec3(0.0), 1.0);
	vec2 final_uv = mix(uv_fy, rot_uv.xy, _rotate_90_degrees); // here is mix color origin
	vec4 bc_noise_color;
	color_0 = noise_texture_3d(vec3(final_uv.x), _spaces, _detail, 1.0);
	bc_noise_color = brightness_contrast(color_0, _height, _transparency_1);
	color_0 = noise_texture_3d(vec3(final_uv.x), _density, 0.0, 0.0);
	temp_f0 = color_0.r;
	color_1 = brightness_contrast(color_0, _height, _smoothness);
	temp_f0 = rgbtobw(color_1);
	temp_f0 += -0.5 + final_uv.y;
	vec4 vec_0 = VIEW_MATRIX * vec4(0.0, -1.0, 0.0, 0.0);
	temp_f1 = (-vec_0.z - 0.3) * 0.25;
	temp_f0 -= temp_f1;
	vec4 g1 = texture(_highlight_gradient_1, vec2(temp_f0, 0.5));
	vec4 g2 = texture(_highlight_gradient_2, vec2(temp_f0, 0.5));
	color_0 = mix(g1, g2, _blur);
	temp_f2 = rgbtobw(color_0);
	temp_f0 = rgbtobw(bc_noise_color);
	temp_f1 = map_range(temp_f0, _sharpness, 1.0, 1.0, 0.1);
	temp_f0 = clamp(temp_f2 * temp_f1 - _overlay_shadow, 0.0, 1.0);
	highlight_ramp = clamp((temp_f0 - _thinness) / (1.0 - _thinness), 0.0, 1.0);
	NORMAL = normal_map_mix(_normal_strength, texture(_normal_map, UV).rgb, NORMAL);

	hsv_overlay = vec4(0.0);
	hsv_overlay = composite_hue_saturation_value(vec4(1.0, 0.0, 0.0, 1.0), _overlay_hue, _overlay_saturation, _brightness, 1.0);

	ALBEDO = texture(_base_color, UV).rgb;
	vec4 base_color = vec4(ALBEDO, 1.0);
	shadow_color = texture(_shadow_color, UV);
}

void light() {
	vec4 color_0, color_1;
	float temp_f0, temp_f1;
	vec4 base_color = vec4(ALBEDO, 1.0);
	float light_shadow_rate = map_range(dot(NORMAL, LIGHT), -1.0, 1.0, 0.0, 1.0) * ATTENUATION;
	vec4 color = vec4((light_shadow_rate * (vec4(LIGHT_COLOR, 1.0)) * base_color).rgb, 1.0);
	vec4 mix_base_color;
	mix_base_color = mix(color, base_color, 0.5);
	color_0 = mix_base_color + hsv_overlay;
	color_1 = mix(color_0, hsv_overlay, _overlaypure_color);
	temp_f0 = clamp(highlight_ramp, 0.0, 1.0);
	color_0 = mix(mix_base_color, color_1, temp_f0);
	vec4 light_part;
	light_part = mix(color_0, hsv_overlay, _transparency_0);
	
	temp_f0 = clamp(_shadow_size + _shadow_transition, 0.0, 1.0);
	float map_shadow_rate = map_range(light_shadow_rate, _shadow_size, temp_f0, 0.0, 1.0);
	color_0 = mix(mix_base_color, shadow_color, _shadow_opacity);
	color_1 = brightness_contrast(vec4(vec3(light_shadow_rate), 1.0), 0.0, -0.5);
	color_1 *= color_0;
	vec4 shadow_part;
	shadow_part = mix(color_1, mix_base_color, _smooth_shadow);
	
	color_0 = mix(shadow_part, light_part, map_shadow_rate);
	DIFFUSE_LIGHT += color_0.rgb / ALBEDO;
}
Tags
ceil, hair, 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.
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments