A Third-Person-Shooter Godot game.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

227 lines
9.3 KiB

// AnimuEyes - procedurally generated anime eyes shader
// add an extra shader pass to your character model and set uvArea to the uv range where you want
// the eyes to appear. xy are the middle point of the rectangle, zw are width/height.
// _if you have other transparent materials, make sure to adjust the render priority_
// adjust the depthOffset if you don't want them to render through hair or if rendering through
// hair is wonky
// godot version tested: 3.2.2 stable
// license: public domain (UNLICENSE)
shader_type spatial;
render_mode unshaded;
// these are the uniforms that should be dynamically changed for facial expressions and such
uniform vec3 lookAt = vec3(0, 0, 100000); // target point relative to eyes
uniform vec4 maxLook = vec4(-.4, -.2, .4, .2); // eye movement range
uniform vec2 lookSens = vec2(.2, .2); // adjust until eyes match up with the look target
uniform float irisScale : hint_range(.1, 1.5) = 1;
uniform float eyebrowOffset = 0;
uniform float eyebrowRotationOffsetDegrees : hint_range(-180, 180) = 0;
uniform float open : hint_range(0, 1) = 1; // might cause some artifacts, mainly used for blinking
// everything else is only recommended to be used to define the base eye aesthetic
uniform vec4 uvArea = vec4(.5, .5, 1, 1);
uniform float position = .2;
uniform float dist = .2;
uniform float depthOffset = .01;
uniform vec4 color : hint_color = vec4(1, .3, 0, 1);
uniform vec4 whiteColor : hint_color = vec4(1, 1, 1, 1);
uniform vec4 rimColor : hint_color = vec4(1, 1, 1, 1);
uniform vec4 eyebrowColor : hint_color = vec4(0, 0, 0, 1);
uniform vec4 border1Color : hint_color = vec4(0, 0, 0, 1);
uniform vec4 border2Color : hint_color = vec4(0, 0, 0, 1);
uniform float shapeSquare : hint_range(0, 1) = .554;
uniform float irisSquare : hint_range(0, 1) = .219;
uniform float border1Square : hint_range(0, 1) = .5;
uniform float border2Square : hint_range(0, 1) = 1;
uniform float eyebrowSquare : hint_range(0, 1) = .5;
uniform vec4 topEllipse = vec4(.444, .598, .5, .4);
uniform vec4 bottomEllipse = vec4(.49, .297, .3, .33);
uniform vec4 irisEllipse = vec4(.53, .35, .17, .236);
uniform vec4 pupilEllipse = vec4(0, .02, .08, .1);
uniform float eyebrowPosition = 0;
uniform vec4 eyebrow = vec4(.037, -.369, 1.1, 1.1);
uniform vec4 eyebrowFalloffEllipse = vec4(.5, -.315, .028, .039);
uniform float eyebrowFalloff : hint_range(0, 1) = .5;
uniform float eyebrowRotationDegrees : hint_range(-180, 180) = -10;
uniform float eyebrowThickness : hint_range(0, .1) = .025;
uniform vec4 border1 = vec4(.025, .016, 1.1, 1.1);
uniform vec4 border1FalloffEllipse = vec4(.222, -.03, .3, .2);
uniform float border1Falloff : hint_range(0, 1) = .3;
uniform float border1RotationDegrees : hint_range(-180, 180) = -3;
uniform float border1Thickness : hint_range(0, .1) = .025;
uniform vec4 border2 = vec4(-.043, .057, .916, 1);
uniform vec4 border2FalloffEllipse = vec4(.5, .861, .03, .049);
uniform float border2Falloff : hint_range(0, 1) = .2;
uniform float border2RotationDegrees : hint_range(-180, 180) = 0;
uniform float border2Thickness : hint_range(0, .1) = .008;
uniform float sharpness : hint_range(100, 1000) = 200;
uniform vec2 scale = vec2(.3, .3);
uniform float attenuation : hint_range(0, 1) = .3;
uniform float irisAttenuation : hint_range(0, 1) = .1;
uniform vec4 highlightEllipse = vec4(.608, .6, .4, .3);
uniform vec2 irisGradientOffset = vec2(0, -.01);
uniform vec4 rim1Ellipse = vec4(0.128, -0.034, .067, .043);
uniform float rim1RotationDegrees : hint_range(-180, 180) = 10;
uniform vec4 rim2Ellipse = vec4(-0.135, 0.057, .05, .03);
uniform float rim2RotationDegrees : hint_range(-180, 180) = 10;
float ellipse(vec2 c, vec2 r, vec2 uv) {
// this is essentialy doing a circle sdf and then distorting the space by the aspect ratio
float smallDim = min(r.x, r.y);
return length((c - uv) / r) * smallDim - smallDim;
}
float ellipse4(vec4 cr, vec2 uv) {
return ellipse(cr.xy, cr.zw, uv);
}
float rect(vec2 c, vec2 r, vec2 uv) {
return length(max(abs(uv - c) - r, 0));
}
float rect4(vec4 cr, vec2 uv) {
return rect(cr.xy, cr.zw, uv);
}
// this gives a smooth transition when doing min distance to combine shapes
float smin(float a, float b, float k) {
float h = clamp(0.5 + 0.5 * (b - a) / k, 0., 1.);
return mix(b, a, h) - k * h * (1.0 - h);
}
float squaredEllipse(vec4 cr, vec2 uv, float squareAmt) {
float e = ellipse4(cr, uv);
float r = rect4(vec4(cr.xy, cr.zw * squareAmt), uv);
return smin(e, r, .3);
}
float hardEdge(float d) {
return 1.0 - smoothstep(0, 1.0 / sharpness, d);
}
mat2 rotateMat(float deg) {
float rad = radians(deg);
float s = sin(rad), c = cos(rad);
return mat2(vec2(c, -s), vec2(s, c));
}
vec2 rotate(vec2 uv, float deg, vec2 origin) {
return (uv - origin) * rotateMat(deg) + origin;
}
vec4 openDelta(float openAmt) {
return -(bottomEllipse - topEllipse) * vec4(0, 1. - openAmt, 0, 0);
}
float shape(vec2 uv, float square, float openAmt) {
float dTop = squaredEllipse(topEllipse + openDelta(openAmt), uv, square);
float dBot = squaredEllipse(bottomEllipse, uv, square);
return max(dTop, dBot);
}
float border(vec2 uv, float ds, vec4 posScale, float rotDeg, vec4 fallEl, float thick,
float square, float fall, float openAmt)
{
// arbitrary small scaling delta to make stuff look better from far away
float epsilon = ds * .00011;
vec2 borderUv = rotate((uv + posScale.xy) / posScale.zw, rotDeg, vec2(.5, .5));
float dShape = shape(borderUv, square, openAmt); // shape sdf offset by border offset
float d = bottomEllipse.y - topEllipse.y;
float dFalloff = ellipse4(fallEl + openDelta(openAmt), borderUv);
float falloffAmt = 1.0 - smoothstep(0, fall, dFalloff);
float thicc = thick * falloffAmt;
float dBorder = abs(dShape - thick * falloffAmt) - thicc; // always touching edge of shape
float borderAmt = 1.0 - hardEdge(falloffAmt);
return hardEdge(dBorder / ds + epsilon) * borderAmt;
}
float brow(vec2 uv, float dScale) {
float r = eyebrowRotationDegrees + eyebrowRotationOffsetDegrees;
return border(uv, dScale, eyebrow, r, eyebrowFalloffEllipse,
eyebrowThickness, eyebrowSquare, eyebrowFalloff, 1);
}
vec4 eye(float flipRims, vec2 uv, vec2 look, float dScale) {
float epsilon = dScale * .00011;
float shapeEdge = hardEdge(shape(uv, shapeSquare, open) / dScale);
float border1Edge = border(uv, dScale, border1, border1RotationDegrees, border1FalloffEllipse,
border1Thickness, border1Square, border1Falloff, open);
float border2Edge = border(uv, dScale, border2, border2RotationDegrees, border2FalloffEllipse,
border2Thickness, border2Square, border2Falloff, open);
vec4 irisE = irisEllipse;
irisE.xy += look;
irisE.zw *= irisScale;
float dIris = squaredEllipse(irisE, uv, irisSquare) / dScale;
float dIrisGradient = squaredEllipse(irisE, uv + irisGradientOffset, irisSquare);
float irisEdge = hardEdge(dIris + epsilon * .5);
float irisGradient = (1.0 - smoothstep(-.001, -.03, dIrisGradient)) * irisEdge;
vec4 pupilE = pupilEllipse;
pupilE.xy += irisE.xy;
pupilE.zw *= irisScale;
float dPupil = ellipse4(pupilE, uv);
float pupilEdge = 1.0 - smoothstep(0, .035, dPupil);
float highlight = ellipse4(highlightEllipse, uv);
float highlightEdge = smoothstep(0, .05, highlight);
vec4 r1 = rim1Ellipse;
vec4 r2 = rim2Ellipse;
r1.x *= flipRims;
r2.x *= flipRims;
r1.xy += irisE.xy;
r2.xy += irisE.xy;
float dRim1 = ellipse4(r1, rotate(uv, rim1RotationDegrees * flipRims, r1.xy));
float rim1Edge = hardEdge(dRim1 / dScale + epsilon);
float dRim2 = ellipse4(r2, rotate(uv, rim2RotationDegrees * flipRims, r2.xy));
float rim2Edge = hardEdge(dRim2 / dScale + epsilon);
vec4 col = vec4(0, 0, 0, 0);
col = mix(col, whiteColor, shapeEdge);
col = mix(col, color, irisEdge);
col = mix(col, color * irisAttenuation, pupilEdge);
col = mix(col, col * irisAttenuation, irisGradient);
col = mix(col, col * attenuation, highlightEdge);
col = mix(col, rimColor, max(rim1Edge, rim2Edge));
col = mix(col, border1Color, border1Edge);
col = mix(col, border2Color, border2Edge);
col.a = max(max(shapeEdge, border1Edge), border2Edge);
return col;
}
// maps uvs that fall into area (x, y, width, height) to 0.0-1.0 - this way functions don't need
// to be aware of position and scale
vec2 toLocal(vec2 uv, vec4 area) {
return (uv - area.xy) / area.zw + .5;
}
void fragment() {
// this is used to scale smoothsteps so it's equally smooth regardless of distance.
// if I didn't do this, it would become blurry close-up and aliased from afar
float dScale = abs(VERTEX.z) / scale.x;
vec2 uv = toLocal(UV, uvArea);
uv = (uv - .5) / vec2(uvArea.z / uvArea.w, 1) + .5; // adjust for uvArea aspect ratio
dScale /= uvArea.z;
vec3 look = normalize(lookAt);
look.xy *= lookSens;
look.xy = clamp(look.xy, maxLook.xy, maxLook.zw);
look.x *= -1.;
vec4 right = eye(1, toLocal(uv, vec4(.5 - dist, position, scale.x, scale.y)), look.xy, dScale);
look.x *= -1.;
vec4 left = eye(-1, toLocal(uv, vec4(.5 + dist, position, -scale.x, scale.y)), look.xy, dScale);
float browpos = eyebrowOffset + eyebrowPosition;
float browR = brow(toLocal(uv, vec4(.5 - dist, browpos, scale.x, scale.y)), dScale);
float browL = brow(toLocal(uv, vec4(.5 + dist, browpos, -scale.x, scale.y)), dScale);
vec4 col = mix(right, left, left.a);
col = mix(col, eyebrowColor, max(browR, browL));
ALPHA = col.a;
ALBEDO = col.rgb;
// adjust depth to render through hair
vec4 fragpos = FRAGCOORD * INV_PROJECTION_MATRIX;
fragpos.z += depthOffset; // view space Z
DEPTH = (fragpos * PROJECTION_MATRIX).z;
}