├── pics ├── demo.gif ├── eyes.png └── model.png ├── pull_request_template.md ├── README.md ├── WAIVER ├── CONTRIBUTING.md ├── UNLICENSE └── AnimuEyes.shader /pics/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Francesco149/AnimuEyes/HEAD/pics/demo.gif -------------------------------------------------------------------------------- /pics/eyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Francesco149/AnimuEyes/HEAD/pics/eyes.png -------------------------------------------------------------------------------- /pics/model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Francesco149/AnimuEyes/HEAD/pics/model.png -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Unlicensing your contributions 2 | - [ ] If this is a non-trivial contribution (more than 15 lines of code changed), I will attach a contributor waiver as explained in 3 | [the contributor guidelines](../blob/master/CONTRIBUTING.md) . 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | procedurally generated anime eyes shader 2 | 3 | [video explanation](https://www.youtube.com/watch?v=AOGSxRZhU_0) 4 | 5 | ![demo gif](pics/demo.gif) 6 | [![eyes demo](pics/eyes.png)](https://nitter.net/forkexec_/status/1293630320990978049) 7 | [![eyes on model demo](pics/model.png)](https://nitter.net/forkexec_/status/1294058239848583168) 8 | 9 | 10 | # usage 11 | * put `AnimuEyes.shader` in your project, create a ShaderMaterial that loads it 12 | * assign the ShaderMaterial to your model and adjust the uvArea (ideally get the exact uv values 13 | from blender or something). for cleaner uv mapping, add an extra surface to your model that's 14 | specially UV mapped for the eyes so you don't have to adjust uvArea or worry about deformation 15 | * play with parameters, have fun 16 | * if you already have a bunch of shaders stacked together, try to make the eyes the last pass 17 | -------------------------------------------------------------------------------- /WAIVER: -------------------------------------------------------------------------------- 1 | # Copyright waiver for 2 | 3 | I dedicate any and all copyright interest in this software to the 4 | public domain. I make this dedication for the benefit of the public at 5 | large and to the detriment of my heirs and successors. I intend this 6 | dedication to be an overt act of relinquishment in perpetuity of all 7 | present and future rights to this software under copyright law. 8 | 9 | To the best of my knowledge and belief, my contributions are either 10 | originally authored by me or are derived from prior works which I have 11 | verified are also in the public domain and are not subject to claims 12 | of copyright by other parties. 13 | 14 | To the best of my knowledge and belief, no individual, business, 15 | organization, government, or other entity has any copyright interest 16 | in my contributions, and I affirm that I will not make contributions 17 | that are otherwise encumbered. 18 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | # Unlicensing your contributions 3 | 4 | This project is public domain. Any non-trivial contribution (more than 15 lines of code changed) 5 | should attach the disclaimer in the WAIVER file to their commit messages so that the intent to 6 | release the code into the public domain is clear. 7 | 8 | Make sure the waiver is in the COMMIT message, not just the pull request, so that the intent is 9 | preserved even if the project is moved to a different git platform. 10 | 11 | An even better way to sign your waiver is to `gpg --no-version --armor --sign WAIVER` and append 12 | that to the AUTHORS file (create it if it doesn't exist) 13 | 14 | If you're a company or your employer normally owns the code you write, you should attach a copyright 15 | disclaimer signed by your company that releases any copyright interest, such as 16 | 17 | ``` 18 | Some Company Inc. hereby disclaims all copyright interest in 19 | 20 | signature of Bob Smith 5 June 2020 21 | Bob Smith, President of Some Company 22 | ``` 23 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /AnimuEyes.shader: -------------------------------------------------------------------------------- 1 | // AnimuEyes - procedurally generated anime eyes shader 2 | // add an extra shader pass to your character model and set uvArea to the uv range where you want 3 | // the eyes to appear. xy are the middle point of the rectangle, zw are width/height. 4 | // _if you have other transparent materials, make sure to adjust the render priority_ 5 | // adjust the depthOffset if you don't want them to render through hair or if rendering through 6 | // hair is wonky 7 | // godot version tested: v4.3.stable.nixpkgs [77dcf97d8] 8 | // license: public domain (UNLICENSE) 9 | 10 | shader_type spatial; 11 | render_mode unshaded; 12 | 13 | // these are the uniforms that should be dynamically changed for facial expressions and such 14 | uniform vec3 lookAt = vec3(0, 0, 100000); // target point relative to eyes 15 | uniform vec4 maxLook = vec4(-.4, -.2, .4, .2); // eye movement range 16 | uniform vec2 lookSens = vec2(.2, .2); // adjust until eyes match up with the look target 17 | uniform float irisScale : hint_range(.1, 1.5) = 1; 18 | uniform float eyebrowOffset = 0; 19 | uniform float eyebrowRotationOffsetDegrees : hint_range(-180, 180) = 0; 20 | uniform float open : hint_range(0, 1) = 1; // might cause some artifacts, mainly used for blinking 21 | 22 | // everything else is only recommended to be used to define the base eye aesthetic 23 | uniform vec4 uvArea = vec4(.5, .5, 1, 1); 24 | uniform float position = .2; 25 | uniform float dist = .2; 26 | uniform float depthOffset = .01; 27 | 28 | uniform vec4 irisColor : source_color = vec4(1, .3, 0, 1); 29 | uniform vec4 whiteColor : source_color = vec4(1, 1, 1, 1); 30 | uniform vec4 rimColor : source_color = vec4(1, 1, 1, 1); 31 | uniform vec4 eyebrowColor : source_color = vec4(0, 0, 0, 1); 32 | uniform vec4 border1Color : source_color = vec4(0, 0, 0, 1); 33 | uniform vec4 border2Color : source_color = vec4(0, 0, 0, 1); 34 | uniform vec4 leftIrisColor : source_color = vec4(1, 1, 1, 1); 35 | uniform vec4 rightIrisColor : source_color = vec4(1, 1, 1, 1); 36 | uniform vec4 leftEyeColor : source_color = vec4(1, 1, 1, 1); 37 | uniform vec4 rightEyeColor : source_color = vec4(1, 1, 1, 1); 38 | 39 | uniform float shapeSquare : hint_range(0, 1) = .554; 40 | uniform float irisSquare : hint_range(0, 1) = .219; 41 | uniform float border1Square : hint_range(0, 1) = .5; 42 | uniform float border2Square : hint_range(0, 1) = 1; 43 | uniform float eyebrowSquare : hint_range(0, 1) = .5; 44 | 45 | uniform vec4 topEllipse = vec4(.444, .598, .5, .4); 46 | uniform vec4 bottomEllipse = vec4(.49, .297, .3, .33); 47 | uniform vec4 irisEllipse = vec4(.53, .35, .17, .236); 48 | uniform vec4 pupilEllipse = vec4(0, .02, .08, .1); 49 | 50 | uniform float eyebrowPosition = 0; 51 | uniform vec4 eyebrow = vec4(.037, -.369, 1.1, 1.1); 52 | uniform vec4 eyebrowFalloffEllipse = vec4(.5, -.315, .028, .039); 53 | uniform float eyebrowFalloff : hint_range(0, 1) = .5; 54 | uniform float eyebrowRotationDegrees : hint_range(-180, 180) = -10; 55 | uniform float eyebrowThickness : hint_range(0, .1) = .025; 56 | 57 | uniform vec4 border1 = vec4(.025, .016, 1.1, 1.1); 58 | uniform vec4 border1FalloffEllipse = vec4(.222, -.03, .3, .2); 59 | uniform float border1Falloff : hint_range(0, 1) = .3; 60 | uniform float border1RotationDegrees : hint_range(-180, 180) = -3; 61 | uniform float border1Thickness : hint_range(0, .1) = .025; 62 | 63 | uniform vec4 border2 = vec4(-.043, .057, .916, 1); 64 | uniform vec4 border2FalloffEllipse = vec4(.5, .861, .03, .049); 65 | uniform float border2Falloff : hint_range(0, 1) = .2; 66 | uniform float border2RotationDegrees : hint_range(-180, 180) = 0; 67 | uniform float border2Thickness : hint_range(0, .1) = .008; 68 | 69 | uniform float sharpness : hint_range(100, 1000) = 200; 70 | uniform vec2 scale = vec2(.3, .3); 71 | uniform float attenuation : hint_range(0, 1) = .3; 72 | uniform float irisAttenuation : hint_range(0, 1) = .1; 73 | uniform vec4 highlightEllipse = vec4(.608, .6, .4, .3); 74 | uniform vec2 irisGradientOffset = vec2(0, -.01); 75 | uniform vec4 rim1Ellipse = vec4(0.128, -0.034, .067, .043); 76 | uniform float rim1RotationDegrees : hint_range(-180, 180) = 10; 77 | uniform vec4 rim2Ellipse = vec4(-0.135, 0.057, .05, .03); 78 | uniform float rim2RotationDegrees : hint_range(-180, 180) = 10; 79 | 80 | float ellipse(vec2 c, vec2 r, vec2 uv) { 81 | // this is essentialy doing a circle sdf and then distorting the space by the aspect ratio 82 | float smallDim = min(r.x, r.y); 83 | return length((c - uv) / r) * smallDim - smallDim; 84 | } 85 | 86 | float ellipse4(vec4 cr, vec2 uv) { 87 | return ellipse(cr.xy, cr.zw, uv); 88 | } 89 | 90 | float rect(vec2 c, vec2 r, vec2 uv) { 91 | return length(max(abs(uv - c) - r, 0)); 92 | } 93 | 94 | float rect4(vec4 cr, vec2 uv) { 95 | return rect(cr.xy, cr.zw, uv); 96 | } 97 | 98 | // this gives a smooth transition when doing min distance to combine shapes 99 | float smin(float a, float b, float k) { 100 | float h = clamp(0.5 + 0.5 * (b - a) / k, 0., 1.); 101 | return mix(b, a, h) - k * h * (1.0 - h); 102 | } 103 | 104 | float squaredEllipse(vec4 cr, vec2 uv, float squareAmt) { 105 | float e = ellipse4(cr, uv); 106 | float r = rect4(vec4(cr.xy, cr.zw * squareAmt), uv); 107 | return smin(e, r, .3); 108 | } 109 | 110 | float hardEdge(float d) { 111 | return 1.0 - smoothstep(0, 1.0 / sharpness, d); 112 | } 113 | 114 | mat2 rotateMat(float deg) { 115 | float rad = radians(deg); 116 | float s = sin(rad), c = cos(rad); 117 | return mat2(vec2(c, -s), vec2(s, c)); 118 | } 119 | 120 | vec2 rotate(vec2 uv, float deg, vec2 origin) { 121 | return (uv - origin) * rotateMat(deg) + origin; 122 | } 123 | 124 | vec4 openDelta(float openAmt) { 125 | return -(bottomEllipse - topEllipse) * vec4(0, 1. - openAmt, 0, 0); 126 | } 127 | 128 | float shape(vec2 uv, float square, float openAmt) { 129 | float dTop = squaredEllipse(topEllipse + openDelta(openAmt), uv, square); 130 | float dBot = squaredEllipse(bottomEllipse, uv, square); 131 | return max(dTop, dBot); 132 | } 133 | 134 | float border(vec2 uv, float ds, vec4 posScale, float rotDeg, vec4 fallEl, float thick, 135 | float square, float fall, float openAmt) 136 | { 137 | // arbitrary small scaling delta to make stuff look better from far away 138 | float epsilon = ds * .00011; 139 | vec2 borderUv = rotate((uv + posScale.xy) / posScale.zw, rotDeg, vec2(.5, .5)); 140 | float dShape = shape(borderUv, square, openAmt); // shape sdf offset by border offset 141 | float d = bottomEllipse.y - topEllipse.y; 142 | float dFalloff = ellipse4(fallEl + openDelta(openAmt), borderUv); 143 | float falloffAmt = 1.0 - smoothstep(0, fall, dFalloff); 144 | float thicc = thick * falloffAmt; 145 | float dBorder = abs(dShape - thick * falloffAmt) - thicc; // always touching edge of shape 146 | float borderAmt = 1.0 - hardEdge(falloffAmt); 147 | return hardEdge(dBorder / ds + epsilon) * borderAmt; 148 | } 149 | 150 | float brow(vec2 uv, float dScale) { 151 | float r = eyebrowRotationDegrees + eyebrowRotationOffsetDegrees; 152 | return border(uv, dScale, eyebrow, r, eyebrowFalloffEllipse, 153 | eyebrowThickness, eyebrowSquare, eyebrowFalloff, 1); 154 | } 155 | 156 | vec4 eye(float flipRims, vec2 uv, vec2 look, float dScale, vec4 eyeIrisColor) { 157 | float epsilon = dScale * .00011; 158 | float shapeEdge = hardEdge(shape(uv, shapeSquare, open) / dScale); 159 | float border1Edge = border(uv, dScale, border1, border1RotationDegrees, border1FalloffEllipse, 160 | border1Thickness, border1Square, border1Falloff, open); 161 | float border2Edge = border(uv, dScale, border2, border2RotationDegrees, border2FalloffEllipse, 162 | border2Thickness, border2Square, border2Falloff, open); 163 | vec4 irisE = irisEllipse; 164 | irisE.xy += look; 165 | irisE.zw *= irisScale; 166 | float dIris = squaredEllipse(irisE, uv, irisSquare) / dScale; 167 | float dIrisGradient = squaredEllipse(irisE, uv + irisGradientOffset, irisSquare); 168 | float irisEdge = hardEdge(dIris + epsilon * .5); 169 | float irisGradient = (1.0 - smoothstep(-.001, -.03, dIrisGradient)) * irisEdge; 170 | vec4 pupilE = pupilEllipse; 171 | pupilE.xy += irisE.xy; 172 | pupilE.zw *= irisScale; 173 | float dPupil = ellipse4(pupilE, uv); 174 | float pupilEdge = 1.0 - smoothstep(0, .035, dPupil); 175 | float highlight = ellipse4(highlightEllipse, uv); 176 | float highlightEdge = smoothstep(0, .05, highlight); 177 | vec4 r1 = rim1Ellipse; 178 | vec4 r2 = rim2Ellipse; 179 | r1.x *= flipRims; 180 | r2.x *= flipRims; 181 | r1.xy += irisE.xy; 182 | r2.xy += irisE.xy; 183 | float dRim1 = ellipse4(r1, rotate(uv, rim1RotationDegrees * flipRims, r1.xy)); 184 | float rim1Edge = hardEdge(dRim1 / dScale + epsilon); 185 | float dRim2 = ellipse4(r2, rotate(uv, rim2RotationDegrees * flipRims, r2.xy)); 186 | float rim2Edge = hardEdge(dRim2 / dScale + epsilon); 187 | vec4 col = vec4(0, 0, 0, 0); 188 | col = mix(col, whiteColor, shapeEdge); 189 | col = mix(col, eyeIrisColor, irisEdge); 190 | col = mix(col, eyeIrisColor * irisAttenuation, pupilEdge); 191 | col = mix(col, col * irisAttenuation, irisGradient); 192 | col = mix(col, col * attenuation, highlightEdge); 193 | col = mix(col, rimColor, max(rim1Edge, rim2Edge)); 194 | col = mix(col, border1Color, border1Edge); 195 | col = mix(col, border2Color, border2Edge); 196 | col.a = max(max(shapeEdge, border1Edge), border2Edge); 197 | return col; 198 | } 199 | 200 | // maps uvs that fall into area (x, y, width, height) to 0.0-1.0 - this way functions don't need 201 | // to be aware of position and scale 202 | vec2 toLocal(vec2 uv, vec4 area) { 203 | return (uv - area.xy) / area.zw + .5; 204 | } 205 | 206 | void fragment() { 207 | // this is used to scale smoothsteps so it's equally smooth regardless of distance. 208 | // if I didn't do this, it would become blurry close-up and aliased from afar 209 | float dScale = abs(VERTEX.z) / scale.x; 210 | vec2 uv = toLocal(UV, uvArea); 211 | uv = (uv - .5) / vec2(uvArea.z / uvArea.w, 1) + .5; // adjust for uvArea aspect ratio 212 | dScale /= uvArea.z; 213 | vec3 look = normalize(lookAt); 214 | look.xy *= lookSens; 215 | look.xy = clamp(look.xy, maxLook.xy, maxLook.zw); 216 | look.x *= -1.; 217 | vec4 right = eye(1, toLocal(uv, vec4(.5 - dist, position, scale.x, scale.y)), look.xy, dScale, 218 | irisColor * rightIrisColor) * rightEyeColor; 219 | look.x *= -1.; 220 | vec4 left = eye(-1, toLocal(uv, vec4(.5 + dist, position, -scale.x, scale.y)), look.xy, dScale, 221 | irisColor * leftIrisColor) * leftEyeColor; 222 | float browpos = eyebrowOffset + eyebrowPosition; 223 | float browR = brow(toLocal(uv, vec4(.5 - dist, browpos, scale.x, scale.y)), dScale); 224 | float browL = brow(toLocal(uv, vec4(.5 + dist, browpos, -scale.x, scale.y)), dScale); 225 | vec4 col = mix(right, left, left.a); 226 | col = mix(col, eyebrowColor, max(browR, browL)); 227 | ALPHA = col.a; 228 | ALBEDO = col.rgb; 229 | // adjust depth to render through hair 230 | vec4 fragpos = FRAGCOORD * INV_PROJECTION_MATRIX; 231 | fragpos.z += depthOffset; // view space Z 232 | DEPTH = (fragpos * PROJECTION_MATRIX).z; 233 | } 234 | --------------------------------------------------------------------------------