跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表

目录

  1. 基于 WebGL(Three.js)技术实现的广义相对论着色器引擎
  2. 架构概览:CPU-GPU 混合计算
  3. 物理引擎实现(粒子系统)
  4. 虚实粒子渲染
  5. 系统架构
  6. 着色器中的虚实融合
  7. 特殊效果:虚粒子的"实体化"模拟
  8. 广义相对论渲染
  9. 多重黑洞处理
  • 💰 8折买阿里云服务器限时8折了解详情
JavaScript大前端算法

WebGL 黑洞着色器:广义相对论真实吸积盘效果

WebGL 黑洞着色器基于 Three.js 引擎实现广义相对论吸积盘视觉效果。系统采用 CPU-GPU 混合架构,CPU 负责 N-body 轨道积分与粒子生命周期管理,GPU 处理引力红移、多普勒效应及相对论集束。核心渲染算法模拟光线弯曲以近似引力透镜和爱因斯坦环,支持多重黑洞线性叠加。通过虚实粒子渲染技术降低计算开销,结合半隐式欧拉积分平衡物理模拟稳定性与性能。代码实现涵盖吸积盘内落、视界消隐及光子环合成,在浏览器端实现了接近电影级特效的低成本方案。

链路追踪发布于 2026/4/5更新于 2026/4/211 浏览

基于 WebGL(Three.js)技术实现的广义相对论着色器引擎

该引擎采用了一种新型的合成方式来实现真正近似黑洞吸积盘的效果。该引擎能够很好地体现在吸积盘高速旋转时产生的红移和蓝移效应。

这是一种极低成本得到最近似真实黑洞影响的一种实现方式。

图 1 红移着色器开到最高的效果图

图 2 没有开红移着色器的效果

图 1 是红移着色器开到最高的效果图,图 2 是没有开红移着色器的效果。现在的黑洞吸积盘是顺时针旋转的,能很明显的对比出红移的效果。另外还有一个较为极端的红移展示(这样子的吸积盘表现是因为设定时是一个漏斗形状的初始状态)。

极端红移展示

效果展示的差不多了,我们来聊聊具体实现。

架构概览:CPU-GPU 混合计算

为了在保持物理交互性的同时实现数十万粒子的相对论视觉特效,我们采用了一种分层架构:

层级负责内容数据类型
CPU(JavaScript)N-body 轨道积分、碰撞检测、吸积逻辑、粒子生命周期Float32Array
GPU(Vertex Shader)引力红移、多普勒效应、相对论集束、引力透镜、颜色混合attribute vec3
GPU(Fragment Shader)纹理、光晕、像素合成gl_FragColor

物理引擎实现(粒子系统)

这里实现了基础的多体运动 N-body,洛希极限解体(这里为了简便直接简化为继承原有运动状态的粒子团了)。这里采用了半隐式欧拉积分。它算得足够快,对于复杂的实时模拟上来看,它在稳定性和性能之间取得了平衡。为了实现红移,我把粒子当成光子,计算它相对于摄像机的速度,远离就是更'红'更暗,靠近就更'蓝'更亮(也实现了多普勒效应)。

// 伪代码:核心物理循环
for(let i = 0; i < particleCount; i++) {
    // 1. 计算合力 (牛顿引力)
    vec3 acc = calculateGravity(position[i], attractors);
    // 2. 更新速度
    velocity[i] += acc * dt;
    // 3. 更新位置
    position[i] += velocity[i] * dt;
    
    ((position[i], blackhole) <  * ) {
        (i); 
    }
}
  • 💰 8折买阿里云服务器限时8折购买
  • 🦞 5分钟部署阿里云小龙虾了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog

更多推荐文章

查看全部
  • AI 创作者崛起:掌握核心工具与 AMA 互动成长
  • OpenClaw 多端交互实测指南:Web、TUI 与钉钉集成配置
  • Python 使用 Web Unlocker API 抓取亚马逊数据
  • 自然语言处理在法律领域的应用与实战
  • Microi 吾码在服务器虚拟化中的应用与资源管理
  • OpenClaw 多飞书机器人配置指南
  • AI 工具前端提示词实战:从设计原则到工程化落地
  • 实测 ToClaw 信息检索与分析能力:AI 实现先找再写

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Keycode 信息

    查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

  • Escape 与 Native 编解码

    JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

  • JavaScript / HTML 格式化

    使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

  • JavaScript 压缩与混淆

    Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

// 4. 史瓦西半径检查 (视界吸收)
if
distance
SchwarzschildRadius
1.05
respawnParticle
// 粒子落入黑洞,重置
虚实粒子渲染

这里涉及到一种独特的渲染方法。为了模拟吸积盘等的绚丽场景,我们不能真的渲染几百万个粒子吧,那样我们的电脑可能就会爆炸了。 我们可以通过一个真实的粒子运动来推断它四周的粒子云的运动方式应该是类似的。这样就可以大大减少计算的数量达到更多的真实的粒子效果。

系统架构
class ParticleManager {
    // 三个核心缓冲区
    positions = new Float32Array(maxCount * 3); // 位置 (虚拟)
    velocities = new Float32Array(maxCount * 3); // 速度 (虚拟)
    colors = new Float32Array(maxCount * 3); // 颜色 (虚拟)

    // 虚实连接点:从实体位置初始化虚拟粒子
    emit(pos, vel, count, radius, spread, colorHex) {
        // 基于实体位置生成虚拟粒子云
        this.positions[idx] = pos.x + r * Math.sin(phi) * Math.cos(theta);
        // ... 速度继承 + 随机扩散
    }
}
着色器中的虚实融合
// 顶点着色器关键逻辑
void main() {
    vec3 physPos = position; // 虚拟粒子位置
    vec3 renderPos = physPos; // 渲染位置(可能被透镜扭曲)
    // 但计算红移时,考虑实体黑洞的位置
    for(int i = 0; i < uBHCount; i++) {
        vec3 bhPos = uBHPositions[i]; // 实体黑洞位置
        float r = distance(physPos, bhPos); // 虚粒子到实体的距离
        // 实体影响虚粒子的渲染效果
        totalGravPotential += uRs[i]/max(r, uRs[i]*1.01);
    }
    // 虚粒子颜色受实体引力影响而改变
    if(totalShift > 1.1) {
        finalColor = mix(baseColor, vec3(0.4,0.6,1.0), t);
    }
}
特殊效果:虚粒子的"实体化"模拟
// 吸积盘的内落效果
// 在粒子更新循环中
if(CONFIG.enableAccretion && dist < b.radius * 1.2) {
    b.absorbMass(CONFIG.PARTICLE_MASS); // 虚拟粒子被实体吸收
    this.lifetimes[i] = 0; // 虚拟粒子消失
    this.positions[idx] = 999999; // 移出视野
}

// 粒子坠入黑洞的视觉效果
// 在着色器中视界消隐
for(int i = 0; i < 2; i++) {
    float r = distance(physPos, uBHPositions[i]);
    float rs = uRs[i]; // 虚拟粒子在视界内透明度为 0(模拟被吞噬)
    vAlpha *= smoothstep(rs * 0.95, rs * 1.1, r);
}

广义相对论渲染

我采用了一种类似于星际穿越剧组渲染黑洞的方法。通过逆向解析光线弯曲来实现引力透镜和爱因斯坦环的近似效果。 我简化了一些物理原理。

简化假设:

  • 平面近似:假设所有光线在同一平面内传播
  • 弱场近似:使用线性叠加而非精确场方程
  • 静态场:忽略黑洞运动带来的效应
  • 单次散射:忽略光线绕行多圈的情况

首先是收集黑洞的信息:

// 从所有天体中找到黑洞
const bhs = bodies.filter(b => b.isBlackhole);
// 按质量排序(最重要的先处理)
bhs.sort((a,b) => b.mass - a.mass);
// 限制数量(性能优化)
const maxLensCount = 4; // 最多支持 4 个黑洞同时透镜
lensPass.uniforms.uCount.value = Math.min(bhs.length, maxLensCount);

坐标变换(世界坐标到屏幕坐标)因为计算效果是通过摄像机的视角进行计算的。

for(let i = 0; i < maxLensCount; i++) {
    if(i < bhs.length) {
        const bh = bhs[i];
        // 关键步骤:3D 世界坐标 → 2D 屏幕坐标
        // 1. 使用 Three.js 的 project() 方法
        const p = bh.position.clone().project(camera);
        // 此时 p.x, p.y 范围是 [-1, 1],z 是深度
        // 2. 归一化到 [0,1] 范围
        lensPass.uniforms.uPositions.value[i].set(
            p.x * 0.5 + 0.5, // 映射到 [0,1]
            p.y * 0.5 + 0.5,
            p.z
        );
        // 3. 计算屏幕空间史瓦西半径
        // 物理半径 → 视角 → 屏幕像素
        const dist = camera.position.distanceTo(bh.position);
        const physicalRs = bh.calcSchwarzschildRadius(); // 2GM/c²
        const angularRs = physicalRs / dist; // 弧度
        const screenRs = angularRs * 2.0; // 经验缩放因子
        lensPass.uniforms.uRadii.value[i] = screenRs;
    } else {
        // 填充无效数据
        lensPass.uniforms.uRadii.value[i] = 0;
    }
}

着色器:

// lensing-fs
uniform sampler2D tDiffuse; // 输入纹理(已渲染的场景)
uniform vec2 uResolution; // 屏幕分辨率
uniform int uCount; // 黑洞数量(0-4)
uniform vec3 uPositions[4]; // 黑洞屏幕位置 (x,y,z)
uniform float uRadii[4]; // 屏幕空间史瓦西半径
uniform float uStrength; // 透镜强度参数
varying vec2 vUv;

void main() {
    // 初始化为原始 UV 坐标
    vec2 uv = vUv;
    vec2 finalUv = uv;
    float totalMask = 0.0; // 视界遮罩
    float photonRing = 0.0; // 光子环累积

    // 考虑屏幕宽高比(保证圆形扭曲)
    vec2 aspect = vec2(uResolution.x / uResolution.y, 1.0);

    // 遍历所有黑洞
    for(int i = 0; i < 4; i++) {
        if(i >= uCount) break; // 跳过无效黑洞

        // 获取当前黑洞参数
        vec2 center = uPositions[i].xy; // 屏幕位置 (x,y)
        float rs = uRadii[i]; // 屏幕空间史瓦西半径

        // 计算当前像素到黑洞的距离(考虑宽高比)
        vec2 dir = (uv - center) * aspect;
        float dist = length(dir);

        // 视界处理
        if(dist < rs) {
            // 像素在视界内,标记为完全屏蔽
            totalMask = 1.0;
        }

        // 扭曲计算
        if(dist > rs) {
            // 关键公式:偏转角度 ∝ 1/(距离 - rs)
            float deflection = (rs * uStrength * 0.12)/max(dist - rs, 0.001);
            // 限制最大偏转(防止过度扭曲)
            deflection = min(deflection, 0.5);

            // 计算扭曲方向:从黑洞指向像素
            vec2 offset = normalize(uv - center) * deflection;
            // 逆向采样:应用扭曲
            finalUv -= offset;

            // 光子环效果
            // 在 1.1 倍史瓦西半径附近添加发光效果
            float glow = exp(-(dist - rs * 1.1)*30.0/(rs + 0.001));
            photonRing += glow * 0.8;
        }
    }

    // 最终颜色合成
    // 1. 采样扭曲后的纹理
    vec4 color = texture2D(tDiffuse, finalUv);
    // 2. 添加光子环颜色(橙黄色)
    vec4 ringColor = vec4(1.0,0.7,0.3,1.0) * photonRing;
    // 3. 应用视界遮罩:视界内为纯黑色
    gl_FragColor = mix(color + ringColor, vec4(0.0,0.0,0.0,1.0), totalMask);
}

最关键的就是逆向扭曲算法: Q = P − Σ δ_i(P) 其中 δ_i(P) 是黑洞 i 在像素 P 处产生的偏转。 偏转函数: δ(r) = k ⋅ r_s / (r - r_s), r > r_s 其中: r: 像素到黑洞中心距离 r_s: 屏幕空间的施瓦西半径 k: 可调节系数

多重黑洞处理

δ_total(P) = Σ δ_i(P) 采用了线性叠加的方案,这里是和真实的大相径庭的,当黑洞相互靠近时,非线性效应显著,会产生复杂的焦散线和多重镜像。但是暂时没办法只能妥协。

最后较为复杂的实现点就这些,也是一些必要的简化部分。系统还有一些小 Bug 和缺失的功能,无伤大雅后面会慢慢补上。

  • MAVROS 安装与基础知识梳理及 ROS C++ 仿真案例
  • 3 月 AI 观察:OpenAI GPT-5.4 上线,Google 强化 Gemini,Anthropic 押注企业级
  • ToClaw 与 OpenClaw:AI 数字助理的功能定位对比
  • DeepSeek 中冷启动数据与多阶段训练的作用
  • 人工智能(AI)常见面试题及答案汇总
  • OpenClaw 开源 AI 智能体框架技术解析与部署指南
  • Z-Image-Turbo_Sugar 脸部 Lora 模型快速部署与使用教程
  • FPGA 基础概念与架构面试题详解
  • 千笔 AI 学术写作工具核心功能解析
  • 基于腾讯云轻量应用服务器部署 OpenClaw 并接入 QQ 与飞书机器人
  • HarmonyOS 6 视频封面智能生成与 AI 集成实战
  • Windows 部署 OpenAkita 并接入飞书模块,搭建本地 AI 助手