Going further than the tutorial:
- Changed the eye color to be hazel
- Small modifications to the smile, pupils, highlights, and eyebrows
Live Version:
Try clicking and dragging with your mouse!
GLSL / ShaderToy Code
#define S(a, b, t) smoothstep(a, b, t)
#define sat(x) clamp(x, 0.0, 1.0)
float remap01(float a, float b, float t) {
return sat((t - a) / (b - a));
}
float remap(float a, float b, float c, float d, float t) {
return sat((t - a) / (b - a)) * (d - c) + c;
}
vec2 within(vec2 uv, vec4 rect) {
return (uv - rect.xy) / (rect.zw - rect.xy);
}
vec4 createBrow(vec2 uv, float smile) {
float offset = mix(0.1, 0.0, smile);
uv.y += offset;
float y = uv.y;
// Skew eyebrows down
uv.y += uv.x * mix(1.0, 0.625, smile) - 0.3;
// Push out from center
uv.x -= mix(-0.025, 0.05, smile);
uv -= 0.5;
// --- Brows ---
vec4 color = vec4(0.0);
float blur = 0.1;
float dist1 = length(uv);
float s1 = S(0.4, 0.475 - blur, dist1);
float dist2 = length(uv - vec2(0.1, -0.2) * 0.75);
float s2 = S(0.5, 0.5 - blur, dist2);
float browMask = sat(s1 - s2);
float colorMask = remap01(0.6, 0.9, y) * 0.8;
colorMask *= S(0.7, 1.0, browMask);
vec4 browColor = mix(vec4(0.4, 0.2, 0.2, 1.0), vec4(1.0, 0.75, 0.5, 1.0), colorMask);
// --- Shadow ---
uv.y += 0.1 - offset;
blur += mix(0.0, 0.2, smile);
dist1 = length(uv);
s1 = S(0.4, 0.475 - blur, dist1);
dist2 = length(uv - vec2(0.1, -0.2) * 0.75);
s2 = S(0.5, 0.5 - blur, dist2);
float shadowMask = sat(s1 - s2);
color = mix(color, vec4(0.0, 0.0, 0.0, 1.0), S(0.0, 1.0, shadowMask) * 0.5);
color = mix(color, browColor, S(0.2, 0.4, browMask));
return color;
}
vec4 createEye(vec2 uv, float side, vec2 mouse, float smile) {
// Re-center uv
uv -= 0.5;
// Swap highlight
uv.x *= side;
float dist = length(uv);
// Create iris color
vec4 irisColor = vec4(0.396, 0.263, 0.129, 1.0);
vec4 irisColorInner = vec4(0.055, 0.169, 0.114, 0.5);
// --- White of the eye ---
vec4 color = vec4(1.0);
color = mix(color, irisColor, S(0.1, 0.7, dist) * 0.5);
color.a = S(0.5, 0.48, dist);
// Edge shadow for white of eyes
color.rgb *= 1.0 - S(0.45, 0.5, dist) * 0.5 * sat(-uv.y - uv.x * side);
// --- Iris ---
// Iris to follow the mouse
dist = length(uv - mouse * 0.6);
// Iris outline
color.rgb = mix(color.rgb, vec3(0.0), S(0.3, 0.28, dist));
// Draw iris
irisColor.rgb *= 0.75 + S(0.35, 0.07, dist) * irisColorInner.rgb * 5.0;
irisColorInner.rgb *= 1.0 + S(0.2, 0.05, dist);
float irisMask = S(0.28, 0.27, dist);
color.rgb = mix(color.rgb, irisColor.rgb, irisMask);
// --- Pupil ---
dist = length(uv - mouse * 0.65);
float pupilSize = mix(0.3, 0.15, smile);
float pupilMask = S(pupilSize, pupilSize * 0.85, dist);
// Constrain iris to pupil
pupilMask *= irisMask;
color.rgb = mix(color.rgb, vec3(0.0), pupilMask);
// --- Highlights ---
// Animate highlights
float time = iTime * 3.0;
vec2 offset = vec2(sin(time + uv.y * 25.0), sin(time + uv.x * 25.0));
offset *= 0.01 * (1.0 - smile);
uv += offset;
// Draw highlights
float highlight = S(0.1, 0.05, length(uv - vec2(-0.15, 0.15)));
highlight += S(0.07, 0.01, length(uv - vec2(0.08, -0.08)));
color.rgb = mix(color.rgb, vec3(1.0), highlight);
return color;
}
vec4 createMouth(vec2 uv, float smile) {
// Reset origin
uv -= 0.5;
// Mouth color
vec4 color = vec4(0.5, 0.18, 0.05, 1.0);
uv.y *= 1.8;
uv.y -= uv.x * uv.x * 2.0 * smile;
uv.x *= mix(2.5, 1.0, smile);
float dist = length(uv);
color.a = S(0.5, 0.48, dist);
// --- Teeth ---
vec2 teethUv = uv;
teethUv.y += (abs(uv.x) * 0.5 + 0.1) * (1.0 - smile);
float teethDist = length(teethUv - vec2(0.0, 0.6));
vec3 teethColor = vec3(1.0) * S(0.6, 0.35, dist);
color.rgb = mix(color.rgb, teethColor, S(0.4, 0.37, teethDist));
// --- Tongue ---
float tongueDist = length(uv + vec2(0.0, 0.5));
color.rgb = mix(color.rgb, vec3(1.0, 0.5, 0.5), S(0.5, 0.2, tongueDist));
return color;
}
vec4 createHead(vec2 uv) {
// Orange color
vec4 color = vec4(0.9, 0.65, 0.1, 1.0);
// Create a circle
float dist = length(uv);
color.a = S(0.5, 0.49, dist);
// --- Edge shadow ---
float edgeShadow = remap01(0.35, 0.5, dist);
edgeShadow *= edgeShadow;
color.rgb *= 1.0 - edgeShadow * 0.5;
// Create outline
color.rgb = mix(color.rgb, vec3(0.6, 0.3, 0.1), S(0.4799, 0.48, dist));
// --- Highlight ---
float highlight = S(0.41, 0.39, dist);
highlight *= remap(0.41, -0.1, 0.75, 0.0, uv.y);
highlight *= S(0.18, 0.19, length(uv - vec2(0.21, 0.08)));
color.rgb = mix(color.rgb, vec3(1.0), highlight);
// --- Cheeks ---
dist = length(uv - vec2(0.25, -0.2));
float cheek = S(0.2, 0.01, dist) * 0.3;
cheek *= S(0.17, 0.16, dist);
color.rgb = mix(color.rgb, vec3(1.0, 0.1, 0.1), cheek);
return color;
}
vec4 createSmiley(vec2 uv, vec2 mouse, float smile) {
vec4 color = vec4(0.0);
// Mirror face while determining side
if (length(uv) < 0.5) {
float side = sign(uv.x);
uv.x = abs(uv.x);
vec4 head = createHead(uv);
vec4 eye = createEye(within(uv, vec4(0.03, -0.1, 0.37, 0.25)), side, mouse, smile);
vec4 mouth = createMouth(within(uv, vec4(-0.3, -0.43, 0.3, -0.13)), smile);
vec4 brow = createBrow(within(uv, vec4(.03, .2, .4, .45)), smile);
color = mix(color, head, head.a);
color = mix(color, eye, eye.a);
color = mix(color, mouth, mouth.a);
color = mix(color, brow, brow.a);
}
return color;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates (from 0 to 1)
vec2 uv = fragCoord.xy / iResolution.xy;
// Move origin to the center
uv -= 0.5;
// Account for aspect ratio
uv.x *= iResolution.x / iResolution.y;
// Get mouse coordinates
vec2 mouse = iMouse.xy / iResolution.xy;
mouse -= 0.5;
//uv -= mouse * (0.5 - length(uv)) * 0.15;
uv -= mouse * (0.5 - dot(uv, uv));// * 1.0;
// Initialize the color value to be black
vec3 color = vec3(0.0);
float smile = cos(iTime) * 0.5 + 0.5;
// Output to screen
fragColor = createSmiley(uv, mouse, smile);
}
Tutorial I followed:
Resources:
ShaderToy