-
-
Save shricodev/ad6e91b6de8781be28a007d62b5a48d9 to your computer and use it in GitHub Desktop.
Black Hole Simulation (Developed by OpenAI o3 Pro Model) - Blog Demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Three.js Black-Hole Shader</title> | |
<style> | |
html, | |
body { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
background: #000; | |
font-family: sans-serif; | |
} | |
#ui { | |
position: fixed; | |
left: 20px; | |
top: 20px; | |
z-index: 10; | |
display: flex; | |
flex-direction: column; | |
gap: 8px; | |
} | |
button { | |
padding: 6px 14px; | |
border: none; | |
border-radius: 4px; | |
background: #111; | |
color: #fff; | |
cursor: pointer; | |
font-size: 14px; | |
transition: all 0.2s; | |
} | |
button:hover { | |
background: #fff; | |
color: #000; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="ui"> | |
<button id="echoBtn">Disk Echo</button> | |
<button id="themeBtn">Theme : Ice</button> | |
</div> | |
<canvas id="c"></canvas> | |
<!-- Three.js CDN (Corrected URL) --> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/0.153.0/three.min.js"></script> | |
<script> | |
(() => { | |
// ---------- 1. Boiler-plate Three.js ---------- | |
const canvas = document.getElementById("c"); | |
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true }); | |
const scene = new THREE.Scene(); | |
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); // full-screen quad | |
// ---------- 2. Themes ---------- | |
const themes = [ | |
{ | |
name: "Ice", | |
primary: "#4fc1ff", | |
secondary: "#c9f0ff", | |
tertiary: "#ffffff", | |
}, | |
{ | |
name: "Ember", | |
primary: "#ff891c", | |
secondary: "#ffefac", | |
tertiary: "#ff3b00", | |
}, | |
{ | |
name: "UltraViolet", | |
primary: "#ae00ff", | |
secondary: "#ffcbff", | |
tertiary: "#00d4ff", | |
}, | |
]; | |
let themeIndex = 0; | |
function hex2rgb(hex) { | |
const c = parseInt(hex.slice(1), 16); | |
return new THREE.Color( | |
((c >> 16) & 255) / 255, | |
((c >> 8) & 255) / 255, | |
(c & 255) / 255, | |
); | |
} | |
// ---------- 3. Shader uniforms ---------- | |
const uniforms = { | |
uTime: { value: 0 }, | |
uPrimary: { value: hex2rgb(themes[0].primary) }, | |
uSecondary: { value: hex2rgb(themes[0].secondary) }, | |
uTertiary: { value: hex2rgb(themes[0].tertiary) }, | |
uRippleStart: { value: -1000 }, // long ago – means “off” | |
uResolution: { value: new THREE.Vector2() }, | |
}; | |
// ---------- 4. GLSL – vertex & fragment ---------- | |
const vtx = ` | |
varying vec2 vUv; | |
void main(){ | |
vUv = uv; | |
gl_Position = vec4(position,1.0); | |
}`; | |
const frag = ` | |
precision highp float; | |
varying vec2 vUv; | |
uniform float uTime; | |
uniform vec3 uPrimary; | |
uniform vec3 uSecondary; | |
uniform vec3 uTertiary; | |
uniform float uRippleStart; | |
uniform vec2 uResolution; | |
#define PI 3.14159265359 | |
// Very tiny hash-based pseudo-noise for starfield | |
float hash(vec2 p){ | |
p = fract(p*vec2(123.34,456.21)); | |
p += dot(p,p+45.32); | |
return fract(p.x*p.y); | |
} | |
// ------------------------------------------------- | |
void main(){ | |
// Normalized device coords centred at (0,0) | |
vec2 uv = vUv*uResolution / min(uResolution.x,uResolution.y); | |
vec2 cUv = uv - 0.5*uResolution/min(uResolution.x,uResolution.y); | |
float r = length(cUv); | |
// Background – cheap starfield | |
float stars = step(0.997,hash(floor(uv*vec2(3.0,4.0)))); | |
vec3 bg = mix(vec3(0.0), vec3(1.0), stars); | |
// Gravitational lensing swirl (very stylised) | |
float bend = 0.15/r; | |
float ang = atan(cUv.y,cUv.x) + bend; | |
vec2 warped = vec2(cos(ang),sin(ang))*r; | |
// Accretion disk – horizontal band, emissive gradient | |
float disk = smoothstep(0.03,0.0,abs(warped.y)); | |
vec3 diskCol = mix(uPrimary,uSecondary,0.5+0.5*sin(uTime*3.0+r*40.0)); | |
// Event horizon | |
float horizon = smoothstep(0.16,0.18,r); | |
// ------------------------------------------------- | |
vec3 col = mix(bg,diskCol,disk); // add accretion disk | |
col = mix(col,vec3(0.0),horizon); // carve the black hole | |
// Ripple (Disk Echo) ------------------------------------------------- | |
float rippleTime = uTime - uRippleStart; | |
if(rippleTime > 0.0){ | |
float rippleRadius = rippleTime*0.12; // speed | |
float width = 0.015; | |
float rip = smoothstep(width,0.0,abs(r-rippleRadius)); | |
col += uTertiary * rip * (1.0-rippleTime*0.2); // fade out slowly | |
} | |
gl_FragColor = vec4(col,1.0); | |
}`; | |
const material = new THREE.ShaderMaterial({ | |
uniforms, | |
vertexShader: vtx, | |
fragmentShader: frag, | |
}); | |
scene.add(new THREE.Mesh(new THREE.PlaneGeometry(2, 2), material)); | |
// ---------- 5. Resize ---------- | |
function onResize() { | |
renderer.setSize(window.innerWidth, window.innerHeight); | |
uniforms.uResolution.value.set( | |
renderer.domElement.width, | |
renderer.domElement.height, | |
); | |
} | |
window.addEventListener("resize", onResize); | |
onResize(); | |
// ---------- 6. Animation loop ---------- | |
const clock = new THREE.Clock(); | |
function animate() { | |
requestAnimationFrame(animate); | |
uniforms.uTime.value = clock.getElapsedTime(); | |
renderer.render(scene, camera); | |
} | |
animate(); | |
// ---------- 7. UI Controls ---------- | |
const echoBtn = document.getElementById("echoBtn"); | |
const themeBtn = document.getElementById("themeBtn"); | |
echoBtn.addEventListener("click", () => { | |
uniforms.uRippleStart.value = uniforms.uTime.value; // trigger ripple | |
}); | |
themeBtn.addEventListener("click", () => { | |
themeIndex = (themeIndex + 1) % themes.length; | |
const t = themes[themeIndex]; | |
uniforms.uPrimary.value = hex2rgb(t.primary); | |
uniforms.uSecondary.value = hex2rgb(t.secondary); | |
uniforms.uTertiary.value = hex2rgb(t.tertiary); | |
themeBtn.textContent = `Theme : ${t.name}`; | |
}); | |
})(); // IIFE | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment