• 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

TORUS KNOT - RETROSCOPIC CRT SHADER SOURCE CODE

<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>Geometry Shader</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body{width:100%;height:100vh;background:#000;overflow:hidden}
canvas{
    display:block;
    width:100vw; 
    height:100vh; 
    filter: grayscale(1) sepia(1) hue-rotate(80deg) saturate(5) brightness(1.15) contrast(1.2);
    position: relative;
    z-index: 2;
}

/* Retroscopic Overlay */
#scope {
    position: fixed;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    display: block;
}
#scope>div {
    position: absolute;
    inset: 0;
}
#scope .glow {
    background: radial-gradient(circle at 50% 46%, rgba(30, 255, 130, .08), rgba(0, 0, 0, 0) 62%);
    animation: nvflick 3.6s steps(24) infinite;
}
#scope .scan {
    background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0) 0 2px, rgba(0, 25, 8, .45) 2px 3px);
    animation: nvscan 7s linear infinite;
}
#scope .vig {
    box-shadow: inset 0 0 200px 50px rgba(0, 0, 0, .8);
}
#scope .mask {
    background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0) 54%, rgba(0, 0, 0, .5) 72%, rgba(0, 0, 0, .94) 100%);
}
@keyframes nvscan {
    from { background-position: 0 0; }
    to { background-position: 0 240px; }
}
@keyframes nvflick {
    0%, 100% { opacity: .55; }
    40% { opacity: .9; }
    62% { opacity: .4; }
    80% { opacity: .75; }
}
</style>
</head><body>
<div id="scope">
    <div class="glow"></div>
    <div class="scan"></div>
    <div class="vig"></div>
    <div class="mask"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<script>
const U = v => ({ value: v });

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000);
scene.fog = new THREE.FogExp2(0x000000, 0.018);

const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 200);
camera.position.set(0, 4, 14);
camera.lookAt(0, 0, 0);
camera.zoom = 3;
camera.updateProjectionMatrix();

const vertShader = `
  precision highp float;
  varying vec3 vPos;
  varying vec3 vNormal;
  varying vec2 vUv;
  varying float vDepth;
  void main(){
    vPos    = (modelMatrix * vec4(position,1.0)).xyz;
    vNormal = normalize(normalMatrix * normal);
    vUv     = uv;
    vec4 mvPos = modelViewMatrix * vec4(position,1.0);
    vDepth  = -mvPos.z;
    gl_Position = projectionMatrix * mvPos;
  }
`;

const fragShader = `
  precision highp float;
  uniform float time;
  uniform float hueOff;
  uniform float hueScale;
  uniform float brightness;
  uniform float satMult;
  uniform float foldStr;
  uniform float foldOff;
  uniform float foldBX;
  uniform float foldBY;
  uniform float foldBZ;
  uniform float modScale;
  uniform float sLogMul;
  uniform float sInit;
  uniform float minFold;
  uniform int   innerIter;
  varying vec3  vPos;
  varying vec3  vNormal;
  varying vec2  vUv;
  varying float vDepth;

  #define MAX_INNER 40

  vec3 hsv(float h, float s, float v){
    vec4 K = vec4(1.,2./3.,1./3.,3.);
    vec3 p = abs(fract(vec3(h)+K.xyz)*6.-K.www);
    return v*mix(K.xxx,clamp(p-K.xxx,0.,1.),s);
  }

  void main(){
    float g = 0.0;
    vec3 p = vec3(vUv * 2.0 - 1.0, vDepth * 0.05 + time * 0.08);
    p += vNormal * 0.3;
    float s = sInit;
    float e = 0.0;
    for(int ii = 0; ii < MAX_INNER; ii++){
      if(ii >= innerIter) break;
      s *= e = max(minFold, foldStr / dot(p,p));
      p  = vec3(foldBX,foldBY,foldBZ) - abs(abs(p)*e - vec3(foldOff));
    }
    g += mod(length(p.xz), p.y * modScale) / s;
    float ls = log(s * sLogMul);
    vec3 col = hsv(hueOff + g * hueScale, p.z * satMult, ls / brightness);

    float rim = pow(1.0 - abs(dot(vNormal, vec3(0.0,0.0,1.0))), 2.2);
    col += rim * hsv(hueOff + 0.5, 0.8, 0.4);
    col = mix(col, col * 0.3, smoothstep(8.0, 20.0, vDepth));

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

function makeMat() {
  return new THREE.ShaderMaterial({
    uniforms: {
      time:       U(0.0),
      sInit:      U(0.7),
      minFold:    U(1.2),
      foldStr:    U(15.0),
      foldOff:    U(3.5),
      foldBX:     U(0.65),
      foldBY:     U(1.40),
      foldBZ:     U(2.00),
      modScale:   U(7.00),
      sLogMul:    U(0.2),
      brightness: U(440.0),
      hueOff:     U(0.78),
      hueScale:   U(0.55),
      satMult:    U(0.50),
      innerIter:  U(3),
    },
    vertexShader: vertShader,
    fragmentShader: fragShader,
    side: THREE.DoubleSide,
  });
}

const objects = [];

const torusKnotGeo = new THREE.TorusKnotGeometry(2.2, 0.55, 180, 32, 2, 3);
const torusKnotMat = makeMat();
const torusKnot = new THREE.Mesh(torusKnotGeo, torusKnotMat);
torusKnot.position.set(0, 0, 0);
scene.add(torusKnot);
objects.push({ mesh: torusKnot, mat: torusKnotMat, spin: new THREE.Vector3(0.003, 0.007, 0.001) });

const icoGeo = new THREE.IcosahedronGeometry(1.1, 4);
const icoMat = makeMat();
const ico = new THREE.Mesh(icoGeo, icoMat);
ico.position.set(-5.5, 1.5, -2);
scene.add(ico);
objects.push({ mesh: ico, mat: icoMat, spin: new THREE.Vector3(0.005, 0.003, 0.006) });

const ico2Mat = makeMat();
const ico2 = new THREE.Mesh(new THREE.IcosahedronGeometry(0.75, 4), ico2Mat);
ico2.position.set(5.5, -1.2, -1);
scene.add(ico2);
objects.push({ mesh: ico2, mat: ico2Mat, spin: new THREE.Vector3(-0.004, 0.006, 0.002) });

const ringGeo = new THREE.TorusGeometry(3.8, 0.22, 24, 120);
const ringMat = makeMat();
const ring = new THREE.Mesh(ringGeo, ringMat);
ring.rotation.x = Math.PI * 0.35;
ring.position.set(0, 0, -1);
scene.add(ring);
objects.push({ mesh: ring, mat: ringMat, spin: new THREE.Vector3(0.001, 0.004, 0.0015) });

const ring2Geo = new THREE.TorusGeometry(6.5, 0.12, 16, 140);
const ring2Mat = makeMat();
const ring2 = new THREE.Mesh(ring2Geo, ring2Mat);
ring2.rotation.x = Math.PI * 0.55;
ring2.rotation.z = Math.PI * 0.2;
ring2.position.set(0, 0, 0);
scene.add(ring2);
objects.push({ mesh: ring2, mat: ring2Mat, spin: new THREE.Vector3(-0.0008, 0.002, 0.001) });

const octGeo = new THREE.OctahedronGeometry(0.9, 2);
const octMat = makeMat();
const oct = new THREE.Mesh(octGeo, octMat);
oct.position.set(2.5, 3.2, 1);
scene.add(oct);
objects.push({ mesh: oct, mat: octMat, spin: new THREE.Vector3(0.007, -0.003, 0.005) });

const dodGeo = new THREE.DodecahedronGeometry(0.7, 1);
const dodMat = makeMat();
const dod = new THREE.Mesh(dodGeo, dodMat);
dod.position.set(-2.8, -3.0, 0.5);
scene.add(dod);
objects.push({ mesh: dod, mat: dodMat, spin: new THREE.Vector3(-0.005, 0.004, -0.003) });

const ANIM_DEFS = [
  { key: 'foldBX',    min: 0.40, max: 1.80, freq: 0.5,  phase: 0.00 },
  { key: 'foldBY',    min: 0.80, max: 2.40, freq: 0.4,  phase: 2.09 },
  { key: 'foldOff',   min: 2.50, max: 5.00, freq: 0.5,  phase: 3.14 },
  { key: 'hueOff',    min: 0.00, max: 1.00, freq: 0.45, phase: 4.19 },
  { key: 'hueScale',  min: 0.30, max: 1.80, freq: 0.15, phase: 5.24 },
  { key: 'foldStr',   min: 8.0,  max: 22.0, freq: 0.35, phase: 1.57 },
  { key: 'brightness',min: 200,  max: 600,  freq: 0.2,  phase: 0.80 },
];

const ANIM_SPEED = 0.025;
const clock = new THREE.Clock();
let animTime = 0;

const camRadius = 13;
let camAngle = 0;

(function animate(){
  requestAnimationFrame(animate);
  const delta = clock.getDelta();
  animTime += delta * ANIM_SPEED;
  const elapsed = clock.elapsedTime;

  camAngle += delta * 0.08;
  camera.position.x = Math.sin(camAngle) * camRadius;
  camera.position.z = Math.cos(camAngle) * camRadius;
  camera.position.y = Math.sin(camAngle * 0.4) * 3 + 3;
  camera.lookAt(0, 0, 0);

  const unifVals = {};
  for (const def of ANIM_DEFS) {
    const t = (Math.sin(animTime * def.freq * Math.PI * 2 + def.phase) + 1) / 2;
    unifVals[def.key] = def.min + (def.max - def.min) * t;
  }

  for (const obj of objects) {
    obj.mesh.rotation.x += obj.spin.x;
    obj.mesh.rotation.y += obj.spin.y;
    obj.mesh.rotation.z += obj.spin.z;
    obj.mat.uniforms.time.value = elapsed;
    for (const k in unifVals) {
      if (obj.mat.uniforms[k]) obj.mat.uniforms[k].value = unifVals[k];
    }
  }

  renderer.render(scene, camera);
})();

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body></html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.