• 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

LATENT - WEBGL CATHEDRAL PROCEDURAL SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Latent Cathedral</title>
<style>
  html,body{margin:0;padding:0;overflow:hidden;background:#000;width:100%;height:100%;cursor:crosshair;}
  canvas{display:block;width:100vw;height:100vh;}
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const gl = canvas.getContext('webgl2', {antialias:false, alpha:false, powerPreference:'high-performance'});
if(!gl){ document.body.innerHTML = '<p style="color:#fff;font-family:sans-serif;padding:40px">WebGL2 not available.</p>'; }

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

const vertSrc = `#version 300 es
precision highp float;
const vec2 verts[3] = vec2[3](vec2(-1.,-1.), vec2(3.,-1.), vec2(-1.,3.));
void main(){ gl_Position = vec4(verts[gl_VertexID],0.,1.); }
`;

const fragSrc = `#version 300 es
precision highp float;
out vec4 outColor;

uniform vec2 uRes;
uniform float uTime;
uniform vec2 uMouse;
uniform vec2 uMouseVel;
uniform float uGrowth;
uniform float uBloomCount;
uniform vec4 uClicks[4];
uniform int uNumClicks;
uniform float uSeed;

#define PI 3.14159265359
#define TAU 6.28318530718

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

vec3 hash3(vec3 p){
  p = vec3( dot(p,vec3(127.1,311.7,74.7)),
            dot(p,vec3(269.5,183.3,246.1)),
            dot(p,vec3(113.5,271.9,124.6)) );
  return -1.0 + 2.0*fract(sin(p)*43758.5453123);
}

float hash1(float n){ return fract(sin(n)*43758.5453123); }

vec4 qFromAxisAngle(vec3 axis, float angle){
  return vec4(axis*sin(angle*0.5), cos(angle*0.5));
}
vec3 qRotate(vec4 q, vec3 v){
  vec3 t = 2.0*cross(q.xyz, v);
  return v + q.w*t + cross(q.xyz, t);
}

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);
}
float smax(float a, float b, float k){
  return -smin(-a,-b,k);
}

vec3 fold(vec3 p, float t, out float scaleAcc){
  scaleAcc = 1.0;
  float growth = uGrowth;
  for(int i=0;i<4;i++){
    p = abs(p) - (0.9 + 0.08*sin(t*0.07 + float(i)*1.3 + growth*0.13));
    float a = t*0.05 + float(i)*0.6 + growth*0.02;
    p.xy *= rot2(a);
    p.yz *= rot2(a*0.7 + sin(t*0.03+float(i))*0.2);
    float r2 = dot(p,p);
    float k = 1.3 / max(r2, 0.25);
    p *= k;
    scaleAcc *= k;
    p -= 0.15*sin(p.zxy*1.7 + t*0.2);
  }
  return p;
}

vec3 mouseWarp(vec3 p, vec3 rd){
  vec3 mdir = normalize(vec3(uMouse*1.4, 1.0));
  float d = length(p - mdir*dot(p,mdir));
  float influence = exp(-d*d*0.9) * (0.6 + 1.8*length(uMouseVel));
  vec3 pull = normalize(mdir*dot(p,mdir) - p + 1e-4);
  p += pull*influence*0.35;
  return p;
}

float clickField(vec3 p, out vec3 glowCol){
  float total = 0.0;
  glowCol = vec3(0.0);
  for(int i=0;i<4;i++){
    if(i>=uNumClicks) break;
    vec4 c = uClicks[i];
    float age = uTime - c.w;
    if(age < 0.0 || age > 14.0) continue;
    vec3 cpos = normalize(c.xyz) * (3.2 + 1.4*sin(c.w*3.1));
    float wave = age*2.6 - length(p-cpos);
    float ring = exp(-wave*wave*0.6) * exp(-age*0.18);
    total += ring;
    vec3 hue = 0.5 + 0.5*cos(TAU*(vec3(0.1,0.4,0.7) + c.w*0.21));
    glowCol += hue * ring;
  }
  return total;
}

float globalClickDisp(vec3 p){
  float d = 0.0;
  for(int i=0;i<4;i++){
    if(i>=uNumClicks) break;
    vec4 c = uClicks[i];
    float age = uTime - c.w;
    if(age<0.0||age>14.0) continue;
    vec3 cpos = normalize(c.xyz) * (3.2 + 1.4*sin(c.w*3.1));
    float wave = age*2.6 - length(p-cpos);
    d += exp(-wave*wave*0.6) * exp(-age*0.18) * 0.5;
  }
  return d;
}

float sdScene(vec3 p, float t, out vec3 matInfo){
  vec3 op = p;
  p = mouseWarp(p, vec3(0.));
  float disp = globalClickDisp(p);
  p += disp*0.4*normalize(p+1e-4);

  float sc;
  vec3 q = fold(p, t, sc);
  float baseR = length(q) - (1.0 + 0.15*sin(t*0.3));
  float d = baseR / sc;

  float shell = abs(d) - 0.015 - 0.01*uGrowth*0.05;

  vec3 vp = op*1.6;
  float veinPhase = t*0.15 + uGrowth*0.4;
  float veins = 0.0;
  {
    vec3 vq = vp;
    float vs = 1.0;
    for(int i=0;i<3;i++){
      vq = abs(vq) - 0.6;
      vq.xy *= rot2(veinPhase*0.3 + float(i));
      float r2 = dot(vq,vq);
      float k = 1.1/max(r2,0.3);
      vq *= k; vs *= k;
    }
    veins = (length(vq)-0.4)/vs;
  }
  float veinShell = abs(veins) - 0.006;
  float veinAmount = smoothstep(0.0, 8.0, uGrowth);
  float combined = smin(shell, veinShell, 0.12 + 0.08*veinAmount);

  matInfo = vec3(sc, veinAmount, disp);
  return combined*0.62;
}

float mapDist(vec3 p, float t){
  vec3 m;
  return sdScene(p,t,m);
}

vec3 calcNormal(vec3 p, float t){
  float eps = 0.0015;
  vec2 e = vec2(1.0,-1.0)*eps;
  return normalize(
    e.xyy*mapDist(p+e.xyy,t) +
    e.yyx*mapDist(p+e.yyx,t) +
    e.yxy*mapDist(p+e.yxy,t) +
    e.xxx*mapDist(p+e.xxx,t)
  );
}

float calcAO(vec3 p, vec3 n, float t){
  float occ=0.0, sca=1.0;
  for(int i=0;i<3;i++){
    float h = 0.02 + 0.12*float(i)/2.0;
    float d = mapDist(p+n*h, t);
    occ += (h-d)*sca;
    sca *= 0.7;
  }
  return clamp(1.0-1.6*occ,0.0,1.0);
}

vec3 spectralShade(vec3 n, vec3 rd, vec3 lightDir, float fres, vec3 base){
  float ndl = dot(n,lightDir);
  float wrap = 0.5+0.5*ndl;
  float spec = pow(clamp(dot(reflect(-lightDir,n), -rd),0.0,1.0), 24.0);
  vec3 iridescence = 0.5+0.5*cos(TAU*(fres*1.8 + vec3(0.0,0.33,0.67) + base.x*0.4));
  vec3 col = base*wrap*0.6 + iridescence*fres*1.4 + spec*vec3(1.0,0.95,0.85)*0.8;
  return col;
}

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

  float orbit = t*0.045;
  float radius = 3.6 + 0.5*sin(t*0.07) + 0.15*sin(uGrowth*0.3);
  vec3 camPos = vec3(
    radius*cos(orbit + uMouse.x*0.25),
    1.1*sin(t*0.025) + uMouse.y*0.35,
    radius*sin(orbit + uMouse.x*0.25)
  );
  vec3 lookAt = vec3(sin(t*0.013)*0.4, cos(t*0.017)*0.3, 0.0);

  vec3 fwd = normalize(lookAt-camPos);
  vec3 right = normalize(cross(fwd, vec3(0.0,1.0,0.0)));
  vec3 up = cross(right, fwd);
  float fovScale = 1.1;
  vec3 rd = normalize(fwd + uv.x*right*fovScale + uv.y*up*fovScale);

  vec3 ro = camPos;

  float dist = 0.0;
  float totalDist = 0.0;
  vec3 matInfo;
  bool hit = false;
  vec3 p = ro;
  const int STEPS = 70;
  for(int i=0;i<STEPS;i++){
    p = ro + rd*totalDist;
    dist = sdScene(p, t, matInfo);
    if(dist < 0.0015){ hit = true; break; }
    totalDist += dist*0.9;
    if(totalDist > 14.0) break;
  }

  vec3 bgTop = vec3(0.02,0.015,0.04);
  vec3 bgBot = vec3(0.005,0.01,0.025);
  vec3 bg = mix(bgBot, bgTop, 0.5+0.5*rd.y);
  vec3 glowDummy;
  float farClickGlow = clickField(ro+rd*6.0, glowDummy);
  bg += glowDummy*0.15;

  vec3 col = bg;

  if(hit){
    vec3 n = calcNormal(p, t);
    float ao = calcAO(p, n, t);

    vec3 lightDir1 = normalize(vec3(0.4,0.8,0.3));
    float fres = pow(1.0-clamp(dot(n,-rd),0.0,1.0), 3.0);

    vec3 baseHue = 0.5+0.5*cos(TAU*(vec3(0.05,0.25,0.5) + matInfo.x*0.02 + uGrowth*0.01));
    vec3 shaded = spectralShade(n, rd, lightDir1, fres, baseHue);

    float emissive = smoothstep(0.0,1.0,matInfo.y) * (0.5+0.5*sin(t*1.3 + matInfo.x*5.0));
    vec3 emCol = vec3(0.9,0.6,1.0)*emissive*1.4;

    vec3 cglow;
    float cf = clickField(p, cglow);
    shaded += cglow*1.6;

    col = shaded*ao + emCol*ao;

    float fog = 1.0 - exp(-totalDist*0.085);
    col = mix(col, bg*1.3, fog*0.85);

    col += vec3(0.3,0.5,0.9) * fres * 0.25 * ao;
  } else {
    float shaft = pow(max(dot(rd, normalize(vec3(0.4,0.8,0.3))),0.0), 8.0);
    col += vec3(0.5,0.4,0.8)*shaft*0.12;
  }

  float vig = smoothstep(1.1, 0.2, length(uv));
  col *= mix(0.6,1.05,vig);

  col = col / (1.0+col);
  col = pow(col, vec3(0.85));

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

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

const uRes = gl.getUniformLocation(prog,'uRes');
const uTime = gl.getUniformLocation(prog,'uTime');
const uMouse = gl.getUniformLocation(prog,'uMouse');
const uMouseVel = gl.getUniformLocation(prog,'uMouseVel');
const uGrowth = gl.getUniformLocation(prog,'uGrowth');
const uClicks = gl.getUniformLocation(prog,'uClicks');
const uNumClicks = gl.getUniformLocation(prog,'uNumClicks');
const uSeed = gl.getUniformLocation(prog,'uSeed');

let mouse = [0,0];
let mouseTarget = [0,0];
let mouseVel = [0,0];
let lastMouse = [0,0];

window.addEventListener('mousemove', (e)=>{
  const nx = (e.clientX/window.innerWidth)*2-1;
  const ny = -((e.clientY/window.innerHeight)*2-1);
  mouseTarget = [nx, ny];
});
window.addEventListener('touchmove', (e)=>{
  if(e.touches.length>0){
    const t0 = e.touches[0];
    const nx = (t0.clientX/window.innerWidth)*2-1;
    const ny = -((t0.clientY/window.innerHeight)*2-1);
    mouseTarget = [nx, ny];
  }
}, {passive:true});

let growth = 0;
let bloomCount = 0;
const MAXCLICKS = 4;
let clicks = [];

function addClick(nx, ny){
  const z = 1.0;
  let v = [nx*1.3, ny*1.3, z];
  const len = Math.hypot(v[0],v[1],v[2])||1;
  v = [v[0]/len, v[1]/len, v[2]/len];
  clicks.push({x:v[0], y:v[1], z:v[2], w: performance.now()/1000 - startTime});
  if(clicks.length > MAXCLICKS) clicks.shift();
  growth += 1.0;
  bloomCount += 1;
}

window.addEventListener('mousedown', (e)=>{
  const nx = (e.clientX/window.innerWidth)*2-1;
  const ny = -((e.clientY/window.innerHeight)*2-1);
  addClick(nx, ny);
});
window.addEventListener('touchstart', (e)=>{
  if(e.touches.length>0){
    const t0 = e.touches[0];
    const nx = (t0.clientX/window.innerWidth)*2-1;
    const ny = -((t0.clientY/window.innerHeight)*2-1);
    addClick(nx, ny);
  }
}, {passive:true});

const startTime = performance.now()/1000;

function render(){
  const now = performance.now()/1000 - startTime;

  const lerp = 0.08;
  mouse[0] += (mouseTarget[0]-mouse[0])*lerp;
  mouse[1] += (mouseTarget[1]-mouse[1])*lerp;
  mouseVel[0] = (mouse[0]-lastMouse[0])*8.0;
  mouseVel[1] = (mouse[1]-lastMouse[1])*8.0;
  lastMouse = [mouse[0], mouse[1]];

  growth += 0.0015;

  gl.useProgram(prog);
  gl.uniform2f(uRes, canvas.width, canvas.height);
  gl.uniform1f(uTime, now);
  gl.uniform2f(uMouse, mouse[0], mouse[1]);
  gl.uniform2f(uMouseVel, mouseVel[0], mouseVel[1]);
  gl.uniform1f(uGrowth, growth);
  gl.uniform1f(uSeed, 1.0);

  const flatClicks = new Float32Array(MAXCLICKS*4);
  for(let i=0;i<clicks.length;i++){
    const c = clicks[i];
    flatClicks[i*4+0]=c.x; flatClicks[i*4+1]=c.y; flatClicks[i*4+2]=c.z; flatClicks[i*4+3]=c.w;
  }
  gl.uniform4fv(uClicks, flatClicks);
  gl.uniform1i(uNumClicks, clicks.length);

  gl.drawArrays(gl.TRIANGLES, 0, 3);

  requestAnimationFrame(render);
}
render();
</script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.