The Code Therapy

FFT Uniforms

A simple audio visualizer tutorial to demonstrate the Fast Fourier Transform uniforms exposed to XYZ Shader Editor.

Created by marcogomez on Sat, 16 Oct 2021 07:45:47 GMT.


// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm3Texture;
uniform vec2 resolution;
uniform float time;

// All the fft uniforms are obtained from the AnalyserNode on the browser Audio API,
// and the Fast Fourier transform is obtained from 512 bins (fftSize).
// On PRGM1 you can find the fftTexture sampler2D uniform, created from a 512x1 px
// grayscale pseudo-texture (data texture) drawn in real-time using the AnalyserNode data.

uniform float fft; //          The average of all 512 values
uniform float fftLow; //       The average of the first (lower) third of the fft values Array
uniform float fftMid; //       The average of the second (mid) third of the fft values Array
uniform float fftHigh; //      The average of the third (high) third of the fft values Array
uniform float fftLowerHalf; // The average of the lower half of the fft values Array
uniform float fftUpperHalf; // The average of the upper half of the fft values Array

// The following values are the same as above, but normalized (0 to 1) according to...
// the ongoing registration of the maximum values ever achieved for each frequency range.

uniform float fftNormalized;
uniform float fftLowNormalized;
uniform float fftMidNormalized;
uniform float fftHighNormalized;
uniform float fftLowerHalfNormalized;
uniform float fftUpperHalfNormalized;

// https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode

vec3 clamps(vec3 x) { return clamp(x, 0.042, 1.0); }

vec3 getColor(float val, vec2 uv, float mul) {
  return clamps(vec3(val * 1.0 - uv.y) * mul);
}

void main(void) {
  float ratio = 0.2;
  float ratioDiff = 1.0 - ratio;
  float invRatioDiff = 1.0 / ratioDiff;
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float uvy = (uv.y / ratioDiff) - ratio * invRatioDiff;
  vec4 prgm3 = texture2D(prgm3Texture, vec2(uv.x, uvy));
  if (uv.y > ratio + 0.001) {
    gl_FragColor = prgm3;
  } else if (uv.y < ratio - 0.001) {
    float huvy = uv.y / ((ratio * 2.0) - ratio);
    vec2 huv = vec2(uv.x, huvy);
    vec2 grid = fract(huv * 12.0) - 0.5;
    vec3 col = vec3(1.0, 0.8, 0.8);
    vec3 normCol = vec3(0.8, 1.0, 0.8);
    vec3 color;
    float mul = 100.0;
         if (uv.x <  1.0 / 12.0) { color = getColor(fft, huv, mul) * col; }
    else if (uv.x <  2.0 / 12.0) { color = getColor(fftLow, huv, mul) * col; }
    else if (uv.x <  3.0 / 12.0) { color = getColor(fftMid, huv, mul) * col; }
    else if (uv.x <  4.0 / 12.0) { color = getColor(fftHigh, huv, mul) * col; }
    else if (uv.x <  5.0 / 12.0) { color = getColor(fftLowerHalf, huv, mul) * col; }
    else if (uv.x <  6.0 / 12.0) { color = getColor(fftUpperHalf, huv, mul) * col; }
    else if (uv.x <  7.0 / 12.0) { color = getColor(fftNormalized, huv, mul) * normCol; }
    else if (uv.x <  8.0 / 12.0) { color = getColor(fftLowNormalized, huv, mul) * normCol; }
    else if (uv.x <  9.0 / 12.0) { color = getColor(fftMidNormalized, huv, mul) * normCol; }
    else if (uv.x < 10.0 / 12.0) { color = getColor(fftHighNormalized, huv, mul) * normCol; }
    else if (uv.x < 11.0 / 12.0) { color = getColor(fftLowerHalfNormalized, huv, mul) * normCol; }
    else if (uv.x < 12.0 / 12.0) { color = getColor(fftUpperHalfNormalized, huv, mul) * normCol; }
    if (grid.x > 0.45 || uv.x < 0.005) color = vec3(0.0);
    gl_FragColor = vec4(clamps(color), 1.0);
  }
  if (uv.y > ratio - 0.001 && uv.y < ratio + 0.001) { gl_FragColor = vec4(1.0); }
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D fftTexture;
uniform vec2 resolution;
uniform float time;
uniform int frame;

vec3 hash33(vec3 p3) {
  p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
  p3 += dot(p3, p3.yxz + 19.19);
  return fract((p3.xxy + p3.yxx) * p3.zyx);
}

vec3 mixColor(vec3 col1, vec3 col2, float v) {
  v = clamp(v, 0.0, 1.0);
  return clamp(col1 + v * (col2 - col1 * 1.5), 0.0, 1.0);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  uv.x = (uv.x - 0.5 > 0.0) ? uv.x - 0.521 : 0.479 - uv.x;
  vec2 p = uv * 2.0 - 1.0;
  p *= vec2(resolution.x / resolution.y, 1.0);
  p.y += 0.3;
  vec3 col = vec3(0.0);
  vec3 ref = vec3(0.0);
  float nBands = 64.0;
  float i = floor((uv.x * 1.3 + 0.3) * nBands);
  float f = fract((uv.x * 1.3 + 0.3) * nBands);
  float band = i/nBands;
  band *= band * band;
  band = band * 0.995;
  band += 0.005;
  float s = texture2D(fftTexture, vec2(band, 0.25)).x;
  const int nColors = 4;
  vec3 colors[nColors];
  colors[0] = vec3(0.0, 0.0, 1.0);
  colors[1] = vec3(0.0, 1.0, 1.0);
  colors[2] = vec3(1.0, 1.0, 0.0);
  colors[3] = vec3(1.0, 0.0, 0.0);
  vec3 gradCol = colors[0];
  float n = float(nColors) - 1.0;
  for(int i = 1; i < nColors; i++) {
    gradCol = mixColor(gradCol, colors[i], (s - float(i - 1) / n) * n);
  }
  col += vec3(1.0 - smoothstep(0.0, 0.001, p.y - s * 1.25));
  col *= gradCol;
  ref += vec3(1.0 - smoothstep(0.0, -0.001, p.y + s * 1.25));
  ref *= gradCol * smoothstep(-0.5, 0.5, p.y);
  col = mix(ref, col, smoothstep(-0.005, 0.005, p.y));
  col *= smoothstep(0.125, 0.375, f);
  col *= smoothstep(0.875, 0.625, f);
  col = clamp(col, 0.0, 1.0);
  vec3 h = hash33(vec3(gl_FragCoord.xy, float(frame)));
  float dither = h.x * h.y;
  col += dither * 0.04;
  gl_FragColor = vec4(col,1.0);
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm1Texture;
uniform vec2 resolution;
uniform float time;
uniform int frame;

vec3 hash33(vec3 p3) {
  p3 = fract(p3 * vec3(0.1031, 0.1030, 0.0973));
  p3 += dot(p3, p3.yxz + 19.19);
  return fract((p3.xxy + p3.yxx) * p3.zyx);
}

void main(void) {
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  vec4 texCol = texture2D(prgm1Texture, uv);
  vec4 col = texCol;
  float w = 0.1;
  vec2 a = vec2(uv.x - 0.5, uv.y - 0.66);
  vec2 b = a * 0.15 / float(10.0);
  vec3 h = hash33(vec3(gl_FragCoord.xy, frame));
  uv += b * h.x;
  for (float i = 1.0; i > 0.9; i-= 0.000625) {
    uv -= 0.5;
    uv *= i;
    uv += 0.5;
    col += texture2D(prgm1Texture, uv) * w * 1.5;
    w *= 0.95;
  }
  col *= 0.9;
  gl_FragColor = mix(texCol, col, 0.5);
}

// ╔═════════════╦════════════════╗
// ║ Marco Gomez ║ https://mgz.me ║
// ╚═════════════╩════════════════╝
precision highp float;

uniform sampler2D prgm2Texture;
uniform vec2 resolution;
uniform float time;
uniform float fft;
uniform float fftHighNormalized;

const float PI = acos(-1.0);
const float TAU = PI * 2.0;
const float hardscan = -16.0; // -8.0 = soft | -16.0 = medium
const float hardPix = -4.0; // -2.0 = soft | -4.0 = hard
const float maskDark = 0.5;
const float maskLight = 2.5;
const float hk = 1.0 / sqrt(3.0);

vec3 hueShift(vec3 col, float a) {
  const vec3 k = vec3(hk);
  float ca = cos(a);
  return vec3(col * ca + cross(k, col) * sin(a) + k * dot(k, col) * (1.0 - ca));
}

float toLinear(float c) {
  return (c <= 0.04045) ? c / 12.92 : pow(abs((c + 0.055) / 1.055), 2.4);
}

vec3 toLinear(vec3 c) {
  return vec3(toLinear(c.r), toLinear(c.g), toLinear(c.b));
}

float toSRGB(float c) {
  return(c < 0.0031308 ? c * 12.92 : 1.055 * pow(abs(c), 0.41666) - 0.055);
}

vec3 toSRGB(vec3 c) {
  return vec3(toSRGB(c.r), toSRGB(c.g), toSRGB(c.b));
}

vec3 fetch(vec2 pos, vec2 off, vec2 res) {
  pos = floor(pos * res + off) / res;
  if (max(abs(pos.x - 0.5), abs(pos.y - 0.5)) > 0.5) {
    return vec3(0.0);
  }
  vec3 fetch = texture2D(prgm2Texture, pos.xy, -16.0).xyz;
  fetch = hueShift(fetch, 1.5 * fftHighNormalized + time * 0.75);
  return toLinear(mix(fetch, fetch * fetch, 0.5));
}

vec2 dist(vec2 pos, vec2 res) {
  pos = pos * res;
  return -((pos - floor(pos)) - vec2(0.5));
}

float gauss(float pos, float scale) {
  return exp2(scale * pos * pos);
}

vec3 horz3(vec2 pos, float off, vec2 res) {
  vec3 b = fetch(pos, vec2(-1.0, off), res);
  vec3 c = fetch(pos, vec2(+0.0, off), res);
  vec3 d = fetch(pos, vec2(+1.0, off), res);
  float dst = dist(pos, res).x;
  float scale = hardPix;
  float wb = gauss(dst - 1.0, scale);
  float wc = gauss(dst + 0.0, scale);
  float wd = gauss(dst + 1.0, scale);
  return (b * wb + c * wc + d * wd) / (wb + wc + wd);
}

vec3 horz5(vec2 pos, float off, vec2 res) {
  vec3 a = fetch(pos, vec2(-2.0, off), res);
  vec3 b = fetch(pos, vec2(-1.0, off), res);
  vec3 c = fetch(pos, vec2(+0.0, off), res);
  vec3 d = fetch(pos, vec2(+1.0, off), res);
  vec3 e = fetch(pos, vec2(+2.0, off), res);
  float dst = dist(pos, res).x;
  float scale = hardPix;
  float wa = gauss(dst - 2.0, scale);
  float wb = gauss(dst - 1.0, scale);
  float wc = gauss(dst + 0.0, scale);
  float wd = gauss(dst + 1.0, scale);
  float we = gauss(dst + 2.0, scale);
  return (a * wa + b * wb + c * wc + d * wd + e * we) / (wa + wb + wc + wd + we);
}

float scan(vec2 pos, float off, vec2 res) {
  float dst = dist(pos, res).y;
  return gauss(dst + off, hardscan);
}

vec3 tri(vec2 pos, vec2 res) {
  vec3 a = horz3(pos, -1.0, res);
  vec3 b = horz5(pos, +0.0, res);
  vec3 c = horz3(pos, +1.0, res);
  float wa = scan(pos, -1.0, res);
  float wb = scan(pos, +0.0, res);
  float wc = scan(pos, +1.0, res);
  return a * wa + b * wb + c * wc;
}

vec3 mask(vec2 pos) {
  pos.x += pos.y * 3.0;
  vec3 m = vec3(maskDark, maskDark, maskDark);
  pos.x = fract(pos.x / 6.0);
  if (pos.x < 0.333) {
    m.r = maskLight;
  } else if (pos.x < 0.666) {
    m.g = maskLight;
  } else {
    m.b = maskLight;
  }
  return m;
}

float bar(float pos, float bar) {
  pos -= bar;
  return pos * pos < 4.0 ? 0.0 : 1.0;
}

vec2 warp(vec2 uv, vec2 warpAmount) {
  uv = uv * 2.0 - 1.0;
  vec2 offset = abs(uv.yx) / vec2(warpAmount.x, warpAmount.y);
  uv = uv + uv * offset * offset;
  uv = uv * 0.5 + 0.5;
  return uv;
}

void drawVig(inout vec3 color, vec2 uv) {
  float vignette = uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y);
  vignette = clamp(pow(abs(16.0 * vignette), 0.1), 0.0, 1.0);
  color *= vignette;
}

void main(void) {
  vec2 warpAmount = vec2(7.0, 5.0);
  vec2 res = vec2(resolution.x / 4.0, resolution.y / 3.0);
  vec2 uv = gl_FragCoord.xy / resolution.xy;
  float vig = (0.0 + 1.0 * 21.0 * uv.x * uv.y * (1.0 - uv.x) * (1.0 - uv.y));
  float v = exp(-0.01 * length(uv)) * vig;
  vec2 pos = mix(uv, warp(uv, warpAmount), 0.75);
  vec4 color = vec4(tri(pos, res) * mask(gl_FragCoord.xy), 1.0);
  color.xyz = toSRGB(color.xyz * 2.0);
  color = mix(color, color * v, 0.7);
  gl_FragColor = vec4(color.xyz, 1.0);
}