I took the Phong shaders balls and fiddled...again
Log in to post a comment.
#version 300 es
precision highp float;
uniform float iTime;
uniform vec2 iResolution;
out vec4 fragColor;
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// PARAMETERS & CONSTANTS
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
const int MAX_MARCHING_STEPS = 100;
const float MIN_DIST = 0.001;
const float MAX_DIST = 100.0;
// Object IDs: 0–2 for spheres, 3 for floor.
struct SceneResult {
float dist;
int objectId; // 0,1,2 for spheres; 3 for floor.
int sphereIndex; // Which sphere (if any) was hit.
};
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// SDF FUNCTIONS
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// A deformed sphere SDF (to simulate squash/stretch when bouncing).
// 'deform' holds the horizontal (x,z) scale in .x and vertical in .y.
float sdSphereDeformed(vec3 p, float r, vec2 deform) {
vec3 q = vec3(p.x / deform.x, p.y / deform.y, p.z / deform.x);
return length(q) - r;
}
// SDF for a horizontal plane (the floor) at y = -1.0.
float sdPlane(vec3 p, float h) {
return p.y - h;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// ANIMATED RIPPLE FUNCTION
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// This function computes a ripple that emanates from 'center' and
// depends on 'lt', the time (in seconds) elapsed since impact.
float rippleFromImpact(vec2 pos, vec2 center, float lt) {
float dist = length(pos - center);
float rippleSpeed = 1.5; // How fast the ripple front moves outward.
float frequency = 10.0; // Frequency of the ripple oscillation.
float damping = 2.0; // Controls how quickly the ripple decays.
float wave = sin(frequency * (dist - rippleSpeed * lt));
// Envelope: maximum at impact (lt=0) and decays over time.
float envelope = exp(-damping * lt) * (1.0 - smoothstep(0.0, 0.2, lt));
return 0.1 * wave * envelope / (1.0 + 10.0 * dist);
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// SCENE MAP: Three bouncing spheres and a rippled floor with animated ripples
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
SceneResult map(in vec3 p) {
SceneResult res;
res.dist = MAX_DIST;
res.objectId = -1;
res.sphereIndex = -1;
// Parameters for the spheres.
float sphereRadius = 0.3;
float orbitRadius = 0.8;
// Process each of the 3 spheres.
for (int i = 0; i < 3; i++) {
// Each sphere gets its own phase offset.
float phase = 6.2831853 * float(i) / 3.0;
float t_i = iTime + phase;
// Bounce using the absolute value makes the motion symmetric.
float bounce = abs(sin(t_i));
// Compute the sphere’s center. They orbit in the xz–plane and bounce in y.
vec3 center;
center.x = orbitRadius * cos(iTime + phase);
center.z = orbitRadius * sin(iTime + phase);
center.y = -1.0 + sphereRadius + bounce;
// Simulate squash/stretch on impact.
float impact = 1.0 - smoothstep(0.0, 0.2, bounce);
float deformX = mix(1.0, 1.2, impact); // horizontal expansion
float deformY = mix(1.0, 0.8, impact); // vertical squash
float d = sdSphereDeformed(p - center, sphereRadius, vec2(deformX, deformY));
if (d < res.dist) {
res.dist = d;
res.objectId = i; // sphere id (0,1,2)
res.sphereIndex = i;
}
}
// Floor: basic SDF for a plane at y = -1.0.
float dPlane = sdPlane(p, -1.0);
// Add animated ripples from each sphere’s impact.
// We simulate a ripple for a short duration after each impact.
float period = 3.14; // Approximate bounce period (in seconds).
float T_ripple = 0.5; // Duration (in seconds) for which a ripple is visible.
for (int i = 0; i < 3; i++) {
float phase = 6.2831853 * float(i) / 3.0;
float t_i = iTime + phase;
// Compute time since impact for this sphere.
float lt = mod(t_i, period);
if (lt < T_ripple) {
vec2 sphereXZ;
sphereXZ.x = orbitRadius * cos(iTime + phase);
sphereXZ.y = orbitRadius * sin(iTime + phase);
dPlane += rippleFromImpact(p.xz, sphereXZ, lt);
}
}
// Return whichever is closer: a sphere or the floor.
if (dPlane < res.dist) {
res.dist = dPlane;
res.objectId = 3; // floor id
res.sphereIndex = -1;
}
return res;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// ESTIMATING NORMALS
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 getNormal(vec3 p) {
float h = 0.0001;
return normalize(vec3(
map(p + vec3(h, 0, 0)).dist - map(p - vec3(h, 0, 0)).dist,
map(p + vec3(0, h, 0)).dist - map(p - vec3(0, h, 0)).dist,
map(p + vec3(0, 0, h)).dist - map(p - vec3(0, 0, h)).dist
));
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// RAY MARCHING
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 rayMarch(vec3 ro, vec3 rd, out int hitObject, out int sphereIndex) {
float t = 0.0;
hitObject = -1;
sphereIndex = -1;
for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
vec3 p = ro + rd * t;
SceneResult res = map(p);
if (res.dist < MIN_DIST) {
hitObject = res.objectId;
sphereIndex = res.sphereIndex;
return p;
}
if (t > MAX_DIST) break;
t += res.dist;
}
return ro + rd * t;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// LIGHTING & FRESNEL
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 getLightDir(vec3 p) {
// A static overhead light
vec3 lightPos = vec3(0.0, 15.0, 0.0);
return normalize(lightPos - p);
}
vec3 phongLighting(vec3 p, vec3 normal, vec3 viewDir, vec3 baseColor) {
vec3 lightDir = getLightDir(p);
float diff = max(dot(normal, lightDir), 0.0);
vec3 reflDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflDir), 0.0), 32.0);
return baseColor * diff + vec3(1.0) * spec;
}
float fresnel(vec3 I, vec3 N, float ior) {
float cosi = clamp(dot(I, N), -1.0, 1.0);
float etai = 1.0, etat = ior;
if (cosi > 0.0) { float tmp = etai; etai = etat; etat = tmp; }
float sint = etai / etat * sqrt(max(0.0, 1.0 - cosi * cosi));
if (sint >= 1.0) {
return 1.0;
} else {
float cost = sqrt(max(0.0, 1.0 - sint * sint));
cosi = abs(cosi);
float Rs = ((etat * cosi) - (etai * cost)) / ((etat * cosi) + (etai * cost));
float Rp = ((etai * cosi) - (etat * cost)) / ((etai * cosi) + (etat * cost));
return (Rs * Rs + Rp * Rp) / 2.0;
}
}
// Simple gradient sky: blue at the top fading to white at horizon
vec3 skyColor(vec3 rd) {
float t = clamp(rd.y * 0.5 + 0.5, 0.0, 1.0);
vec3 horizon = vec3(0.8, 0.9, 1.0);
vec3 zenith = vec3(0.2, 0.5, 0.9);
return mix(horizon, zenith, t);
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// RAY-TRACING: REFLECTIONS & REFRACTIONS
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
vec3 trace(vec3 ro, vec3 rd) {
vec3 color = vec3(0.0);
vec3 attenuation = vec3(1.0);
// Allow up to 2 bounces.
for (int bounce = 0; bounce < 2; bounce++) {
int hitObject, sphereIndex;
vec3 hitPos = rayMarch(ro, rd, hitObject, sphereIndex);
// If nothing is hit, return a dark background.
if (hitObject == -1) {
// Instead of a flat dark background, sample the sky
color += attenuation * skyColor(rd);
break;
}
vec3 normal = getNormal(hitPos);
vec3 viewDir = normalize(-rd);
if (hitObject == 3) {
// Hit the floor: apply a chequerboard pattern.
vec2 uvFloor = hitPos.xz * 2.0;
float checker = mod(floor(uvFloor.x) + floor(uvFloor.y), 2.0);
vec3 colorA = vec3(0.1, 0.1, 0.1);
vec3 colorB = vec3(0.8, 0.8, 0.8);
vec3 base = mix(colorA, colorB, checker);
//vec3 lit = phongLighting(hitPos, normal, viewDir, base);
vec3 ambient = 0.2 * skyColor(normal); // light coming from the sky
vec3 lit = ambient + phongLighting(hitPos, normal, viewDir, base);
color += attenuation * lit;
rd = reflect(rd, normal);
ro = hitPos + normal * MIN_DIST * 2.0;
attenuation *= 0.8;
} else {
// MIRRORED SPHERE
rd = reflect(rd, normal);
ro = hitPos + normal * MIN_DIST * 2.0;
// no color loss for a perfect mirror
attenuation *= 1.0;
// let it bounce again
continue;
}
}
return color;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// MAIN – Set up camera ray and output final colour.
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
void main() {
// Normalized pixel coordinates (with y scaled by iResolution.y).
vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;
// A simple camera: positioned at (0,0,3) looking toward -z.
vec3 ro = vec3(0.0, 0.0, 3.0);
vec3 rd = normalize(vec3(uv, -1.5));
vec3 col = trace(ro, rd);
fragColor = vec4(col, 1.0);
}