• 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

GLASS - WEBGL CATHEDRAL PROCEDURAL SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Glass 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, premultipliedAlpha:false});
if (!gl) { document.body.innerHTML = '<p style="color:#fff;font-family:sans-serif;padding:2em">WebGL not available.</p>'; }
const derivExt = gl && gl.getExtension('OES_standard_derivatives');

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,w,h);
  }
}
window.addEventListener('resize', resize);
resize();

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

const fragSrc = `
#extension GL_OES_standard_derivatives : enable
precision highp float;

uniform vec2  uRes;
uniform float uTime;

#define PI 3.14159265359
#define TAU 6.28318530718

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

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), 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);
}

float fbm(vec2 p){
  float v = 0.0, a = 0.5;
  for(int i=0;i<5;i++){
    v += a*vnoise(p);
    p = rot(0.7)*p*2.02;
    a *= 0.55;
  }
  return v;
}

vec4 voronoi(vec2 p, float jitterTime){
  vec2 ip = floor(p);
  vec2 fp = fract(p);
  float d1 = 8.0, d2 = 8.0;
  vec2 bestOff = vec2(0.0);
  float idMix = 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);
      o = 0.5 + 0.5*sin(jitterTime*vec2(0.31,0.27) + o*TAU);
      vec2 r = g + o - fp;
      float d = dot(r,r);
      if (d < d1){ d2 = d1; d1 = d; bestOff = g+o; idMix = hash1(ip+g+vec2(3.1,7.7)); }
      else if (d < d2){ d2 = d; }
    }
  }
  return vec4(sqrt(d1), sqrt(d2), idMix, 0.0);
}

vec2 foldSpace(vec2 p, float t, out float depthAccum){
  depthAccum = 0.0;
  float scale = 1.0;
  float order = 5.0 + 3.5*(0.5+0.5*sin(t*0.021 + 1.7));
  for(int i=0;i<6;i++){
    float ang = TAU/order;
    float a = atan(p.y, p.x);
    float r = length(p);
    a = mod(a, ang);
    a = abs(a - ang*0.5);
    p = r*vec2(cos(a), sin(a));
    float breathe = 1.55 + 0.18*sin(t*0.05 + float(i)*1.3);
    p = p*breathe - vec2(0.62, 0.0);
    p *= rot(0.15*sin(t*0.013 + float(i)) + float(i)*0.21);
    depthAccum += 1.0/breathe * float(i+1);
    scale *= breathe;
  }
  return p;
}

void main(){
  vec2 uv = (gl_FragCoord.xy - 0.5*uRes) / uRes.y;

  float t = uTime;

  float tA = t*0.063;
  float tB = t*0.0287;
  float tC = t*0.0119;
  float tD = t*0.0411;

  vec2 p = uv;
  float warpAng = atan(p.y,p.x);
  float warpR = length(p);
  warpR = warpR / (1.0 - 0.35*warpR*warpR*sin(tC*1.7));
  p = warpR * vec2(cos(warpAng + 0.15*sin(tB*2.0+warpR*3.0)), sin(warpAng + 0.15*sin(tB*2.0+warpR*3.0)));

  p *= rot(tA*0.4);
  p *= 1.0 + 0.12*sin(tB*1.3);

  float foldDepth;
  vec2 pf = foldSpace(p*1.6, t, foldDepth);

  float cellD = 1e9, edgeGlow = 0.0, idA = 0.0;
  float totalW = 0.0;
  vec3 accumColor = vec3(0.0);

  const int LAYERS = 4;
  for(int L=0; L<LAYERS; L++){
    float fl = float(L);
    float scale = pow(1.8, fl) * 1.15;
    float ang = fl*0.6283 + tD*0.5 + fl*0.31*sin(tC*0.7+fl);
    vec2 q = rot(ang) * pf * scale + fl*vec2(13.1,7.3);
    vec4 v = voronoi(q, t*0.6 + fl*11.0);
    float d = v.x;
    float edge = smoothstep(0.0, 0.09, v.y - v.x);
    float w = 1.0/(fl+1.0);
    totalW += w;

    float k = 0.35;
    float h = clamp(0.5 + 0.5*(cellD - d)/k, 0.0, 1.0);
    cellD = mix(cellD, d, h) - k*h*(1.0-h);

    edgeGlow += (1.0-edge) * w;
    idA += v.z * w;
  }
  edgeGlow /= totalW;
  idA /= totalW;

  vec2 rdCoord = pf*0.9 + vec2(tB*0.7, -tC*0.5);
  float rd = fbm(rdCoord*1.3 + foldDepth*0.15);
  float rd2 = fbm(rdCoord*2.7 - foldDepth*0.1 + 4.0);
  float organic = smoothstep(0.25, 0.85, rd*0.6 + rd2*0.4);

  float micro = fbm(pf*9.0 + idA*7.0 + t*0.05);
  micro = pow(micro, 2.0);

  float wall = cellD - 0.04 - 0.05*organic;
  float aa = 1.5 / uRes.y * 200.0 * fwidth(wall) + 0.002;
  float solid = 1.0 - smoothstep(0.0, aa, wall);

  float eps = 0.0025;
  float dx = cellD - voronoi(rot(0.0)*pf*1.15, t*0.6).x;
  vec2 grad = vec2(
    dFdx(cellD)*uRes.x,
    dFdy(cellD)*uRes.y
  );
  grad = normalize(grad + 1e-5);
  vec3 normal = normalize(vec3(grad*1.4, 1.0));
  vec3 lightDir = normalize(vec3(0.4*sin(tA*2.0), 0.5, 0.8));
  float diff = clamp(dot(normal, lightDir), 0.0, 1.0);
  float spec = pow(diff, 24.0);

  float hueBase = fract(tD*0.5 + idA*0.7 + foldDepth*0.04);
  vec3 col;
  {
    vec3 a3 = vec3(0.55, 0.5, 0.55);
    vec3 b3 = vec3(0.45, 0.45, 0.5);
    vec3 c3 = vec3(1.0, 1.0, 1.2);
    vec3 d3 = vec3(0.30 + 0.4*sin(tC*0.3), 0.55, 0.9 + 0.3*cos(tB*0.2));
    col = a3 + b3*cos(TAU*(c3*hueBase + d3));
  }

  vec3 base = col * (0.25 + 0.95*diff) + vec3(1.0,0.95,0.85)*spec*0.6;

  vec3 edgeColor = mix(vec3(0.05,0.02,0.08), vec3(1.0,0.85,0.55), 0.6+0.4*sin(tD*3.0+idA*6.0));
  base = mix(base, edgeColor, pow(edgeGlow,3.5)*0.85);

  base *= (0.85 + 0.3*micro);

  base *= 0.7 + 0.6*organic;

  vec3 bg = mix(vec3(0.01,0.01,0.03), vec3(0.05,0.02,0.08), 0.5+0.5*sin(tC*0.6+length(uv)));
  bg += 0.04*vec3(0.3,0.5,1.0)*fbm(uv*3.0+tA);

  vec3 finalColor = mix(bg, base, solid);

  float glow = pow(edgeGlow,2.0) * solid;
  finalColor += edgeColor * glow * 0.35;
  finalColor += vec3(0.7,0.85,1.0) * spec * solid * 0.4;

  float vig = smoothstep(1.3, 0.2, length(uv*vec2(1.0, uRes.y/uRes.x)));
  finalColor *= mix(0.55, 1.05, vig);

  finalColor = finalColor / (1.0 + finalColor*0.6);
  finalColor = pow(finalColor, vec3(0.85));

  gl_FragColor = vec4(finalColor, 1.0);
}
`;

let activeFragSrc = fragSrc;
if (!derivExt){
  activeFragSrc = fragSrc
    .replace('#extension GL_OES_standard_derivatives : enable', '')
    .replace('fwidth(wall)', '0.0008')
    .replace('dFdx(cellD)*uRes.x,', '0.001*uRes.x,')
    .replace('dFdy(cellD)*uRes.y', '0.001*uRes.y');
}

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 prog = gl.createProgram();
gl.attachShader(prog, compile(gl.VERTEX_SHADER, vertSrc));
gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, activeFragSrc));
gl.linkProgram(prog);
if (!gl.getProgramParameter(prog, gl.LINK_STATUS)){
  console.error(gl.getProgramInfoLog(prog));
}
gl.useProgram(prog);

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

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

let startTime = performance.now();

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