news 2026/5/8 17:39:59

黑洞引力透镜 html开源

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
黑洞引力透镜 html开源

参考了多个开源项目(包括 bzk9x/blackhole 和 Interstellar 的 Gargantua 渲染方法 )的实现,制作了一个终极炫酷版

主要增强效果

表格

特性原版终极版
吸积盘单层简单纹理多层结构 + 温度梯度 + Doppler 红蓝移
光子球发光环 + 动态颜色变化
喷流双极高能喷流(沿旋转轴)
星空背景简单星星银河盘面 + 亮核 + 多层星云
爱因斯坦环引力透镜最外环
后处理UnrealBloom 辉光效果
交互基础控制鼠标偏移视角 + 自动旋转

直接打开 HTML 文件即可运行,无需任何依赖安装(所有库均通过 CDN 引入)。

操作方式

  • 拖拽:旋转视角

  • 滚轮:缩放距离

  • 鼠标移动:轻微偏移视角(移开鼠标后恢复自动旋转)

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>黑洞引力透镜 - 终极版</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; width: 100vw; height: 100vh; font-family: 'Segoe UI', system-ui, sans-serif; } #canvas-container { width: 100%; height: 100%; position: relative; } canvas { display: block; width: 100% !important; height: 100% !important; } #loading { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: #fff; font-size: 14px; letter-spacing: 4px; text-transform: uppercase; opacity: 0.6; transition: opacity 0.5s; pointer-events: none; } #ui { position: absolute; top: 20px; left: 20px; color: rgba(255,255,255,0.5); z-index: 10; pointer-events: none; font-size: 12px; letter-spacing: 2px; } #stats { position: absolute; bottom: 20px; right: 20px; color: rgba(255,255,255,0.3); font-size: 11px; text-align: right; pointer-events: none; } </style> <base target="_blank"> </head> <body> <div id="canvas-container"></div> <div id="loading">初始化引力场...</div> <div id="ui">BLACK HOLE GRAVITATIONAL LENSING</div> <div id="stats">拖拽旋转 | 滚轮缩放</div> <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'; // ============ 场景初始化 ============ const container = document.getElementById('canvas-container'); const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false, powerPreference: "high-performance" }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); renderer.toneMapping = THREE.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.3; container.appendChild(renderer.domElement); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 3, 14); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; controls.maxDistance = 40; controls.minDistance = 5; controls.enablePan = false; controls.autoRotate = true; controls.autoRotateSpeed = 0.3; // ============ 后处理 - Bloom效果 ============ const composer = new EffectComposer(renderer); const renderPass = new RenderPass(scene, camera); composer.addPass(renderPass); const bloomPass = new UnrealBloomPass( new THREE.Vector2(window.innerWidth, window.innerHeight), 1.8, // strength 0.4, // radius 0.85 // threshold ); composer.addPass(bloomPass); // ============ 黑洞引力透镜着色器 ============ const vertexShader = ` varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position, 1.0); } `; const fragmentShader = ` precision highp float; uniform float uTime; uniform vec2 uResolution; uniform vec3 uCameraPos; uniform vec3 uCameraDir; uniform vec3 uCameraRight; uniform vec3 uCameraUp; uniform vec3 uBlackHolePos; uniform float uBlackHoleMass; uniform float uDiskBrightness; uniform float uStarDensity; uniform vec2 uMouse; uniform float uMouseActive; varying vec2 vUv; #define PI 3.14159265359 #define MAX_STEPS 600 #define STEP_SIZE 0.06 #define SCHWARZSCHILD_FACTOR 0.12 // ============ 噪声函数 ============ float hash(float n) { return fract(sin(n) * 43758.5453123); } float hash2(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); } float hash3(vec3 p) { return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); } float noise(vec3 p) { vec3 i = floor(p); vec3 f = fract(p); f = f * f * (3.0 - 2.0 * f); float n = i.x + i.y * 57.0 + i.z * 113.0; return mix( mix(mix(hash(n), hash(n + 1.0), f.x), mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y), mix(mix(hash(n + 113.0), hash(n + 114.0), f.x), mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y), f.z ); } float fbm(vec3 p) { float val = 0.0; float amp = 0.5; float freq = 1.0; for(int i = 0; i < 5; i++) { val += amp * noise(p * freq); freq *= 2.0; amp *= 0.5; } return val; } // ============ 星空背景 ============ vec3 generateStarField(vec3 dir) { vec3 col = vec3(0.0); // 银河盘面 - 更明显的银河带 vec3 galacticNormal = normalize(vec3(0.2, 0.15, 1.0)); float galacticPlane = abs(dot(dir, galacticNormal)); float galacticGlow = pow(max(0.0, 1.0 - galacticPlane * 2.5), 2.5); col += vec3(0.15, 0.2, 0.4) * galacticGlow * 0.4; // 银河中心亮核 float galacticCenter = dot(dir, normalize(vec3(0.2, 0.1, 1.0))); float centerGlow = pow(max(0.0, galacticCenter), 8.0); col += vec3(0.3, 0.25, 0.2) * centerGlow * 0.3; // 多层星星 float starDensity = uStarDensity; for(int layer = 0; layer < 5; layer++) { float scale = pow(2.0, float(layer)) * 40.0; vec3 p = dir * scale; vec3 grid = floor(p); vec3 frac = fract(p); float star = hash3(grid); if(star > 0.96 - float(layer) * 0.005) { float brightness = hash3(grid + 100.0); vec3 starColor = mix( vec3(1.0, 0.7, 0.4), mix( vec3(0.5, 0.7, 1.0), vec3(1.0, 1.0, 1.0), hash3(grid + 200.0) ), hash3(grid + 300.0) ); float dist = length(frac - 0.5); float glow = exp(-dist * dist * 150.0); float twinkle = 0.5 + 0.5 * sin(uTime * (0.5 + hash3(grid) * 2.0) + hash3(grid) * 10.0); col += starColor * glow * brightness * twinkle * (0.6 + 0.4 * float(layer) / 5.0); } } // 星云 - 更丰富的颜色 float nebula1 = fbm(dir * 2.0 + uTime * 0.01); nebula1 = pow(nebula1, 3.0); col += vec3(0.08, 0.03, 0.12) * nebula1 * galacticGlow; float nebula2 = fbm(dir * 4.0 - uTime * 0.015); nebula2 = pow(nebula2, 4.0); col += vec3(0.05, 0.08, 0.15) * nebula2 * galacticGlow * 0.5; return col; } // ============ 吸积盘 - 多层结构 ============ vec4 accretionDisk(vec3 pos, vec3 bhPos, float rs) { vec3 relPos = pos - bhPos; float dist = length(relPos.xz); float height = abs(relPos.y); float innerRadius = rs * 3.0; float outerRadius = rs * 30.0; if(dist < innerRadius || dist > outerRadius || height > rs * 0.4) { return vec4(0.0); } // 多层吸积盘 float layer1 = smoothstep(innerRadius, innerRadius + rs * 2.0, dist) * (1.0 - smoothstep(innerRadius + rs * 8.0, innerRadius + rs * 12.0, dist)); float layer2 = smoothstep(innerRadius + rs * 10.0, innerRadius + rs * 14.0, dist) * (1.0 - smoothstep(innerRadius + rs * 18.0, innerRadius + rs * 22.0, dist)); float layer3 = smoothstep(innerRadius + rs * 20.0, innerRadius + rs * 24.0, dist) * (1.0 - smoothstep(outerRadius - rs * 3.0, outerRadius, dist)); float totalLayer = max(max(layer1, layer2 * 0.7), layer3 * 0.4); // 旋转角度 float angle = atan(relPos.z, relPos.x); float rotationSpeed = 1.0 / sqrt(max(dist / rs, 3.0)); float rotatedAngle = angle + uTime * rotationSpeed * 0.5; // 螺旋纹理 float spiral = sin(rotatedAngle * 3.0 + dist * 2.0) * 0.5 + 0.5; float spiral2 = sin(rotatedAngle * 5.0 - dist * 3.0 + uTime * 0.3) * 0.5 + 0.5; // 噪声纹理 float tex = fbm(vec3( cos(rotatedAngle) * dist * 1.5, sin(rotatedAngle) * dist * 1.5, uTime * 0.08 )); // 温度梯度 - 越靠近越热(蓝白) float temp = 1.0 - smoothstep(innerRadius, outerRadius, dist); temp = pow(temp, 0.7); // 多普勒效应 - 一侧红移一侧蓝移 float doppler = sin(angle + uTime * 0.15) * 0.5 + 0.5; // 颜色 - 基于温度的黑体辐射近似 vec3 hotColor = mix(vec3(0.6, 0.8, 1.0), vec3(1.0, 1.0, 1.0), temp); vec3 warmColor = mix(vec3(1.0, 0.6, 0.2), vec3(1.0, 0.9, 0.5), temp); vec3 coolColor = mix(vec3(1.0, 0.3, 0.05), vec3(0.8, 0.5, 0.2), temp); vec3 color = mix(coolColor, warmColor, temp * 0.7); color = mix(color, hotColor, temp * temp * 0.5); // 多普勒偏移 color = mix(color * vec3(1.2, 0.8, 0.6), color * vec3(0.7, 0.9, 1.3), doppler); // 亮度 float brightness = temp * (0.4 + 0.6 * tex) * (0.5 + 0.5 * spiral); brightness *= (1.0 + 0.3 * spiral2); brightness *= uDiskBrightness; // 透明度 float alpha = totalLayer * brightness * 2.0; alpha *= (1.0 - height / (rs * 0.4)); alpha = clamp(alpha, 0.0, 1.0); return vec4(color * brightness * 4.0, alpha); } // ============ 喷流 ============ vec4 jetStream(vec3 pos, vec3 bhPos, float rs, vec3 dir) { vec3 relPos = pos - bhPos; float distXZ = length(relPos.xz); float height = abs(relPos.y); // 喷流沿Y轴 if(height < rs * 2.0 || distXZ > rs * 0.5 + height * 0.08) { return vec4(0.0); } float jetLength = rs * 80.0; if(height > jetLength) return vec4(0.0); // 喷流强度随距离衰减 float intensity = 1.0 - height / jetLength; intensity = pow(intensity, 0.5); // 喷流纹理 float jetTex = fbm(vec3(relPos.x * 3.0, relPos.y * 0.5 + uTime * 2.0, relPos.z * 3.0)); // 喷流颜色 - 蓝白色高能喷流 vec3 jetColor = mix(vec3(0.3, 0.6, 1.0), vec3(0.8, 0.9, 1.0), jetTex); float alpha = intensity * jetTex * 0.15 * (1.0 - distXZ / (rs * 0.5 + height * 0.08)); return vec4(jetColor * intensity * 2.0, alpha); } // ============ 引力透镜主函数 ============ vec3 gravitationalLensing(vec3 rayOrigin, vec3 rayDir) { vec3 bhPos = uBlackHolePos; float rs = uBlackHoleMass * SCHWARZSCHILD_FACTOR; vec3 pos = rayOrigin; vec3 dir = rayDir; vec4 accumulatedColor = vec4(0.0); float minDistToBH = 1000.0; bool hitDisk = false; for(int i = 0; i < MAX_STEPS; i++) { vec3 toBH = bhPos - pos; float distToBH = length(toBH); minDistToBH = min(minDistToBH, distToBH); // 引力偏转 - 更精确的物理模型 float gravitationalPull = rs / (distToBH * distToBH + 0.001); vec3 acceleration = normalize(toBH) * gravitationalPull * 0.6; // 更新光线 dir = normalize(dir + acceleration * STEP_SIZE); pos += dir * STEP_SIZE; // 检测吸积盘交叉 if(i > 5) { vec4 diskColor = accretionDisk(pos, bhPos, rs); if(diskColor.a > 0.0) { accumulatedColor.rgb = mix(accumulatedColor.rgb, diskColor.rgb, diskColor.a * (1.0 - accumulatedColor.a)); accumulatedColor.a += diskColor.a * (1.0 - accumulatedColor.a); hitDisk = true; } // 喷流 vec4 jetColor = jetStream(pos, bhPos, rs, dir); if(jetColor.a > 0.0) { accumulatedColor.rgb = mix(accumulatedColor.rgb, jetColor.rgb, jetColor.a * (1.0 - accumulatedColor.a)); accumulatedColor.a += jetColor.a * (1.0 - accumulatedColor.a); } } // 事件视界 if(distToBH < rs * 2.2) { // 事件视界边缘微光 float edgeGlow = smoothstep(rs * 2.2, rs * 2.0, distToBH); vec4 horizonGlow = vec4(vec3(0.1, 0.15, 0.3) * edgeGlow * 0.5, edgeGlow * 0.3); accumulatedColor.rgb = mix(accumulatedColor.rgb, horizonGlow.rgb, horizonGlow.a * (1.0 - accumulatedColor.a)); // 纯黑中心 accumulatedColor = vec4(0.0, 0.0, 0.0, 1.0); break; } // 光子球 - 发光环 float photonSphereInner = rs * 2.5; float photonSphereOuter = rs * 4.0; if(distToBH < photonSphereOuter && distToBH > photonSphereInner) { float photonGlow = smoothstep(photonSphereOuter, rs * 3.0, distToBH); photonGlow *= smoothstep(photonSphereInner, rs * 3.2, distToBH); // 光子球颜色变化 vec3 photonColor = mix(vec3(1.0, 0.9, 0.5), vec3(0.5, 0.8, 1.0), sin(uTime * 0.5 + distToBH * 10.0) * 0.5 + 0.5); vec4 glowColor = vec4(photonColor * photonGlow * 3.0, photonGlow * 0.4); accumulatedColor.rgb = mix(accumulatedColor.rgb, glowColor.rgb, glowColor.a * (1.0 - accumulatedColor.a)); accumulatedColor.a += glowColor.a * (1.0 - accumulatedColor.a); } // 足够远了或足够不透明了 if(distToBH > 60.0 && accumulatedColor.a > 0.98) { break; } } // 背景星空 if(accumulatedColor.a < 1.0) { vec3 starColor = generateStarField(dir); accumulatedColor.rgb = mix(starColor, accumulatedColor.rgb, accumulatedColor.a); } // 爱因斯坦环 - 引力透镜最外环 float einsteinRingInner = rs * 5.0; float einsteinRingOuter = rs * 12.0; if(minDistToBH > einsteinRingInner && minDistToBH < einsteinRingOuter) { float ringIntensity = smoothstep(einsteinRingOuter, rs * 7.0, minDistToBH); ringIntensity *= smoothstep(einsteinRingInner, rs * 9.0, minDistToBH); ringIntensity = pow(ringIntensity, 2.0); vec3 ringColor = vec3(0.7, 0.85, 1.0) * ringIntensity * 0.6; // 环的闪烁 ringColor *= 0.8 + 0.2 * sin(uTime * 2.0 + minDistToBH * 5.0); accumulatedColor.rgb += ringColor; } return accumulatedColor.rgb; } void main() { vec2 uv = vUv * 2.0 - 1.0; uv.x *= uResolution.x / uResolution.y; // 鼠标交互 - 轻微偏移视角 if(uMouseActive > 0.5) { uv += (uMouse - 0.5) * 0.08; } // 相机射线 vec3 rayDir = normalize(uCameraDir + uCameraRight * uv.x + uCameraUp * uv.y); vec3 color = gravitationalLensing(uCameraPos, rayDir); // 色调映射 - ACES近似 color = color / (1.0 + color * 0.8); // 暗角 float vignette = 1.0 - dot(vUv - 0.5, vUv - 0.5) * 0.6; color *= vignette; // 色温微调 - 偏冷色调 color = pow(color, vec3(0.92, 1.0, 1.08)); // 对比度增强 color = (color - 0.5) * 1.1 + 0.5; gl_FragColor = vec4(max(color, vec3(0.0)), 1.0); } `; const material = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 }, uResolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }, uCameraPos: { value: new THREE.Vector3() }, uCameraDir: { value: new THREE.Vector3() }, uCameraRight: { value: new THREE.Vector3() }, uCameraUp: { value: new THREE.Vector3() }, uBlackHolePos: { value: new THREE.Vector3(0, 0, 0) }, uBlackHoleMass: { value: 1.0 }, uDiskBrightness: { value: 1.2 }, uStarDensity: { value: 2.0 }, uMouse: { value: new THREE.Vector2(0.5, 0.5) }, uMouseActive: { value: 0.0 } }, vertexShader: vertexShader, fragmentShader: fragmentShader, transparent: false, side: THREE.DoubleSide }); const geometry = new THREE.PlaneGeometry(2, 2); const quad = new THREE.Mesh(geometry, material); scene.add(quad); // ============ 鼠标交互 ============ let mouseActive = false; window.addEventListener('mousemove', (e) => { material.uniforms.uMouse.value.x = e.clientX / window.innerWidth; material.uniforms.uMouse.value.y = 1.0 - e.clientY / window.innerHeight; material.uniforms.uMouseActive.value = 1.0; mouseActive = true; // 鼠标移动时停止自动旋转 controls.autoRotate = false; }); window.addEventListener('mouseleave', () => { material.uniforms.uMouseActive.value = 0.0; mouseActive = false; controls.autoRotate = true; }); // ============ 窗口大小调整 ============ window.addEventListener('resize', () => { const w = window.innerWidth; const h = window.innerHeight; camera.aspect = w / h; camera.updateProjectionMatrix(); renderer.setSize(w, h); composer.setSize(w, h); material.uniforms.uResolution.value.set(w, h); }); // ============ 动画循环 ============ const clock = new THREE.Clock(); function animate() { requestAnimationFrame(animate); const time = clock.getElapsedTime(); material.uniforms.uTime.value = time; material.uniforms.uCameraPos.value.copy(camera.position); const direction = new THREE.Vector3(); camera.getWorldDirection(direction); material.uniforms.uCameraDir.value.copy(direction); const right = new THREE.Vector3(); const up = new THREE.Vector3(); camera.matrixWorld.extractBasis(right, up, direction); material.uniforms.uCameraRight.value.copy(right); material.uniforms.uCameraUp.value.copy(up); controls.update(); composer.render(); } // 隐藏加载 document.getElementById('loading').style.opacity = '0'; setTimeout(() => { document.getElementById('loading').style.display = 'none'; }, 500); animate(); </script> </body> </html>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/8 17:39:46

Claude学习笔记【番外章】- Claude Code接入小米大模型MiMo

近期&#xff0c;小米推出了自己的大模型MiMo&#xff0c;同时发放百亿Token。 作为开发者&#xff0c;我们可以向小米申请Token额度&#xff0c;一般来说能拿到7亿或16亿&#xff0c;这个取决于你填写的申请资料。 一般来说申请填写后的三个工作日内就能拿到&#xff0c;笔者晚…

作者头像 李华
网站建设 2026/5/8 17:38:35

如何快速安装iPhone USB网络共享驱动:Windows用户终极解决方案

如何快速安装iPhone USB网络共享驱动&#xff1a;Windows用户终极解决方案 【免费下载链接】Apple-Mobile-Drivers-Installer Powershell script to easily install Apple USB and Mobile Device Ethernet (USB Tethering) drivers on Windows! 项目地址: https://gitcode.co…

作者头像 李华
网站建设 2026/5/8 17:37:12

效率拉满!OpenClaw最新版中文配置全过程

OpenClaw&#xff08;小龙虾&#xff09;是实用性较强的本地 AI 自动化工具&#xff0c;可在离线环境下运行&#xff0c;通过自然语言指令完成电脑端各类重复操作&#xff0c;助力用户提升日常使用与办公效率&#xff0c;不依赖云端服务也能保持稳定运行。 官方下载地址&#…

作者头像 李华
网站建设 2026/5/8 17:35:14

RT-Thread Studio V2.1.5新建工程避坑指南:解决CubeMX生成代码的编译错误

RT-Thread Studio V2.1.5与CubeMX联调实战&#xff1a;从编译错误到工程优化的完整指南 当嵌入式开发者第一次尝试将RT-Thread Studio与STM32CubeMX结合使用时&#xff0c;往往会遇到各种棘手的编译问题。这些问题不仅浪费时间&#xff0c;还可能让初学者对RT-Thread产生畏惧心…

作者头像 李华