-
-
Save shricodev/3f47123218370499f798ffb379940186 to your computer and use it in GitHub Desktop.
Black Hole Animation - Grok 4 - 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"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Black Hole Visualization</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;500;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background: radial-gradient(ellipse at center, #0a001a 0%, #000000 70%); | |
| color: #d0c0ff; | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| canvas { | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| #info { | |
| position: absolute; | |
| top: 30px; | |
| width: 100%; | |
| text-align: center; | |
| color: rgba(200, 180, 255, 0.85); | |
| font-size: 20px; | |
| letter-spacing: 1px; | |
| pointer-events: none; | |
| z-index: 100; | |
| text-shadow: 0 0 10px rgba(100, 50, 255, 0.5); | |
| transition: opacity 3s ease-in-out 2s; | |
| animation: pulse 4s infinite ease-in-out; | |
| } | |
| @keyframes pulse { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.6; } } | |
| .ui-panel { | |
| position: absolute; | |
| background-image: linear-gradient(145deg, rgba(25, 10, 45, 0.9), rgba(15, 5, 30, 0.95)); | |
| backdrop-filter: blur(12px) saturate(170%); | |
| -webkit-backdrop-filter: blur(12px) saturate(170%); | |
| padding: 12px 18px; | |
| border-radius: 12px; | |
| border: 1px solid rgba(200, 180, 255, 0.2); | |
| color: rgba(240, 230, 255, 0.95); | |
| font-size: 15px; | |
| user-select: none; | |
| z-index: 50; | |
| transition: all 0.4s ease; | |
| box-shadow: 0 8px 25px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(200,180,255,0.1) inset; | |
| box-sizing: border-box; | |
| opacity: 0; | |
| transform: translateX(-15px); | |
| animation: panelSlideIn 0.8s cubic-bezier(0.22, 0.61, 0.36, 1) 0.3s forwards; | |
| } | |
| @keyframes panelSlideIn { to { opacity: 1; transform: translateX(0); } } | |
| .ui-panel:hover { | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(200,180,255,0.15) inset, 0 0 15px rgba(100, 50, 255, 0.3); | |
| } | |
| #controls { bottom: 30px; left: 30px; } | |
| #autoRotateToggle { | |
| cursor: pointer; padding: 10px; display: flex; align-items: center; | |
| gap: 10px; color: inherit; font-size: inherit; transition: all 0.3s ease; | |
| border-radius: 6px; background: rgba(100, 50, 255, 0.1); | |
| } | |
| #autoRotateToggle:hover { color: #ffffff; background: rgba(100, 50, 255, 0.3); box-shadow: 0 0 10px rgba(100, 50, 255, 0.5); } | |
| #autoRotateToggle span { vertical-align: middle; } | |
| .rotate-icon { | |
| width: 1.2em; height: 1.2em; stroke: currentColor; stroke-width: 2.0; | |
| fill: none; stroke-linecap: round; stroke-linejoin: round; vertical-align: middle; | |
| filter: drop-shadow(0 0 2px rgba(100, 50, 255, 0.7)); | |
| } | |
| @media (max-width: 640px) { | |
| .ui-panel { padding: 8px 10px; border-radius: 6px; } | |
| #controls { max-width: 140px; left: 15px; bottom: 15px; } | |
| #info { font-size: 16px; top: 20px; } | |
| #info span { font-size: 12px; } | |
| } | |
| </style> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js", | |
| "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <div id="info"> | |
| Cosmic Black Hole<br> | |
| <span style="font-size: 14px; opacity: 0.8;">Drag to Explore the Void</span> | |
| </div> | |
| <div id="controls" class="ui-panel"> | |
| <div id="autoRotateToggle" title="Toggle cosmic rotation"></div> | |
| </div> | |
| <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'; | |
| const BLACK_HOLE_RADIUS = 1.5; | |
| const DISK_INNER_RADIUS = BLACK_HOLE_RADIUS + 0.3; | |
| const DISK_OUTER_RADIUS = 10.0; | |
| const DISK_TILT_ANGLE = Math.PI / 4.0; | |
| const scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0x020104, 0.02); | |
| const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000); | |
| camera.position.set(-7, 6, 7); | |
| const renderer = new THREE.WebGLRenderer({ antialias: true, powerPreference: "high-performance" }); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); | |
| renderer.outputColorSpace = THREE.SRGBColorSpace; | |
| renderer.toneMapping = THREE.ACESFilmicToneMapping; | |
| renderer.toneMappingExposure = 1.3; | |
| document.body.appendChild(renderer.domElement); | |
| const composer = new EffectComposer(renderer); | |
| composer.addPass(new RenderPass(scene, camera)); | |
| const bloomPass = new UnrealBloomPass( | |
| new THREE.Vector2(window.innerWidth, window.innerHeight), | |
| 0.9, 0.6, 0.7 | |
| ); | |
| composer.addPass(bloomPass); | |
| const lensingShader = { | |
| uniforms: { | |
| "tDiffuse": { value: null }, | |
| "blackHoleScreenPos": { value: new THREE.Vector2(0.5, 0.5) }, | |
| "lensingStrength": { value: 0.15 }, | |
| "lensingRadius": { value: 0.35 }, | |
| "aspectRatio": { value: window.innerWidth / window.innerHeight }, | |
| "chromaticAberration": { value: 0.006 } | |
| }, | |
| vertexShader: `varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`, | |
| fragmentShader: ` | |
| uniform sampler2D tDiffuse; | |
| uniform vec2 blackHoleScreenPos; | |
| uniform float lensingStrength; | |
| uniform float lensingRadius; | |
| uniform float aspectRatio; | |
| uniform float chromaticAberration; | |
| varying vec2 vUv; | |
| void main() { | |
| vec2 screenPos = vUv; | |
| vec2 toCenter = screenPos - blackHoleScreenPos; | |
| toCenter.x *= aspectRatio; | |
| float dist = length(toCenter); | |
| float distortionAmount = lensingStrength / (dist * dist + 0.003); | |
| distortionAmount = clamp(distortionAmount, 0.0, 0.7); | |
| float falloff = smoothstep(lensingRadius, lensingRadius * 0.3, dist); | |
| distortionAmount *= falloff; | |
| vec2 offset = normalize(toCenter) * distortionAmount; | |
| offset.x /= aspectRatio; | |
| vec2 distortedUvR = screenPos - offset * (1.0 + chromaticAberration); | |
| vec2 distortedUvG = screenPos - offset; | |
| vec2 distortedUvB = screenPos - offset * (1.0 - chromaticAberration); | |
| float r = texture2D(tDiffuse, distortedUvR).r; | |
| float g = texture2D(tDiffuse, distortedUvG).g; | |
| float b = texture2D(tDiffuse, distortedUvB).b; | |
| gl_FragColor = vec4(r, g, b, 1.0); | |
| }` | |
| }; | |
| const lensingPass = new ShaderPass(lensingShader); | |
| composer.addPass(lensingPass); | |
| const controls = new OrbitControls(camera, renderer.domElement); | |
| controls.enableDamping = true; controls.dampingFactor = 0.04; | |
| controls.rotateSpeed = 0.5; controls.autoRotate = false; | |
| controls.autoRotateSpeed = 0.15; | |
| controls.target.set(0, 0, 0); | |
| controls.minDistance = 3; | |
| controls.maxDistance = 120; | |
| controls.enablePan = false; | |
| controls.update(); | |
| let autoRotateEnabled = false; | |
| const autoRotateToggle = document.getElementById('autoRotateToggle'); | |
| const rotateIconSVG = `<svg class="rotate-icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 4V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/></svg>`; | |
| updateAutoRotateText(); | |
| autoRotateToggle.addEventListener('click', () => { | |
| autoRotateEnabled = !autoRotateEnabled; controls.autoRotate = autoRotateEnabled; updateAutoRotateText(); | |
| }); | |
| function updateAutoRotateText() { | |
| autoRotateToggle.innerHTML = rotateIconSVG + `<span>Spin: ${autoRotateEnabled ? "Active" : "Paused"}</span>`; | |
| } | |
| const starGeometry = new THREE.BufferGeometry(); | |
| const starCount = 200000; | |
| const starPositions = new Float32Array(starCount * 3); | |
| const starColors = new Float32Array(starCount * 3); | |
| const starSizes = new Float32Array(starCount); | |
| const starTwinkle = new Float32Array(starCount); | |
| const starFieldRadius = 2500; | |
| const starPalette = [ | |
| new THREE.Color(0x88aaff), new THREE.Color(0xffaaff), new THREE.Color(0xaaffff), | |
| new THREE.Color(0xffddaa), new THREE.Color(0xffeecc), new THREE.Color(0xffffff), | |
| new THREE.Color(0xff8888), new THREE.Color(0x88ff88), new THREE.Color(0xffff88), | |
| new THREE.Color(0x88ffff), new THREE.Color(0xff88ff), new THREE.Color(0x88ffaa) | |
| ]; | |
| for (let i = 0; i < starCount; i++) { | |
| const i3 = i * 3; | |
| const phi = Math.acos(-1 + (2 * i) / starCount); | |
| const theta = Math.sqrt(starCount * Math.PI) * phi; | |
| const radius = Math.cbrt(Math.random()) * starFieldRadius + 150; | |
| starPositions[i3] = radius * Math.sin(phi) * Math.cos(theta); | |
| starPositions[i3 + 1] = radius * Math.sin(phi) * Math.sin(theta); | |
| starPositions[i3 + 2] = radius * Math.cos(phi); | |
| const starColor = starPalette[Math.floor(Math.random() * starPalette.length)].clone(); | |
| starColor.multiplyScalar(Math.random() * 0.8 + 0.2); | |
| starColors[i3] = starColor.r; starColors[i3 + 1] = starColor.g; starColors[i3 + 2] = starColor.b; | |
| starSizes[i] = THREE.MathUtils.randFloat(0.5, 3.5); | |
| starTwinkle[i] = Math.random() * Math.PI * 2; | |
| } | |
| starGeometry.setAttribute('position', new THREE.BufferAttribute(starPositions, 3)); | |
| starGeometry.setAttribute('color', new THREE.BufferAttribute(starColors, 3)); | |
| starGeometry.setAttribute('size', new THREE.BufferAttribute(starSizes, 1)); | |
| starGeometry.setAttribute('twinkle', new THREE.BufferAttribute(starTwinkle, 1)); | |
| const starMaterial = new THREE.ShaderMaterial({ | |
| uniforms: { | |
| uTime: { value: 0 }, | |
| uPixelRatio: { value: renderer.getPixelRatio() } | |
| }, | |
| vertexShader: ` | |
| uniform float uTime; | |
| uniform float uPixelRatio; | |
| attribute float size; | |
| attribute float twinkle; | |
| varying vec3 vColor; | |
| varying float vTwinkle; | |
| void main() { | |
| vColor = color; | |
| vTwinkle = sin(uTime * 3.0 + twinkle) * 0.5 + 0.5; | |
| vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); | |
| gl_PointSize = size * uPixelRatio * (350.0 / -mvPosition.z); | |
| gl_Position = projectionMatrix * mvPosition; | |
| } | |
| `, | |
| fragmentShader: ` | |
| varying vec3 vColor; | |
| varying float vTwinkle; | |
| void main() { | |
| float dist = distance(gl_PointCoord, vec2(0.5)); | |
| if (dist > 0.5) discard; | |
| float alpha = 1.0 - smoothstep(0.0, 0.5, dist); | |
| alpha *= (0.3 + vTwinkle * 0.7); | |
| gl_FragColor = vec4(vColor, alpha); | |
| } | |
| `, | |
| transparent: true, | |
| vertexColors: true, | |
| blending: THREE.AdditiveBlending, | |
| depthWrite: false | |
| }); | |
| const stars = new THREE.Points(starGeometry, starMaterial); | |
| scene.add(stars); | |
| const eventHorizonGeom = new THREE.SphereGeometry(BLACK_HOLE_RADIUS * 1.1, 128, 64); | |
| const eventHorizonMat = new THREE.ShaderMaterial({ | |
| uniforms: { | |
| uTime: { value: 0 }, | |
| uCameraPosition: { value: camera.position } | |
| }, | |
| vertexShader: ` | |
| varying vec3 vNormal; | |
| varying vec3 vPosition; | |
| void main() { | |
| vNormal = normalize(normalMatrix * normal); | |
| vPosition = position; | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| uniform float uTime; | |
| uniform vec3 uCameraPosition; | |
| varying vec3 vNormal; | |
| varying vec3 vPosition; | |
| void main() { | |
| vec3 viewDirection = normalize(uCameraPosition - vPosition); | |
| float fresnel = 1.0 - abs(dot(vNormal, viewDirection)); | |
| fresnel = pow(fresnel, 2.0); | |
| vec3 glowColor = vec3(1.0, 0.5, 0.2); | |
| float pulse = sin(uTime * 3.0) * 0.2 + 0.8; | |
| gl_FragColor = vec4(glowColor * fresnel * pulse, fresnel * 0.5); | |
| } | |
| `, | |
| transparent: true, | |
| blending: THREE.AdditiveBlending, | |
| side: THREE.BackSide | |
| }); | |
| const eventHorizon = new THREE.Mesh(eventHorizonGeom, eventHorizonMat); | |
| scene.add(eventHorizon); | |
| const blackHoleGeom = new THREE.SphereGeometry(BLACK_HOLE_RADIUS, 128, 64); | |
| const blackHoleMat = new THREE.MeshBasicMaterial({ color: 0x000000 }); | |
| const blackHoleMesh = new THREE.Mesh(blackHoleGeom, blackHoleMat); | |
| blackHoleMesh.renderOrder = 0; | |
| scene.add(blackHoleMesh); | |
| const diskGeometry = new THREE.RingGeometry(DISK_INNER_RADIUS, DISK_OUTER_RADIUS, 256, 128); | |
| const diskMaterial = new THREE.ShaderMaterial({ | |
| uniforms: { | |
| uTime: { value: 0.0 }, | |
| uColorHot: { value: new THREE.Color(0xffeecc) }, | |
| uColorMid1: { value: new THREE.Color(0xff7733) }, | |
| uColorMid2: { value: new THREE.Color(0xff4477) }, | |
| uColorMid3: { value: new THREE.Color(0x7744ff) }, | |
| uColorOuter: { value: new THREE.Color(0x4477ff) }, | |
| uNoiseScale: { value: 3.0 }, | |
| uFlowSpeed: { value: 0.25 }, | |
| uDensity: { value: 1.5 } | |
| }, | |
| vertexShader: ` | |
| varying vec2 vUv; | |
| varying float vRadius; | |
| varying float vAngle; | |
| void main() { | |
| vUv = uv; | |
| vRadius = length(position.xy); | |
| vAngle = atan(position.y, position.x); | |
| gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); | |
| } | |
| `, | |
| fragmentShader: ` | |
| uniform float uTime; | |
| uniform vec3 uColorHot; | |
| uniform vec3 uColorMid1; | |
| uniform vec3 uColorMid2; | |
| uniform vec3 uColorMid3; | |
| uniform vec3 uColorOuter; | |
| uniform float uNoiseScale; | |
| uniform float uFlowSpeed; | |
| uniform float uDensity; | |
| varying vec2 vUv; | |
| varying float vRadius; | |
| varying float vAngle; | |
| 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) ) ); | |
| } | |
| void main() { | |
| float normalizedRadius = smoothstep(${DISK_INNER_RADIUS.toFixed(2)}, ${DISK_OUTER_RADIUS.toFixed(2)}, vRadius); | |
| float spiral = vAngle * 4.0 - (1.0 / (normalizedRadius + 0.1)) * 2.5; | |
| vec2 noiseUv = vec2(vUv.x + uTime * uFlowSpeed * (2.5 / (vRadius * 0.3 + 1.0)) + sin(spiral) * 0.15, vUv.y * 0.7 + cos(spiral) * 0.15); | |
| float noiseVal1 = snoise(vec3(noiseUv * uNoiseScale, uTime * 0.2)); | |
| float noiseVal2 = snoise(vec3(noiseUv * uNoiseScale * 3.5 + 0.8, uTime * 0.25)); | |
| float noiseVal3 = snoise(vec3(noiseUv * uNoiseScale * 7.0 + 1.5, uTime * 0.35)); | |
| float noiseVal = (noiseVal1 * 0.4 + noiseVal2 * 0.4 + noiseVal3 * 0.2); | |
| noiseVal = (noiseVal + 1.0) * 0.5; | |
| vec3 color = uColorOuter; | |
| color = mix(color, uColorMid3, smoothstep(0.0, 0.3, normalizedRadius)); | |
| color = mix(color, uColorMid2, smoothstep(0.25, 0.6, normalizedRadius)); | |
| color = mix(color, uColorMid1, smoothstep(0.55, 0.8, normalizedRadius)); | |
| color = mix(color, uColorHot, smoothstep(0.75, 1.0, normalizedRadius)); | |
| color *= (0.6 + noiseVal * 1.0); | |
| float brightness = pow(1.0 - normalizedRadius, 1.2) * 4.0 + 0.6; | |
| brightness *= (0.4 + noiseVal * 2.0); | |
| float pulse = sin(uTime * 2.0 + normalizedRadius * 15.0 + vAngle * 2.5) * 0.2 + 0.8; | |
| brightness *= pulse; | |
| float alpha = uDensity * (0.25 + noiseVal * 0.85); | |
| alpha *= smoothstep(0.0, 0.2, normalizedRadius); | |
| alpha *= (1.0 - smoothstep(0.8, 1.0, normalizedRadius)); | |
| alpha = clamp(alpha, 0.0, 1.0); | |
| gl_FragColor = vec4(color * brightness, alpha); | |
| } | |
| `, | |
| transparent: true, | |
| side: THREE.DoubleSide, | |
| depthWrite: false, | |
| blending: THREE.AdditiveBlending | |
| }); | |
| const accretionDisk = new THREE.Mesh(diskGeometry, diskMaterial); | |
| accretionDisk.rotation.x = DISK_TILT_ANGLE; | |
| accretionDisk.renderOrder = 1; | |
| scene.add(accretionDisk); | |
| setTimeout(() => { const info = document.getElementById('info'); if (info) info.style.opacity = '0'; }, 6000); | |
| let resizeTimeout; | |
| window.addEventListener('resize', () => { | |
| clearTimeout(resizeTimeout); | |
| resizeTimeout = setTimeout(() => { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| composer.setSize(window.innerWidth, window.innerHeight); | |
| bloomPass.resolution.set(window.innerWidth, window.innerHeight); | |
| lensingPass.uniforms.aspectRatio.value = window.innerWidth / window.innerHeight; | |
| renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5)); | |
| }, 150); | |
| }); | |
| const clock = new THREE.Clock(); | |
| const blackHoleScreenPosVec3 = new THREE.Vector3(); | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| const elapsedTime = clock.getElapsedTime(); | |
| const deltaTime = clock.getDelta(); | |
| diskMaterial.uniforms.uTime.value = elapsedTime; | |
| starMaterial.uniforms.uTime.value = elapsedTime; | |
| eventHorizonMat.uniforms.uTime.value = elapsedTime; | |
| eventHorizonMat.uniforms.uCameraPosition.value.copy(camera.position); | |
| blackHoleScreenPosVec3.copy(blackHoleMesh.position).project(camera); | |
| lensingPass.uniforms.blackHoleScreenPos.value.set( | |
| (blackHoleScreenPosVec3.x + 1) / 2, | |
| (blackHoleScreenPosVec3.y + 1) / 2 | |
| ); | |
| controls.update(); | |
| stars.rotation.y += deltaTime * 0.004; | |
| stars.rotation.x += deltaTime * 0.002; | |
| accretionDisk.rotation.z += deltaTime * 0.006; | |
| composer.render(deltaTime); | |
| } | |
| animate(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment