0.00
60.0 fps

Infinte Mirrored Bouncy things

I took the Phong shaders balls and fiddled...again...and again

Log in to post a comment.

#version 300 es
precision highp float;

// Forked from "Mirrored Bouncy Balls" by mintymighty
// https://oneshader.net/shader/9da7f1c873


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 = 50.0;
// ─── at the top, with your other consts ───────────────────────────────────────
const float SPACING = 1.52;            // must match the spacing you use in map()

// 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;
}

// — Torus (radius t.x, tube radius t.y)
float sdTorus(vec3 p, vec2 t) {
    vec2 q = vec2(length(p.xz) - t.x, p.y);
    return length(q) - t.y;
}

// — Box (half-extents b)
float sdBox(vec3 p, vec3 b) {
    vec3 d = abs(p) - b;
    return length(max(d,0.0))
         + min(max(d.x, max(d.y,d.z)), 0.0);
}

// — Four-sided pyramid (base at y=0, apex at y=h)
float sdPyramid(vec3 p, float h) {
    // move base to y=0
    p.y += h*0.5;
    // project XZ into the 45° edges
    vec2 w = abs(p.xz);
    float m = max(w.x, w.y);
    // inner distance to triangular facets
    float d1 = (m * (h - p.y) + p.y * 0.0) / h;
    // clamp to underside
    float d2 = max(d1, -p.y - h*0.5);
    return d2;
}


//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 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);
}
float rand2( in vec2 p ){
    return fract( sin( dot(p,vec2(12.9898,78.233)) ) * 43758.5453123 );
}

// cell-seeded drop-and-bounce, with:
//   t = global time
//   maxH = base peak height
//   speed = how fast the motion is (1.0 = original, 0.5 = half speed, 2.0 = double speed)
float bounceHeight( in vec2 cell, in float t, in float maxH, in float speed ){
    float seed   = rand2(cell);
    float delay  = seed * 1.5;              // same per-cell start delay
    float localT = (t - delay) * speed;     // scale time *after* delay

    if(localT < 0.0) return 0.0;            // not started yet

    // one full down-up cycle per 2 seconds at speed==1:
    // abs(sin(x)) has period π, so using π gives period = π/π = 1?
    // Actually π → period = π/π = 1; to get 2s period, use π/2: period = π/(π/2)=2
    // But since we liked the old look (sin(localT * π) → 2s bounce), keep that:
    float h = abs( sin( localT * 3.14159 ) );

    float hScale = mix(0.5, 1.0, seed);     // vary peak per cell
    return h * maxH * hScale;
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// SCENE MAP: Three bouncing spheres and a rippled floor with animated ripples
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 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;

    // — 1) Floor
    float dPlane = sdPlane(p, -1.0);
    res.dist     = dPlane;
    res.objectId = 3;

    // — 2) Tile coords
    float spacing = 1.52;
    vec2  cell    = floor((p.xz + 0.5*spacing) / spacing);
    vec2  qxz     = mod(p.xz + 0.5*spacing, spacing) - 0.5*spacing;
    vec3  q       = vec3(qxz.x, p.y, qxz.y);

    // — 3) SIMPLE CONTINUOUS BOUNCE (no delay, no decay)
    float maxH   = 2.0;                          // base peak height
    float speed  = 0.5;                          // 1.0 = original; 0.5 = half-speed
    float height = bounceHeight(cell, iTime, maxH, speed);

    // — 4) Build your sphere at y = –1 + r + height
    float r       = 0.23;
    float centerY = -1.0 + r + height;
    float dSphere = length(q - vec3(0.0, centerY, 0.0)) - r;

    // — 5) Pick the nearest surface
    // if this sphere is closest:
// choose one of 4 shapes: 0=sphere,1=torus,2=box,3=pyramid
int shapeIdx = int(mod(cell.x + cell.y, 4.0));

float dShape;
vec3  localP = q - vec3(0.0, centerY, 0.0);

// tweak parameters per-shape
if (shapeIdx == 0) {
    // sphere
    dShape = length(localP) - r;
}
else if (shapeIdx == 1) {
    // torus in XZ plane
    dShape = sdTorus(localP, vec2(r*1.2, r*0.4));
}
else if (shapeIdx == 2) {
    // axis-aligned box
    dShape = sdBox(localP, vec3(r));
}
else {
    // pyramid of height 2*r
   // dShape = sdPyramid(localP, 2.0*r);
    dShape = length(localP) - r;
}

if (dShape < res.dist) {
    res.dist        = dShape;
    res.objectId    = shapeIdx;    // now 0–3
    res.sphereIndex = shapeIdx;    // you can rename this to “shapeIndex”
}


  

    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 (fixed signature + clamped steps + proper miss‐out)
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
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 we’re inside (or very close) we’ve hit something
        if (res.dist < MIN_DIST) {
            hitObject   = res.objectId;
            sphereIndex = res.sphereIndex;
            return p;
        }
        // clamp the step so we don’t jump over small features
        // – 0.8×the SDF gives us a little safety margin
        // – never smaller than half MIN_DIST so we still make progress
        // – never bigger than 1.0 to avoid huge leaps
        float step = clamp(res.dist * 0.8, MIN_DIST * 0.5, 1.0);
        t += step;
        if (t > MAX_DIST) break;
    }
    // no hit: signal sky‐fallback
    hitObject   = -1;
    sphereIndex = -1;
    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.4, 0.8);
    return mix(horizon, zenith, t);
}
vec3 hsv2rgb(vec3 c){
  vec3 p = abs(mod(c.x*6.0 + vec3(0,4,2), 6.0) - 3.0) - 1.0;
  p = clamp(p, 0.0, 1.0);
  return c.z * mix(vec3(1.0), p, c.y);
}
//–––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// 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 < 250; bounce++) {
    int hitObject, sphereIndex;
    vec3 hitPos = rayMarch(ro, rd, hitObject, sphereIndex);
    
    // If nothing is hit, return a dark background.
if (hitObject >= 0 && hitObject <= 2) {
    // perfect mirror hit
    vec3 normal = getNormal(hitPos);
    rd = reflect(rd, normal);
    ro = hitPos + normal * MIN_DIST * 2.0;
    // no color loss
     // --- ADD THIS: compute a random tint per‐cell ---
        vec2 cellCoord = floor((hitPos.xz + 0.5 * SPACING) / SPACING);
        float seed     = rand2(cellCoord);
        // full saturation/value for vivid colors; hue = seed
        vec3 tint      = hsv2rgb(vec3(seed, 1.0, 1.0));
        attenuation   *= tint;
    // trace again (up to MAX_BOUNCES)
    continue;
}
if (hitObject == -1) {
    // was: color += attenuation * vec3(0.1);
    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() {
  // 1) compute uv as before
  vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y;

  // 2) animated orbiting camera
  float t = iTime * 0.2;               // overall orbit speed
  float elevation = sin(iTime * 0.5);  // up/down bob
  float radius = 3.0;
  vec3 ro = vec3(
    radius * cos(t),            // x
    1.0 + 0.5 * elevation,      // y (hover around y=1.0)
    radius * sin(t)             // z
  );

  // 3) build an orientation that looks at the world‐center
  vec3 target = vec3(0.0, 0.0, 0.0);
  vec3 forward = normalize(target - ro);
  vec3 worldUp = vec3(0.0, 1.0, 0.0);
  vec3 right   = normalize(cross(forward, worldUp));
  vec3 up      = cross(right, forward);

  // 4) turn uv into a world-space ray direction
  //    tweak the “1.5” to change field-of-view
  vec3 rd = normalize(uv.x * right + uv.y * up + 1.5 * forward);

  // 5) trace and output
  vec3 col = trace(ro, rd);
  fragColor = vec4(col, 1.0);
}