-
-
Save shricodev/2a87d241dff2a7630f31e1d504b32cd8 to your computer and use it in GitHub Desktop.
Black Hole Animation - Gemini 2.5 Pro - 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> | |
| <title>Black Hole Horizon</title> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" /> | |
| <style> | |
| body { | |
| margin: 0; | |
| background-color: #000000; | |
| color: #fff; | |
| font-family: Monospace; | |
| font-size: 13px; | |
| line-height: 24px; | |
| overscroll-behavior: none; | |
| } | |
| canvas { | |
| display: block; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.160.0/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| <script type="module"> | |
| import * as THREE from 'three'; | |
| import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; | |
| import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; | |
| import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; | |
| import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'; | |
| import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; | |
| let scene, camera, renderer, composer, controls; | |
| let accretionDisk, blackHole, plasmaBeam, stars; | |
| // Shaders | |
| const gravitationalLensingShader = { | |
| uniforms: { | |
| tDiffuse: { value: null }, | |
| 'strength': { value: 0.05 }, // Gravitational lensing strength | |
| 'center': { value: new THREE.Vector2(0.5, 0.5) } | |
| }, | |
| vertexShader: ` | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| uniform sampler2D tDiffuse; | |
| uniform float strength; | |
| uniform vec2 center; | |
| varying vec2 vUv; | |
| void main() { | |
| vec2 toCenter = center - vUv; | |
| float dist = length(toCenter); | |
| vec2 distortedUv = vUv + toCenter * (1.0/dist) * strength * pow(max(0.0, 1.0-dist*2.0), 2.0); | |
| gl_FragColor = texture2D(tDiffuse, distortedUv); | |
| } | |
| ` | |
| }; | |
| const accretionDiskShader = { | |
| uniforms: { | |
| 'time': { value: 1.0 }, | |
| 'color1': { value: new THREE.Color(0xff8c00) }, // Orange | |
| 'color2': { value: new THREE.Color(0x00bfff) } // Blue | |
| }, | |
| vertexShader: ` | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| uniform float time; | |
| uniform vec3 color1; | |
| uniform vec3 color2; | |
| varying vec2 vUv; | |
| float noise(vec2 p) { | |
| return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453); | |
| } | |
| void main() { | |
| float angle = atan(vUv.y - 0.5, vUv.x - 0.5); | |
| float radius = length(vUv - 0.5); | |
| float strength = (sin(angle * 10.0 + time * 2.0) * 0.1 + 0.9); | |
| vec3 color = mix(color1, color2, (angle + 3.1415) / (2.0*3.1415)); | |
| float n = noise(vUv * 20.0 + time * 0.1); | |
| gl_FragColor = vec4(color * strength * (1.0 - radius * 0.1) * (1.0 - n * 0.2), 1.0); | |
| } | |
| ` | |
| }; | |
| const plasmaBeamShader = { | |
| uniforms: { | |
| 'time': { value: 1.0 } | |
| }, | |
| vertexShader: ` | |
| varying vec2 vUv; | |
| void main() { | |
| vUv = uv; | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| uniform float time; | |
| varying vec2 vUv; | |
| void main() { | |
| float t = time * 2.0; | |
| float plasma = sin(vUv.y * 10.0 + t) + sin(vUv.x * 5.0 - t) * 0.5; | |
| vec3 color = mix(vec3(0.0, 0.5, 1.0), vec3(1.0, 0.5, 0.0), smoothstep(0.0, 1.0, vUv.x)); | |
| float intensity = pow(0.05 / abs(vUv.y - 0.5), 1.2); | |
| gl_FragColor = vec4(color * intensity * (sin(vUv.x * 20.0 + time * 5.0) * 0.2 + 0.8), 1.0); | |
| } | |
| ` | |
| }; | |
| init(); | |
| animate(); | |
| function init() { | |
| // Scene | |
| scene = new THREE.Scene(); | |
| // Camera | |
| camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); | |
| camera.position.z = 10; | |
| // Renderer | |
| renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(window.devicePixelRatio); | |
| document.body.appendChild(renderer.domElement); | |
| // Controls | |
| controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; | |
| controls.dampingFactor = 0.05; | |
| controls.screenSpacePanning = false; | |
| controls.minDistance = 3; | |
| controls.maxDistance = 50; | |
| // Starfield | |
| const starVertices = []; | |
| for (let i = 0; i < 10000; i++) { | |
| const x = THREE.MathUtils.randFloatSpread(2000); | |
| const y = THREE.MathUtils.randFloatSpread(2000); | |
| const z = THREE.MathUtils.randFloatSpread(2000); | |
| starVertices.push(x, y, z); | |
| } | |
| const starGeometry = new THREE.BufferGeometry(); | |
| starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3)); | |
| const starMaterial = new THREE.PointsMaterial({ color: 0x888888 }); | |
| stars = new THREE.Points(starGeometry, starMaterial); | |
| scene.add(stars); | |
| // Black Hole Sphere | |
| const blackHoleGeometry = new THREE.SphereGeometry(1, 32, 32); | |
| const blackHoleMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 }); | |
| blackHole = new THREE.Mesh(blackHoleGeometry, blackHoleMaterial); | |
| scene.add(blackHole); | |
| // Accretion Disk | |
| const diskGeometry = new THREE.RingGeometry(1.2, 4, 64); | |
| const diskMaterial = new THREE.ShaderMaterial({ | |
| uniforms: accretionDiskShader.uniforms, | |
| vertexShader: accretionDiskShader.vertexShader, | |
| fragmentShader: accretionDiskShader.fragmentShader, | |
| side: THREE.DoubleSide, | |
| transparent: true, | |
| }); | |
| accretionDisk = new THREE.Mesh(diskGeometry, diskMaterial); | |
| accretionDisk.rotation.x = Math.PI * 0.5; | |
| scene.add(accretionDisk); | |
| // Plasma Beam | |
| const beamGeometry = new THREE.PlaneGeometry(20, 0.2); | |
| const beamMaterial = new THREE.ShaderMaterial({ | |
| uniforms: plasmaBeamShader.uniforms, | |
| vertexShader: plasmaBeamShader.vertexShader, | |
| fragmentShader: plasmaBeamShader.fragmentShader, | |
| side: THREE.DoubleSide, | |
| transparent: true, | |
| blending: THREE.AdditiveBlending, | |
| }); | |
| plasmaBeam = new THREE.Mesh(beamGeometry, beamMaterial); | |
| plasmaBeam.rotation.z = Math.PI / 4; // Diagonal | |
| scene.add(plasmaBeam); | |
| // Post-processing | |
| composer = new EffectComposer(renderer); | |
| const renderPass = new RenderPass(scene, camera); | |
| composer.addPass(renderPass); | |
| // Gravitational Lensing Pass | |
| const lensingPass = new ShaderPass(gravitationalLensingShader); | |
| composer.addPass(lensingPass); | |
| // Bloom Pass | |
| const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 1.5, 0.4, 0.85); | |
| bloomPass.threshold = 0; | |
| bloomPass.strength = 1.2; | |
| bloomPass.radius = 0.5; | |
| composer.addPass(bloomPass); | |
| window.addEventListener('resize', onWindowResize, false); | |
| } | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| composer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const time = performance.now() * 0.001; | |
| accretionDisk.material.uniforms['time'].value = time; | |
| plasmaBeam.material.uniforms['time'].value = time; | |
| controls.update(); | |
| composer.render(); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment