← Mike Higgins Home
Custom Code Experiment •

Conformal Map 6

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>Conformal Map 6</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    html, body {
      width: 100%;
      height: 100%;
      overflow: hidden;
      background: #000;
    }
    canvas {
      display: block;
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<body>
<canvas id="c"></canvas>
<script>
(function(){
  const canvas = document.getElementById('c');
  const gl = canvas.getContext('webgl2', { antialias: false, depth: false });
  if (!gl) {
    canvas.parentElement.innerHTML = '<div style="color:white; padding:20px;">WebGL2 not supported<br>Try a modern browser.</div>';
    return;
  }

  // --------------------------------------------------------------
  // VERTEX SHADER (fullscreen triangle strip)
  // --------------------------------------------------------------
  const VS = `#version 300 es
    in vec2 aPos;
    out vec2 vUv;
    void main() {
      vUv = aPos * 0.5 + 0.5;
      gl_Position = vec4(aPos, 0.0, 1.0);
    }
  `;

  // ========== SHADER A: RETRO CHAOS ENGINE ==========
  const FS_GENERATOR = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform float uTime;
    uniform float uAspect;
    uniform vec2 uRandomSeed;

    float hash(vec2 p) {
      return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
    }
    float noise(vec2 p) {
      vec2 i = floor(p);
      vec2 f = fract(p);
      vec2 u = f * f * (3.0 - 2.0 * f);
      return mix( mix(hash(i + vec2(0.0,0.0)), hash(i + vec2(1.0,0.0)), u.x),
                  mix(hash(i + vec2(0.0,1.0)), hash(i + vec2(1.0,1.0)), u.x), u.y);
    }

    void main() {
      vec2 uv = vUv;
      uv.x *= uAspect;
      float t = uTime * 1.7;
      float chaos = sin(t * 0.37) * cos(t * 0.59) + sin(t * 1.13);
      float chaos2 = fract(sin(t * 0.923) * 1245.7);

      vec2 grid = uv * 8.0 + vec2(t * 0.5, t * 0.3);
      vec2 igrid = floor(grid);
      vec2 fgrid = fract(grid);
      float logistic = 0.0;
      for(int i = -1; i <= 1; i++) {
        for(int j = -1; j <= 1; j++) {
          vec2 neighbor = igrid + vec2(i, j);
          float seed = hash(neighbor + floor(uTime * 0.23));
          float x = seed;
          for(int iter = 0; iter < 5; iter++) {
            x = 3.9 * x * (1.0 - x);
          }
          logistic += x * exp(-2.0 * length(fgrid - vec2(i,j)));
        }
      }
      logistic = fract(logistic * 10.0);

      vec2 pos = uv * 3.0 - 1.5;
      float r = length(pos + vec2(sin(t*0.71)*0.5, cos(t*0.83)*0.5));
      float theta = atan(pos.y, pos.x) + t * 0.9;
      float attractX = sin(theta * 4.0 + logistic * 12.0) * 0.8 / (r + 0.2);
      float attractY = cos(theta * 3.7 - t) * 0.8 / (r + 0.2);
      vec2 attractField = vec2(attractX, attractY);

      vec2 warpUv = uv + attractField * 0.25 + vec2(noise(uv * 12.0 + t * 0.5), noise(uv.yx * 13.0 - t * 0.4)) * 0.1;
      warpUv = fract(warpUv * 2.0) * 2.0 - 1.0;
      warpUv.x *= uAspect;

      vec3 col = vec3(0.0);
      float chromaShift = sin(warpUv.y * 80.0 - t * 25.0) * 0.4 + cos(warpUv.x * 70.0 + t * 18.0) * 0.4;

      vec2 ctr = vec2(0.5, 0.5);
      float dist = length(warpUv - ctr);
      float rings = sin(30.0 * dist - t * 12.0) * 0.5 + 0.5;
      rings *= 1.0 - smoothstep(0.2, 0.8, dist);
      rings = pow(rings, 1.5) * 1.2;

      float scanline = sin(uv.y * 600.0 + t * 100.0) * 0.3 + 0.7;
      float staticNoise = hash(uv * 400.0 + floor(t * 40.0)) * 0.5;

      vec2 checkerUv = warpUv * 12.0;
      float check = step(0.5, fract(checkerUv.x + checkerUv.y + logistic * 5.0));
      vec3 baseColor = mix(vec3(0.05, 0.9, 0.2), vec3(0.9, 0.05, 0.5), check);
      baseColor = mix(baseColor, vec3(0.2, 0.1, 0.8), fract(logistic * 8.0));

      col.r = sin(baseColor.r * 15.0 + t * 7.0 + rings * 12.0) * 0.8 + 0.5;
      col.g = cos(baseColor.g * 18.0 - t * 6.5 + rings * 9.0) * 0.8 + 0.5;
      col.b = sin(baseColor.b * 22.0 + t * 8.0 + logistic * 10.0) * 0.8 + 0.5;

      col += vec3(attractField.x * 0.8, attractField.y * 0.7, (attractField.x + attractField.y) * 0.5);
      col += vec3(0.7, 0.3, 0.9) * rings * (0.8 + 0.5 * sin(t * 9.0));

      float glitch = step(0.98, hash(vec2(floor(uv.y * 30.0 + t * 12.0), t)));
      col = mix(col, vec3(1.0, 0.0, 1.0), glitch * 0.9);

      vec2 tearUv = uv;
      tearUv.x += sin(uv.y * 150.0 + t * 90.0) * 0.05 * glitch;
      if (fract(uv.y * 20.0 + t * 5.0) < 0.03) col = vec3(1.0, 1.0, 1.0);

      col.r *= scanline * (0.8 + staticNoise);
      col.g *= scanline * (0.7 + staticNoise * 0.7);
      col.b *= scanline * (0.6 + staticNoise * 0.9);

      float vig = 1.0 - length(uv - 0.5) * 0.7;
      col *= vig;
      col = floor(col * 8.0) / 8.0;

      fragColor = vec4(col, 1.0);
    }
  `;

  // ========== SHADER B: FEEDBACK CHAOS LOOP ==========
  const FS_FEEDBACK = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform sampler2D uPrevTex;
    uniform sampler2D uFreshTex;
    uniform float uTime;
    uniform float uAspect;
    uniform float uFeedbackAmount;

    float hash(vec2 p) {
      return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
    }

    void main() {
      vec2 uv = vUv;
      float t = uTime;

      vec2 q = uv * 2.0 - 1.0;
      q.x *= uAspect;
      float angle = sin(t * 0.47) * 6.2831;
      float s = sin(angle);
      float c = cos(angle);
      vec2 rot1 = vec2( c * q.x - s * q.y, s * q.x + c * q.y );
      vec2 rot2 = vec2( c * q.x + s * q.y, -s * q.x + c * q.y );

      float sel = fract(sin(t * 0.231) * 123.456);
      vec2 warp;
      if (sel < 0.33) {
        warp = rot1 * 2.0 + vec2(0.5, 0.3);
      } else if (sel < 0.66) {
        warp = rot2 * 1.8 + vec2(-0.4, 0.6);
      } else {
        warp = (rot1 + rot2) * 0.9 + vec2(0.2, -0.5);
      }
      warp = abs(fract(warp * 1.5) - 0.5) * 2.0;
      vec2 chaoticUv = warp * 0.5 + 0.5;
      chaoticUv.x /= uAspect;

      vec2 distort = vec2(
        sin(uv.y * 40.0 + t * 15.0) * 0.03,
        cos(uv.x * 35.0 - t * 12.0) * 0.03
      );
      vec2 finalUv = chaoticUv + distort;

      vec3 prev = texture(uPrevTex, fract(finalUv)).rgb;
      vec3 fresh = texture(uFreshTex, uv + distort * 0.5).rgb;

      float feedback = uFeedbackAmount * (0.7 + 0.3 * sin(t * 2.3));
      vec3 col = mix(fresh, prev, feedback);
      col = max(col, prev * 0.85);
      col += fresh * 0.3;
      col = clamp(col, 0.0, 1.2);
      col = pow(col, vec3(1.0/1.2));

      fragColor = vec4(col, 1.0);
    }
  `;

  const FS_COPY = `#version 300 es
    precision highp float;
    in vec2 vUv;
    out vec4 fragColor;
    uniform sampler2D uTex;
    void main() {
      fragColor = texture(uTex, vUv);
    }
  `;

  function compileShader(type, src) {
    const s = gl.createShader(type);
    gl.shaderSource(s, src);
    gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
      console.error(gl.getShaderInfoLog(s));
      gl.deleteShader(s);
      return null;
    }
    return s;
  }

  function createProgram(vsSrc, fsSrc) {
    const vs = compileShader(gl.VERTEX_SHADER, vsSrc);
    const fs = compileShader(gl.FRAGMENT_SHADER, fsSrc);
    if (!vs || !fs) return null;
    const prog = gl.createProgram();
    gl.attachShader(prog, vs);
    gl.attachShader(prog, fs);
    gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
      console.error(gl.getProgramInfoLog(prog));
      return null;
    }
    gl.deleteShader(vs);
    gl.deleteShader(fs);
    return prog;
  }

  const progGen = createProgram(VS, FS_GENERATOR);
  const progFeedback = createProgram(VS, FS_FEEDBACK);
  const progCopy = createProgram(VS, FS_COPY);
  if (!progGen || !progFeedback || !progCopy) throw new Error("Shader compilation failed");

  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);
  const vbo = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, -1,1, 1,1]), gl.STATIC_DRAW);
  const aPos = gl.getAttribLocation(progGen, 'aPos');
  gl.enableVertexAttribArray(aPos);
  gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

  let fboA, fboB, fboTemp;

  function createFBOandTex(w, h) {
    const tex = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    const fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
    return { fbo, tex };
  }

  let w, h;
  function resizeAndRecreate() {
    w = canvas.clientWidth;
    h = canvas.clientHeight;
    canvas.width = w;
    canvas.height = h;
    if (fboA) {
      gl.deleteFramebuffer(fboA.fbo);
      gl.deleteTexture(fboA.tex);
      gl.deleteFramebuffer(fboB.fbo);
      gl.deleteTexture(fboB.tex);
      gl.deleteFramebuffer(fboTemp.fbo);
      gl.deleteTexture(fboTemp.tex);
    }
    fboA = createFBOandTex(w, h);
    fboB = createFBOandTex(w, h);
    fboTemp = createFBOandTex(w, h);
  }
  window.addEventListener('resize', () => { resizeAndRecreate(); });
  resizeAndRecreate();

  const locGenTime = gl.getUniformLocation(progGen, 'uTime');
  const locGenAspect = gl.getUniformLocation(progGen, 'uAspect');
  const locGenSeed = gl.getUniformLocation(progGen, 'uRandomSeed');
  const locFbTime = gl.getUniformLocation(progFeedback, 'uTime');
  const locFbAspect = gl.getUniformLocation(progFeedback, 'uAspect');
  const locFbPrev = gl.getUniformLocation(progFeedback, 'uPrevTex');
  const locFbFresh = gl.getUniformLocation(progFeedback, 'uFreshTex');
  const locFbAmount = gl.getUniformLocation(progFeedback, 'uFeedbackAmount');
  const locCopyTex = gl.getUniformLocation(progCopy, 'uTex');

  function render(nowMs) {
    requestAnimationFrame(render);
    let t = nowMs * 0.001;
    let aspect = w / h;

    gl.bindVertexArray(vao);
    gl.viewport(0, 0, w, h);

    // fresh chaotic frame
    gl.bindFramebuffer(gl.FRAMEBUFFER, fboTemp.fbo);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(progGen);
    gl.uniform1f(locGenTime, t);
    gl.uniform1f(locGenAspect, aspect);
    gl.uniform2f(locGenSeed, Math.sin(t)*1245.7, Math.cos(t*0.71)*982.3);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    // feedback pass
    gl.useProgram(progFeedback);
    gl.uniform1f(locFbTime, t);
    gl.uniform1f(locFbAspect, aspect);
    gl.uniform1i(locFbPrev, 0);
    gl.uniform1i(locFbFresh, 1);
    let fbAmount = 0.65 + 0.3 * Math.sin(t * 0.63) * Math.cos(t * 0.91);
    gl.uniform1f(locFbAmount, fbAmount);

    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fboA.tex);
    gl.activeTexture(gl.TEXTURE1);
    gl.bindTexture(gl.TEXTURE_2D, fboTemp.tex);

    gl.bindFramebuffer(gl.FRAMEBUFFER, fboB.fbo);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    // swap A and B
    let tempFbo = fboA;
    fboA = fboB;
    fboB = tempFbo;

    // output to screen
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.useProgram(progCopy);
    gl.uniform1i(locCopyTex, 0);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fboA.tex);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
  }

  requestAnimationFrame(render);
})();
</script>
</body>
</html>