Refractive Procedural Eye
Configurable eye with corneal refraction. Works on any sphere mesh. You can use it out of the box as a toon eye shader or modify it to support more realistic effects.
Shader code
shader_type spatial;
/**
* Eye forward axis. For the default Godot sphere mesh, Y is optimal
*/
uniform int eye_axis : hint_enum("X", "Y", "Z") = 1;
uniform bool flip = false;
uniform float iris_size : hint_range(0.001, 1.0) = 0.35;
uniform float pupil_dilation : hint_range(0.0, 1.0) = 0.375;
uniform float cornea_bulge : hint_range(0.0, 0.999) = 0.75;
uniform float cornea_index_of_refraction : hint_range(1.0, 2.0) = 1.376;
uniform float roughness : hint_range(0.0, 1.0) = 0.05;
group_uniforms Colors;
uniform vec3 iris_color_1 : source_color = vec3(0.8, 0.4, 0.0);
uniform vec3 iris_color_2 : source_color = vec3(0.5, 0.2, 0.0);
uniform vec3 pupil_color : source_color = vec3(0.0);
uniform vec3 sclera_color : source_color = vec3(1.0);
group_uniforms;
const float cornea_blend_radius = 0.1;
const float iris_blend_radius = 0.05;
const float iris_edge_radius = 0.05;
const float iris_inset = 0.02;
const float pupil_blend_radius = 0.03;
// --- Vertex ---
// Sphere signed distance and gradient
vec4 sdg_sphere(vec3 p, vec4 sphere) {
vec3 v = p - sphere.xyz;
float l = length(v);
float d = l - sphere.w;
vec3 n = v / l;
return vec4(n, d);
}
varying vec3 axis;
varying mat4 inv_modelview_matrix;
varying float sclera_radius;
varying float iris_radius;
varying float cornea_depth;
void vertex() {
// Set eye forward axis
axis = vec3(0.0);
axis[eye_axis] = 1.0;
axis *= flip ? 1.0 : -1.0;
// Eye measures required by vertex and fragment
sclera_radius = length(VERTEX);
float iris_angle = iris_size * PI / 2.0;
iris_radius = sin(iris_angle) * sclera_radius;
cornea_depth = -cos(iris_angle) * sclera_radius;
// Cornea bulge
float cornea_radius = mix(sclera_radius, iris_radius, cornea_bulge);
vec3 cornea_center = axis * (cornea_depth + sqrt(cornea_radius * cornea_radius - iris_radius * iris_radius));
vec4 s = sdg_sphere(VERTEX, vec4(cornea_center, cornea_radius));
if (s.w < 0.0)
VERTEX += -s.xyz * s.w;
// Simple normal blending
// Godot normalizes NORMAL automatically so we don't need to do it here
float normal_blend_radius = cornea_blend_radius * iris_size * sclera_radius;
NORMAL = mix(NORMAL, s.xyz, smoothstep(0.0, -normal_blend_radius, s.w));
// Inverse MODELVIEW_MATRIX for transforming view ray into local space
inv_modelview_matrix = inverse(MODELVIEW_MATRIX);
}
// --- Fragment ---
// Ray-plane intersection
float intersect_plane(vec3 o, vec3 n, vec3 ro, vec3 rd) {
return dot(o - ro, n) / dot(rd, n);
}
// https://godotshaders.com/snippet/polar-coordinates/
vec2 polar_coordinates(vec2 uv, vec2 center, float zoom, float repeat) {
vec2 dir = uv - center;
float radius = length(dir);
float angle = atan(dir.y, dir.x) * 1.0 / TAU;
return vec2(angle * repeat, radius * zoom);
}
// Filtered cosine
// https://iquilezles.org/articles/bandlimiting/
float fcos(float x) {
float w = fwidth(x);
return cos(x) * smoothstep(TAU, 0.0, w);
}
// Basic procedural eye color
// Can be modified or replaced with texture read for different effects
vec4 eye_color(vec2 uv) {
vec4 color = vec4(1.0);
float pupil_radius = pupil_dilation;
// Iris pattern
float s = fcos(uv.x * TAU * 35.0) + fcos(uv.x * TAU * 20.0);
s = s * 0.25 + 0.5;
float r = pupil_radius - 0.075;
float remapped_y = (uv.y - r) / (1.0 - r);
float fade = smoothstep(1.0, 0.6, remapped_y);
float pattern = smoothstep(1.0, s, remapped_y) * fade;
color.rgb = mix(iris_color_1, iris_color_2, clamp(remapped_y, 0.0, 1.0));
color.rgb = mix(color.rgb * 0.25, color.rgb, pattern);
// Pupil
float pupil_mask = smoothstep(pupil_radius, pupil_radius - pupil_blend_radius, uv.y);
color.rgb = mix(color.rgb, pupil_color, pupil_mask);
return color;
}
void fragment() {
// View ray in local space
vec3 local_vertex = (inv_modelview_matrix * vec4(VERTEX, 1.0)).xyz;
vec3 ray_origin = local_vertex;
vec3 ray_direction = (inv_modelview_matrix * vec4(-VIEW, 0.0)).xyz;
// Refract view ray through cornea
vec3 local_normal = (inv_modelview_matrix * vec4(NORMAL, 0.0)).xyz;
ray_direction = refract(ray_direction, local_normal, 1.0 / cornea_index_of_refraction);
ray_direction = normalize(ray_direction);
// View ray intersection with iris plane
vec3 iris_normal = axis;
vec3 iris_origin = iris_normal * (cornea_depth + iris_inset);
float t = intersect_plane(iris_origin, iris_normal, ray_origin, ray_direction);
vec3 iris_vertex = ray_origin + ray_direction * t;
// Set iris UV depending on axis
vec2 iris_uv;
if (eye_axis == 0)
iris_uv = iris_vertex.zy;
else if (eye_axis == 1)
iris_uv = iris_vertex.xz;
else
iris_uv = iris_vertex.xy;
// Apply eye color
// UVs are adjusted to avoid filtering artifacts
// https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c#8789
iris_uv = polar_coordinates(iris_uv, vec2(0.0, 0.0), 1.0, 1.0);
iris_uv.y /= iris_radius;
vec2 fract_iris_uv = vec2(fract(iris_uv.x), iris_uv.y);
float w0 = fwidth(iris_uv.x);
float w1 = fwidth(fract_iris_uv.x);
ALBEDO = w0 < w1 ? eye_color(iris_uv).rgb : eye_color(fract_iris_uv).rgb;
// Mask eye color with sclera
float sclera_blend_radius = cornea_blend_radius * iris_size * sclera_radius;
float sclera_mask = smoothstep(cornea_depth + sclera_blend_radius, cornea_depth, (float(flip) * 2.0 - 1.0) * local_vertex[eye_axis]);
ALBEDO = mix(sclera_color, ALBEDO, sclera_mask);
// Surface properties
ROUGHNESS = roughness;
}



Probably the coolest shader on this website