#version 430 core in layout(location = 0) vec3 normal; in layout(location = 1) vec2 textureCoordinates; in layout(location = 2) vec3 worldPosition; struct LightSource { vec3 position; vec3 color; }; uniform LightSource lights[3]; uniform vec3 cameraPosition; uniform vec3 ballPosition; out vec4 fragColor; const vec3 baseColor = vec3(1.0); const float ambientStrength = 0.1; const float specularStrength = 0.5; const float shininess = 32.0; // attenuation: 1 / (la + lb*d + lc*d²) const float la = 0.1; const float lb = 0.01; const float lc = 0.001; // soft shadow radii const float ballRadius = 1.0; const float hardRadius = ballRadius; const float softRadius = ballRadius * 2.0; float rand(vec2 co) { return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453); } float dither(vec2 uv) { return (rand(uv) * 2.0 - 1.0) / 256.0; } void main() { vec3 N = normalize(normal); vec3 V = normalize(cameraPosition - worldPosition); // start with ambience vec3 result = ambientStrength * baseColor; for (int i = 0; i < 3; i++) { vec3 toLight = lights[i].position - worldPosition; vec3 L = normalize(toLight); float dist = length(toLight); float attenuation = 1.0 / (la + lb * dist + lc * dist * dist); float diff = max(dot(N, L), 0.0); vec3 diffuse = diff * lights[i].color; // blinn-phong half-vector vec3 H = normalize(L + V); float spec = pow(max(dot(N, H), 0.0), shininess); vec3 specular = specularStrength * spec * lights[i].color; vec3 toBall = ballPosition - worldPosition; float projDist = dot(toBall, L); // distance along light ray float perpDist = length(toBall - projDist * L); // perpendicular distance to ray bool inShadowCone = (projDist > 0.0) && (projDist < dist); float shadow = 1.0; // skip ball's own light (index 2) to avoid self-shadowing the whole scene if (inShadowCone) { if (perpDist <= hardRadius) { shadow = 0.0; } else if (perpDist < softRadius) { shadow = (perpDist - hardRadius) / (softRadius - hardRadius); } } result += shadow * attenuation * (diffuse + specular) * baseColor; } fragColor = vec4(result + dither(textureCoordinates), 1.0); }