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>