Back to shaders

Shader test bench

Piano

suboptimal-shader-tutorials generative glsl runnable fragment MIT
Source
runnable fragment

Complete GLSL fragment shader. Stronghold runs it directly when the browser can compile it.

Code

precision mediump float;

// #extension GL_GOOGLE_include_directive : enable
#include "../common/functions.glsl"

// shapes
float sdRoundBox(vec3 p, vec3 b, float r);
// space
float opUnion(float d1, float d2);
vec3 opBend(in vec3 p, float bendFactor);
vec3 opSpaceRepetition(in vec3 p, in vec3 s, inout vec3 id);
// lighting
vec3 calcNormal(vec3 p);
vec3 calcLighting(vec3 ro, vec3 n);
// functions
mat2 rotate(float angle);

// constants
float MAX_DIST = 100.0;
float MIN_DIST = 0.0001;

// colors
vec3 BLACK = vec3(0.0);
vec3 WHITE = vec3(1.0);
vec3 RED = vec3(1.0, 0.0, 0.0);
vec3 MAGENTA = vec3(1.0, 0.0, 1.0);
vec3 GREEN = vec3(0.0, 1.0, 0.0);

// uniforms
uniform float u_time;
uniform vec2 u_resolution;

float whiteKey(vec3 p) {
  float y = 2.0;
  float x = y / 4.0;
  float z = y / 10.0;
  float d = sdRoundBox(p, vec3(x, y, z), 0.1) * .5;
  return d;
}

float blackKey(vec3 p) {
  float y = 1.2;
  float x = y / 4.0;
  float z = y / 8.0;
  float d = sdRoundBox(p, vec3(x, y, z), 0.1) * 0.5;
  return d;
}

float map(vec3 p, inout vec3 c) {
  // bend piano
  p = opBend(p, (sin(u_time) * 0.5) * 0.25);
  p.xzy = opBend(p.xzy, 0.05);

  vec3 whiteKeyId;
  vec3 q = opSpaceRepetition(p, vec3(1.1, 0.0, 0.0), whiteKeyId);
  q.y = q.y + sin(u_time + whiteKeyId.x * 0.5);

  vec3 blackKeyId;
  vec3 qb = opSpaceRepetition(p - vec3(0.5, 0.8, -0.25), vec3(1.0, 0.0, 0.0), blackKeyId);
  qb.y = qb.y + sin(u_time + blackKeyId.x * 0.5);

  // pressing keys progress
  float speed = 4.0;
  float amplitude = 0.05;
  vec3 axisPoint = vec3(0.0, 2.0, 0.0);
  if (whiteKeyId.x == 2.0) {
    q -= axisPoint;
    q.yz = q.yz * rotate((sin(u_time * speed) * 0.5 + 0.5) * amplitude);
    q += axisPoint;
  }
  if (blackKeyId.x == -3.0) {
    qb -= axisPoint;
    qb.yz = qb.yz * rotate((sin(u_time * speed) * 0.5 + 0.5) * amplitude);
    qb += axisPoint;
  }

  float wk = whiteKey(q);
  float bk = blackKey(qb);

  float m = mod(blackKeyId.x, 7.0);
  bk = m == 0.0 || m == 3.0 ? 20.0 : bk;
  c = wk < bk ? WHITE : BLACK;
  if (wk < bk) {
    if (mod(whiteKeyId.x, 7.0) == 2.0) {
      c = mix(WHITE, RED, smoothstep(0.1, 0.15, sin(u_time * 4.0) * 0.5 + 0.5));
    }
  } else {
    if (mod(blackKeyId.x, 7.0) == 4.0) {
      c = mix(BLACK, RED, smoothstep(0.1, 0.2, sin(u_time * 4.0) * 0.5 + 0.5));
    }
  }

  float d = opUnion(wk, bk);
  return d;
}

void main() {
  vec2 uv = (2.0 * gl_FragCoord.xy - u_resolution.xy) / u_resolution.y;
  vec3 ro = vec3(0.0, 0.0, -4.0);
  vec3 rd = normalize(vec3(uv, 1.0));
  float td = 0.0; // total distance

  vec3 c;
  vec3 n;
  float steps = 0.0;
  for (int i = 0; i < 100; i++) {
    vec3 p = ro + rd * td;
    float d = map(p, c);
    steps = float(i);
    if (td > MAX_DIST || d < abs(MIN_DIST)) {
      break;
    }
    td += d;
  }

  vec3 color = GREEN;
  if (td < MAX_DIST) {
    vec3 p = ro + rd * td;
    n = calcNormal(p);
    color = c * calcLighting(ro, n);
  }

  // uncomment each one to see different color styles
  // color = vec3(td * 0.05);
  // color = steps < 80.0 ? pow(vec3(steps * 0.01) * color, vec3(0.25)) : pow(color * steps * 0.1, vec3(0.1));
  // color = pow(vec3(steps * 0.01) * color * steps, vec3(0.125));
  // color = td < 100.0 ? pow(vec3(steps * 0.01), vec3(0.25)) : color * steps * 0.01;
  // color = td < 100.0 ? pow(vec3(steps * 0.01) * color, vec3(0.125)) : color * steps * 0.01;
  color = td < 100.0 ? pow(vec3(steps * 0.01), vec3(0.25)) * n : color * steps * 0.01;

  // add gamma correction
  color = pow(color, vec3(1.0 / 2.2));

  gl_FragColor = vec4(color, 1.0);
}