Skip to content

Instantly share code, notes, and snippets.

@Kvnbbg
Last active September 1, 2026 09:41
Show Gist options
  • Select an option

  • Save Kvnbbg/81fefc8fb221d23d6c94af948cda2e64 to your computer and use it in GitHub Desktop.

Select an option

Save Kvnbbg/81fefc8fb221d23d6c94af948cda2e64 to your computer and use it in GitHub Desktop.
Division By Zero VISUALS

Division By Zero VISUALS

Interactive singularity scene featuring smooth state transitions from calm accretion flow to turbulence and relativistic collapse. Built with JavaScript, Three.js, GLSL shaders, and GSAP.

A Pen by Kevin Marville on CodePen.

License.

Source.

<style>
body {
margin: 0;
overflow: hidden;
background-color: #010103;
font-family: 'Inter', -apple-system, sans-serif;
color: #fff;
}
canvas {
display: block;
}
#overlay {
position: absolute;
inset: 0;
pointer-events: none;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 40px;
background: radial-gradient(circle at center, transparent 30%, rgba(0, 0, 0, 0.5) 100%);
z-index: 10;
}
.header {
text-align: center;
}
.title {
font-size: 1.2rem;
letter-spacing: 0.8em;
text-transform: uppercase;
color: #fff;
margin-bottom: 12px;
font-weight: 300;
opacity: 0.9;
}
.status-pill {
display: inline-block;
padding: 6px 20px;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 30px;
font-size: 0.65rem;
letter-spacing: 0.25em;
text-transform: uppercase;
transition: all 1.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.hud-bottom {
display: flex;
justify-content: space-between;
align-items: flex-end;
font-family: 'JetBrains Mono', monospace;
font-size: 0.7rem;
opacity: 0.6;
letter-spacing: 1px;
}
.metric {
margin-bottom: 6px;
}
.val {
color: #00f3ff;
font-weight: bold;
transition: color 1.5s ease;
}
#vignette {
position: fixed;
inset: 0;
background: radial-gradient(circle, transparent 50%, black 150%);
pointer-events: none;
z-index: 5;
}
</style>
<script async src="https://unpkg.com/es-module-shims@1.8.0/dist/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/",
"gsap": "https://unpkg.com/gsap@3.12.5/index.js"
}
}
</script>
<div id="vignette"></div>
<div id="overlay">
<div class="header">
<div class="title" id="main-title">Stable Singularity</div>
<div class="status-pill" id="status-text">Topology: Nominal</div>
</div>
<div class="hud-bottom">
<div>
<div class="metric">MASS_INDEX: <span class="val">4.2M SOL</span></div>
<div class="metric">LENSING: <span class="val" id="lensing-val">SCHWARZSCHILD</span></div>
</div>
<div style="text-align: right">
<div class="metric">RELATIVITY: <span class="val" id="vel-val">0.45c</span></div>
<div class="metric">RADIATION: <span class="val">DETECTION ON</span></div>
</div>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import {
OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import gsap from 'gsap';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(60, 30, 60);
const renderer = new THREE.WebGLRenderer({
antialias: true,
powerPreference: "high-performance"
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.6;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.03;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.4;
const noiseChunk = `
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0) ;
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy) );
vec3 x0 = v - i + dot(i, C.xxx) ;
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min( g.xyz, l.zxy );
vec3 i2 = max( g.xyz, l.zxy );
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i);
vec4 p = permute( permute( permute( i.z + vec4(0.0, i1.z, i2.z, 1.0 )) + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_ );
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4( x.xy, y.xy );
vec4 b1 = vec4( x.zw, y.zw );
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
vec3 p0 = vec3(a0.xy,h.x);
vec3 p1 = vec3(a0.zw,h.y);
vec3 p2 = vec3(a1.xy,h.z);
vec3 p3 = vec3(a1.zw,h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3) ) );
}
`;
const coreGroup = new THREE.Group();
scene.add(coreGroup);
const bhMat = new THREE.MeshBasicMaterial({
color: 0x000000
});
const bhGeo = new THREE.SphereGeometry(4, 64, 64);
coreGroup.add(new THREE.Mesh(bhGeo, bhMat));
const auraMat = new THREE.ShaderMaterial({
uniforms: {
uTime: {
value: 0
},
uIntensity: {
value: 1.0
}
},
vertexShader: `
varying vec3 vNormal;
varying vec3 vView;
void main() {
vNormal = normalize(normalMatrix * normal);
vView = normalize(-(modelViewMatrix * vec4(position, 1.0)).xyz);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
uniform float uIntensity;
varying vec3 vNormal;
varying vec3 vView;
void main() {
float rim = pow(1.0 - max(dot(vNormal, vView), 0.0), 4.0);
gl_FragColor = vec4(vec3(1.0, 0.45, 0.1) * rim * uIntensity * 5.0, 1.0);
}
`,
side: THREE.BackSide,
transparent: true,
blending: THREE.AdditiveBlending
});
coreGroup.add(new THREE.Mesh(new THREE.SphereGeometry(4.25, 64, 64), auraMat));
const instanceCount = 5000;
const streakGeo = new THREE.CylinderGeometry(0.01, 0.12, 2.2, 3);
streakGeo.rotateX(Math.PI / 2);
const diskMaterial = new THREE.ShaderMaterial({
uniforms: {
uTime: {
value: 0
},
uMorph: {
value: 0.1
},
uCompression: {
value: 1.0
},
uIntensity: {
value: 1.0
},
uOrbitScale: {
value: 1.0
}
},
vertexShader: `
${noiseChunk}
uniform float uTime;
uniform float uMorph;
uniform float uCompression;
uniform float uIntensity;
uniform float uOrbitScale;
varying vec3 vColor;
varying float vOpacity;
void main() {
vec4 instPos = instanceMatrix * vec4(0.0, 0.0, 0.0, 1.0);
float rOriginal = length(instPos.xz);
float r = rOriginal * uCompression;
float initialAngle = atan(instPos.z, instPos.x);
float orbitalVelocity = (1.5 / sqrt(rOriginal)) * uOrbitScale;
float currentAngle = initialAngle + (uTime * orbitalVelocity);
vec3 morphedWorldPos = vec3(cos(currentAngle) * r, instPos.y, sin(currentAngle) * r);
float noise = snoise(vec3(morphedWorldPos.x * 0.08, morphedWorldPos.z * 0.08, uTime * 0.3));
morphedWorldPos.y += noise * uMorph * 4.0;
vec3 viewDir = normalize(cameraPosition - morphedWorldPos);
vec3 orbitDir = normalize(vec3(-sin(currentAngle), 0.0, cos(currentAngle)));
float doppler = dot(orbitDir, viewDir);
vec3 hot = vec3(1.0, 0.95, 0.9);
vec3 warm = vec3(1.0, 0.45, 0.1);
vec3 cool = vec3(0.1, 0.35, 1.0);
vec3 color = mix(cool, warm, smoothstep(45.0, 12.0, r));
color = mix(color, hot, smoothstep(10.0, 4.0, r));
vColor = color * (1.3 + doppler * 0.7) * uIntensity;
vOpacity = (smoothstep(3.8, 5.5, r) * (1.0 - smoothstep(38.0, 48.0, r))) * 0.8;
float deltaAngle = currentAngle - initialAngle;
float c = cos(deltaAngle);
float s = sin(deltaAngle);
mat3 rotY = mat3(
c, 0, s,
0, 1, 0,
-s, 0, c
);
vec3 localPos = (instanceMatrix * vec4(position, 0.0)).xyz;
vec3 rotatedLocalPos = rotY * localPos;
gl_Position = projectionMatrix * viewMatrix * vec4(morphedWorldPos + rotatedLocalPos, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
varying float vOpacity;
void main() {
gl_FragColor = vec4(vColor, vOpacity);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const instancedDisk = new THREE.InstancedMesh(streakGeo, diskMaterial, instanceCount);
const dummy = new THREE.Object3D();
for (let i = 0; i < instanceCount; i++) {
const r = 5 + Math.pow(Math.random(), 1.3) * 40;
const angle = Math.random() * Math.PI * 2;
dummy.position.set(Math.cos(angle) * r, (Math.random() - 0.5) * (8 / r), Math.sin(angle) * r);
dummy.lookAt(dummy.position.x + Math.sin(angle), dummy.position.y, dummy.position.z - Math.cos(angle));
dummy.updateMatrix();
instancedDisk.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedDisk);
const config = [{
title: "Stable Singularity",
status: "Topology: Nominal",
morph: 0.1,
compress: 1.0,
intensity: 1.0,
rotate: 0.4,
camY: 25,
camDist: 85,
orbit: 1.0,
color: "#00f3ff",
vel: "0.45c"
},
{
title: "Accretion Turbulence",
status: "Topology: Fluctuating",
morph: 4.5,
compress: 1.15,
intensity: 1.4,
rotate: 1.5,
camY: 45,
camDist: 95,
orbit: 1.8,
color: "#ffaa00",
vel: "0.78c"
},
{
title: "Relativistic Collapse",
status: "Topology: Critical",
morph: 0.8,
compress: 0.38,
intensity: 3.5,
rotate: 5.0,
camY: 12,
camDist: 55,
orbit: 4.5,
color: "#ff0044",
vel: "0.99c"
}
];
let stateIdx = 0;
const mainTitle = document.getElementById('main-title');
const statusText = document.getElementById('status-text');
const velVal = document.getElementById('vel-val');
const camControl = {
distance: 85
};
function transition() {
stateIdx = (stateIdx + 1) % config.length;
const s = config[stateIdx];
const tl = gsap.timeline({
defaults: {
duration: 4.0,
ease: "power2.inOut"
}
});
tl.to(diskMaterial.uniforms.uMorph, {
value: s.morph
}, 0);
tl.to(diskMaterial.uniforms.uCompression, {
value: s.compress
}, 0);
tl.to(diskMaterial.uniforms.uIntensity, {
value: s.intensity
}, 0);
tl.to(diskMaterial.uniforms.uOrbitScale, {
value: s.orbit
}, 0);
tl.to(auraMat.uniforms.uIntensity, {
value: s.intensity
}, 0);
tl.to(controls, {
autoRotateSpeed: s.rotate
}, 0);
tl.to(camera.position, {
y: s.camY
}, 0);
tl.to(camControl, {
distance: s.camDist
}, 0);
gsap.to([mainTitle, statusText, '.val'], {
opacity: 0,
duration: 0.8,
onComplete: () => {
mainTitle.innerText = s.title;
statusText.innerText = s.status;
statusText.style.color = s.color;
statusText.style.borderColor = s.color;
velVal.innerText = s.vel;
velVal.style.color = s.color;
gsap.to([mainTitle, statusText, '.val'], {
opacity: 1,
duration: 1.2
});
}
});
}
setInterval(transition, 10000);
const clock = new THREE.Clock();
function animate() {
const time = clock.getElapsedTime();
diskMaterial.uniforms.uTime.value = time;
auraMat.uniforms.uTime.value = time;
instancedDisk.rotation.y += 0.0005;
const currentDir = new THREE.Vector3().subVectors(camera.position, controls.target).normalize();
camera.position.x = controls.target.x + currentDir.x * camControl.distance;
camera.position.z = controls.target.z + currentDir.z * camControl.distance;
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment