• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

LOST - WEBGL CATHEDRAL PROCEDURAL SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lost Cathedral</title>
<style>
  html,body{margin:0;padding:0;overflow:hidden;background:#000;height:100%;width:100%}
  canvas{display:block;width:100vw;height:100vh}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl', {antialias:true, alpha:false}) ||
           canvas.getContext('experimental-webgl', {antialias:true, alpha:false});
if(!gl){
  document.body.innerHTML = '<p style="color:#fff;font-family:sans-serif;padding:2em">WebGL not available.</p>';
}

const vsSource = `
attribute vec2 aPos;
void main(){
  gl_Position = vec4(aPos, 0.0, 1.0);
}
`;

const fsSource = `
precision highp float;

uniform vec2  uRes;
uniform float uTime;

#define PI 3.14159265359
#define TAU 6.28318530718

vec2 hash2(vec2 p){
  p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
  return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float hash1(vec2 p){
  return fract(sin(dot(p, vec2(41.3, 289.1))) * 43758.5453123);
}

float vnoise(vec2 p){
  vec2 i = floor(p);
  vec2 f = fract(p);
  vec2 u = f*f*(3.0-2.0*f);
  float a = hash1(i);
  float b = hash1(i+vec2(1.0,0.0));
  float c = hash1(i+vec2(0.0,1.0));
  float d = hash1(i+vec2(1.0,1.0));
  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}

vec3 voronoi(vec2 p, float t){
  vec2 ip = floor(p);
  vec2 fp = fract(p);
  float minD1 = 8.0, minD2 = 8.0;
  float cellId = 0.0;
  for(int j=-1;j<=1;j++){
    for(int i=-1;i<=1;i++){
      vec2 g = vec2(float(i), float(j));
      vec2 o = hash2(ip + g);
      vec2 cpos = g + 0.5 + 0.5*sin(t*0.13 + o*6.2831 + o.yx*3.0);
      vec2 r = cpos - fp;
      float d = dot(r, r);
      if(d < minD1){
        minD2 = minD1;
        minD1 = d;
        cellId = hash1(ip+g+3.1);
      } else if(d < minD2){
        minD2 = d;
      }
    }
  }
  return vec3(sqrt(minD1), sqrt(minD2), cellId);
}

mat2 rot(float a){
  float s = sin(a), c = cos(a);
  return mat2(c, -s, s, c);
}

vec2 kaleidoFold(vec2 p, float segments, float time){
  float a = atan(p.y, p.x);
  float r = length(p);
  float seg = TAU / segments;
  a = mod(a, seg);
  a = abs(a - seg*0.5);
  r = r / (1.0 + 0.15*sin(r*3.0 - time*0.4));
  return vec2(cos(a), sin(a)) * r;
}

vec2 fractalFold(vec2 p, float time){
  for(int i=0; i<4; i++){
    p = abs(p) - 0.6 + 0.05*sin(time*0.07 + float(i)*1.7);
    p *= rot(0.4 + 0.15*sin(time*0.05 + float(i)));
    p *= 1.35;
  }
  return p;
}

// Fixed: integer loop counter with constant bound (required by GLSL ES / ANGLE)
float quasicrystal(vec2 p, float time){
  float sum = 0.0;
  const float n = 7.0;
  for(int k=0; k<7; k++){
    float i = float(k);
    float ang = PI * i / n + time*0.015;
    vec2 dir = vec2(cos(ang), sin(ang));
    sum += cos(dot(p, dir) * 6.0 + time*0.25 + i);
  }
  return sum / n;
}

vec3 palette(float t, vec3 a, vec3 b, vec3 c, vec3 d){
  return a + b*cos(TAU*(c*t+d));
}

float smin(float a, float b, float k){
  float h = clamp(0.5 + 0.5*(b-a)/k, 0.0, 1.0);
  return mix(b, a, h) - k*h*(1.0-h);
}

void main(){
  vec2 uv = (gl_FragCoord.xy - 0.5*uRes) / min(uRes.x, uRes.y);
  float time = uTime;

  float globalA = time * 0.025 + 0.3*sin(time*0.011);
  vec2 p = uv * rot(globalA);
  float zoom = 1.6 + 0.45*sin(time*0.027) + 0.15*sin(time*0.071+1.3);
  p *= zoom;

  float segCount = 6.0 + 4.0*sin(time*0.017);
  float symBreak = 0.18*sin(time*0.0083 + 1.0);
  vec2 kp = kaleidoFold(p, max(3.0, segCount), time);
  kp += symBreak * vec2(vnoise(p*1.3 + time*0.05), vnoise(p*1.7 - time*0.04));

  vec2 fp = fractalFold(kp*1.4, time);

  vec3 v1 = voronoi(fp*1.4 + 0.0, time);
  vec3 v2 = voronoi(fp*3.1 + 7.0, time*1.3);
  vec3 v3 = voronoi(fp*6.7 + 19.0, time*0.7);

  float edge1 = smoothstep(0.0, 0.06, v1.y - v1.x);
  float edge2 = smoothstep(0.0, 0.04, v2.y - v2.x);
  float edge3 = smoothstep(0.0, 0.025, v3.y - v3.x);

  float qc = quasicrystal(kp*2.2, time);
  float qcGlow = smoothstep(-0.2, 1.0, qc);

  float structure = 1.0 - edge1;
  structure = smin(structure, 1.0 - edge2*0.85, 0.25);
  structure = smin(structure, 1.0 - edge3*0.6, 0.15);

  float rd = vnoise(fp*2.5 + time*0.08) - vnoise(fp*4.1 - time*0.06);
  structure += rd * 0.08;

  float idMix = fract(v1.z*1.0 + v2.z*0.6 + v3.z*0.3 + time*0.01);
  float hueShift = time*0.02 + 0.4*sin(time*0.007);

  vec3 colA = vec3(0.30);
  vec3 colB = vec3(0.28);
  vec3 colC = vec3(1.0, 0.85, 0.6);
  vec3 colD = vec3(0.15, 0.4, 0.6) + hueShift;

  vec3 baseColor = palette(idMix + 0.15*qc, colA, colB, colC, colD);

  float depth = clamp(1.0 - v1.x*0.9, 0.0, 1.0);

  vec3 color = baseColor * (0.15 + 0.6*depth);
  color *= mix(0.5, 1.1, qcGlow);

  float edgeGlowMask = (1.0-edge1)*0.6 + (1.0-edge2)*0.35 + (1.0-edge3)*0.2;
  vec3 edgeColor = palette(idMix*1.7 + 0.5, vec3(0.35), vec3(0.30), vec3(1.0), vec3(0.2,0.5,0.8)+hueShift);
  color += edgeColor * edgeGlowMask * 0.4;

  float bloomSrc = clamp(structure*0.6 + qcGlow*0.5 + edgeGlowMask*0.7 - 0.4, 0.0, 1.0);
  vec3 bloom = baseColor * pow(bloomSrc, 2.0) * 0.5;
  color += bloom;

  float vig = smoothstep(1.4, 0.0, length(uv));
  color *= mix(0.35, 0.95, vig);

  float shimmer = 0.04*sin(time*1.7 + length(p)*8.0) * sin(time*0.31);
  color += shimmer;

  color = color / (1.0 + color*1.0);
  color = pow(color, vec3(1.1));

  float dither = (hash1(gl_FragCoord.xy + time) - 0.5) / 255.0;
  color += dither;

  gl_FragColor = vec4(clamp(color, 0.0, 1.0), 1.0);
}
`;

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

const vs = compile(gl.VERTEX_SHADER, vsSource);
const fs = compile(gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS)){
  console.error(gl.getProgramInfoLog(program));
}
gl.useProgram(program);

const quad = new Float32Array([
  -1,-1,  1,-1,  -1,1,
  -1,1,   1,-1,   1,1
]);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, quad, gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(program, 'aPos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

const uRes = gl.getUniformLocation(program, 'uRes');
const uTime = gl.getUniformLocation(program, 'uTime');

function resize(){
  const dpr = Math.min(window.devicePixelRatio || 1, 2.0);
  const w = Math.floor(window.innerWidth * dpr);
  const h = Math.floor(window.innerHeight * dpr);
  if(canvas.width !== w || canvas.height !== h){
    canvas.width = w;
    canvas.height = h;
  }
  gl.viewport(0, 0, canvas.width, canvas.height);
}
window.addEventListener('resize', resize);
resize();

let startTime = performance.now();
function render(){
  const t = (performance.now() - startTime) * 0.001;
  gl.uniform2f(uRes, canvas.width, canvas.height);
  gl.uniform1f(uTime, t);
  gl.drawArrays(gl.TRIANGLES, 0, 6);
  requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.