#version 300 es precision highp float; precision highp int; out vec4 outColor; uniform vec2 uResolution; uniform vec4 uSpheres[70]; uniform int uMaterials[70]; uniform vec4 uGroupBounds[5]; uniform vec3 uCameraEye; uniform vec3 uCameraForward; uniform vec3 uCameraRight; uniform vec3 uCameraUp; uniform vec2 uTanHalf; uniform int uReflectionBounces; uniform int uAuthentic; // Schaduwmodel en constanten volgen Eric Grahams raytracer (rt1.c, 1987) en // zijn gekalibreerd op de 24 originele frames uit 1986. Alle kleuren staan in // weergaveruimte: het origineel kende geen gammacorrectie. const float EPSILON = 0.003; const float FAR_DISTANCE = 1.0e6; const int FLOOR_MATERIAL = 5; const vec3 MIRROR_TINT = vec3(0.9, 0.9, 0.9); const vec3 TORSO_COLOR = vec3(1.0, 0.1, 0.1); const vec3 SKIN_COLOR = vec3(1.0, 0.7, 0.7); const vec3 EYE_COLOR = vec3(0.1, 0.1, 1.0); const vec3 HAIR_COLOR = vec3(0.2, 0.1, 0.1); const vec3 FLOOR_YELLOW = vec3(1.5, 1.5, 0.0); const vec3 FLOOR_GREEN = vec3(0.0, 1.5, 0.0); const vec3 SKY_HORIZON = vec3(0.7, 0.7, 1.0); const vec3 SKY_ZENITH = vec3(0.1, 0.1, 1.0); const vec3 ILLUM_AMBIENT = vec3(0.25, 0.25, 0.25); // Lamp gefit op de schaduwen en het helderheidsverloop van de vloer. const vec3 LAMP_POSITION = vec3(-53.0, 93.0, -139.0); const float LAMP_RADIUS = 15.0; const float EXPOSURE = 25700.0; const float GLINT_COSINE_SQUARED = 0.95; struct Hit { float distance; int material; vec3 position; vec3 normal; }; float sphereDistance(vec3 rayOrigin, vec3 rayDirection, vec4 sphere) { vec3 relativeOrigin = rayOrigin - sphere.xyz; float projected = dot(relativeOrigin, rayDirection); float discriminant = projected * projected - dot(relativeOrigin, relativeOrigin) + sphere.w * sphere.w; if (discriminant < 0.0) { return FAR_DISTANCE; } float root = sqrt(discriminant); float nearDistance = -projected - root; if (nearDistance > EPSILON) { return nearDistance; } float farDistance = -projected + root; return farDistance > EPSILON ? farDistance : FAR_DISTANCE; } bool boundMayIntersect( vec3 rayOrigin, vec3 rayDirection, vec4 bound, float maximumDistance ) { vec3 relativeOrigin = rayOrigin - bound.xyz; float projected = dot(relativeOrigin, rayDirection); float discriminant = projected * projected - dot(relativeOrigin, relativeOrigin) + bound.w * bound.w; if (discriminant < 0.0) { return false; } float root = sqrt(discriminant); return -projected + root > EPSILON && -projected - root < maximumDistance; } void testSphere( vec3 rayOrigin, vec3 rayDirection, int sphereIndex, inout Hit hit ) { float distance = sphereDistance( rayOrigin, rayDirection, uSpheres[sphereIndex] ); if (distance < hit.distance) { vec3 position = rayOrigin + rayDirection * distance; hit.distance = distance; hit.material = uMaterials[sphereIndex]; hit.position = position; hit.normal = normalize(position - uSpheres[sphereIndex].xyz); } } Hit traceScene(vec3 rayOrigin, vec3 rayDirection) { Hit hit; hit.distance = FAR_DISTANCE; hit.material = -1; hit.position = vec3(0.0); hit.normal = vec3(0.0); for (int sphereIndex = 0; sphereIndex < 3; sphereIndex += 1) { testSphere(rayOrigin, rayDirection, sphereIndex, hit); } for (int groupIndex = 0; groupIndex < 5; groupIndex += 1) { if (boundMayIntersect( rayOrigin, rayDirection, uGroupBounds[groupIndex], hit.distance )) { int start = groupIndex == 0 ? 3 : 14 * groupIndex; int count = groupIndex == 0 ? 11 : 14; for (int partIndex = 0; partIndex < 14; partIndex += 1) { if (partIndex >= count) { break; } testSphere(rayOrigin, rayDirection, start + partIndex, hit); } } } if (rayDirection.y < -1.0e-6) { float floorDistance = -rayOrigin.y / rayDirection.y; if (floorDistance > EPSILON && floorDistance < hit.distance) { hit.distance = floorDistance; hit.material = FLOOR_MATERIAL; hit.position = rayOrigin + rayDirection * floorDistance; hit.normal = vec3(0.0, 1.0, 0.0); } } return hit; } bool sphereOccludes( vec3 rayOrigin, vec3 rayDirection, int sphereIndex, float maximumDistance ) { return sphereDistance( rayOrigin, rayDirection, uSpheres[sphereIndex] ) < maximumDistance; } bool isOccluded(vec3 rayOrigin, vec3 rayDirection, float maximumDistance) { for (int sphereIndex = 0; sphereIndex < 3; sphereIndex += 1) { if (sphereOccludes(rayOrigin, rayDirection, sphereIndex, maximumDistance)) { return true; } } for (int groupIndex = 0; groupIndex < 5; groupIndex += 1) { if (boundMayIntersect( rayOrigin, rayDirection, uGroupBounds[groupIndex], maximumDistance )) { int start = groupIndex == 0 ? 3 : 14 * groupIndex; int count = groupIndex == 0 ? 11 : 14; for (int partIndex = 0; partIndex < 14; partIndex += 1) { if (partIndex >= count) { break; } if (sphereOccludes( rayOrigin, rayDirection, start + partIndex, maximumDistance )) { return true; } } } } return false; } // Lucht: menging op sin²(elevatie), zoals in rt1.c (skybrite). vec3 skyColor(vec3 rayOrigin, vec3 rayDirection) { vec3 toLamp = LAMP_POSITION - rayOrigin; float lampDistance = length(toLamp); float lampSine = LAMP_RADIUS / lampDistance; float lampCosine = sqrt(1.0 - lampSine * lampSine); if (dot(rayDirection, toLamp / lampDistance) > lampCosine) { return vec3(1.0); } float sine = clamp(rayDirection.y, 0.0, 1.0); return mix(SKY_HORIZON, SKY_ZENITH, sine * sine); } // Exacte port van Grahams gingham(): tegels van 3 eenheden, gespiegeld om // beide assen; pariteit 0 is geel (gekalibreerd aan frame 1). vec3 checkerColor(vec3 position) { float x = position.x; float y = position.z; int kx = 0; int ky = 0; if (x < 0.0) { x = -x; kx = 1; } if (y < 0.0) { y = -y; ky = 1; } int parity = ((int(x) + kx) / 3 + (int(y) + ky) / 3) % 2; return parity == 0 ? FLOOR_YELLOW : FLOOR_GREEN; } vec3 materialColor(Hit hit) { if (hit.material == 1) { return TORSO_COLOR; } if (hit.material == 2) { return SKIN_COLOR; } if (hit.material == 3) { return EYE_COLOR; } if (hit.material == 4) { return HAIR_COLOR; } if (hit.material == FLOOR_MATERIAL) { return checkerColor(hit.position); } return vec3(0.8); } vec3 shade(Hit hit, vec3 rayDirection) { vec3 toLamp = LAMP_POSITION - hit.position; float lampDistanceSquared = dot(toLamp, toLamp); float lampDistance = sqrt(lampDistanceSquared); vec3 lampDirection = toLamp / lampDistance; vec3 shadowOrigin = hit.position + hit.normal * (EPSILON * 2.0); float visibility = isOccluded( shadowOrigin, lampDirection, lampDistance - EPSILON ) ? 0.0 : 1.0; // Grahams wrap-ambient: (n·ẑ + 1.5) · 0.4, bereik 0,4 … 1,0. float wrap = (hit.normal.y + 1.5) * 0.4; float lamp = EXPOSURE * max(dot(hit.normal, lampDirection), 0.0) / lampDistanceSquared; vec3 color = materialColor(hit) * (wrap * ILLUM_AMBIENT + visibility * lamp); // BRIGHT-oppervlakken: binair zuiver wit glimpunt boven cos² > 0,95. if (hit.material != FLOOR_MATERIAL && visibility > 0.0) { vec3 reflected = reflect(rayDirection, hit.normal); float glint = dot(lampDirection, reflected); if (glint > 0.0 && glint * glint > GLINT_COSINE_SQUARED) { color = vec3(1.0); } } return color; } void main() { vec2 screenPosition = (gl_FragCoord.xy * 2.0 - uResolution) / uResolution; vec2 tanHalf = uTanHalf; vec3 rayOrigin = uCameraEye; vec3 rayDirection = normalize( uCameraForward + screenPosition.x * uCameraRight * tanHalf.x + screenPosition.y * uCameraUp * tanHalf.y ); vec3 radiance = vec3(0.0); vec3 throughput = vec3(1.0); for (int bounce = 0; bounce < 3; bounce += 1) { Hit hit = traceScene(rayOrigin, rayDirection); if (hit.material < 0) { radiance += throughput * skyColor(rayOrigin, rayDirection); break; } if (hit.material == 0) { // Spiegelbal: recursieve reflectie × eigen kleur ⟨.9,.9,.9⟩ (rt1.c). throughput *= MIRROR_TINT; vec3 reflected = reflect(rayDirection, hit.normal); if (bounce < uReflectionBounces) { rayOrigin = hit.position + hit.normal * (EPSILON * 2.0); rayDirection = reflected; continue; } radiance += throughput * skyColor(hit.position, reflected); break; } radiance += throughput * shade(hit, rayDirection); break; } vec3 color = clamp(radiance, 0.0, 1.0); if (uAuthentic == 1) { // HAM-kwantisatie van rt2.c: pix = 16·brite + 0,5, 4 bits per kanaal. color = min(floor(color * 16.0 + 0.5), 15.0) / 15.0; } outColor = vec4(color, 1.0); }