Skip to content
Matrix4096Public

About

GARGANTUA — Schwarzschild black hole raytracer. Full-screen real-time GLSL null-geodesic integration, event horizon + photon ring + multiple disk crossings + Doppler beaming + gravitational redshift. No build step, vendored three.js.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

GARGANTUA — Schwarzschild Black Hole Raytracer

简体中文 · English

在线演示 → https://player4086.github.io/gargantua/ · 源码 → https://github.com/player4086/gargantua

version license three.js build webgl verify

一个全屏实时黑洞光线追踪器。屏幕上每一个像素,都是片元着色器在该像素的视线方向上 反向积分史瓦西时空零测地线得到的:光线在弯曲时空里走了多远、穿过吸积盘几次、 以多大频移逃逸到无穷远,全部现算。

场景里没有任何几何体——没有球、没有圆环、没有天空盒,只有一块铺满屏幕的三角形。

常见做法 本项目
黑色球体 + 平面圆环摆出黑洞 无任何几何体,画面由全屏三角形上的单个片元着色器生成
预渲染视频 / 截图 / 序列帧 逐帧实时积分,成本随视角与画质档变化
环境贴图假装星空 立方体面格点哈希程序化星场 + 银河带,且在逃逸渐近方向采样
贴一张"亮边"假装光子环 光子环是临界曲线附近光线绕转多圈的自然结果
手动调两侧亮度假装多普勒 由 g = √(1−3M/r)/(1−Ω·b_φ) 与 g⁴ 精确计算

物理量一览:r_s = 1 · 事件视界 r = 1 · 光子球 r = 1.5 · ISCO r = 3 · 临界碰撞参数 b_crit = 3√3/2 ≈ 2.598


目录

  1. 快速开始
  2. 操作与快捷键
  3. 画面控制
  4. 自动化接口
  5. 实现原理
  6. 工程结构
  7. 验证与测试
  8. 部署与缓存
  9. 故障排查 FAQ
  10. 已知限制
  11. 许可与更新日志

1. 快速开始

cd gargantua

# A. 内置零依赖静态服务器(推荐)
node tools/serve.mjs --port 8123        # → http://127.0.0.1:8123/
npm start                               # 等价写法

# B. 任何静态服务器都行(不需要 Node)
python -m http.server 8123
npx serve -l 8123

必须通过 HTTP 打开:file:// 下 ES Module 与 import map 会被浏览器安全策略拦截。 需要支持 WebGL2 与 import map 的浏览器(Chrome / Edge / Firefox / Safari 16.4+)。

测试与验证

npm test                # 快速档:静态审计 + 单元测试 + GLSL + API + 物理/视觉验收
npm run test:full       # 全量档:外加 21 项参数响应、0–9 调试视图、4 个预设
npm run test:unit       # 41 项单元测试(在 Node 中直接跑真实模块)
npm run test:glsl       # 用真实 GLSL ES 1.0 语法分析器解析全部着色器
npm run test:api        # 跨模块 API:THREE 符号、方法解析、uniform 写通、DOM/CSS 契约
npm run test:physics    # 物理与图像结构验收(CPU 参考渲染器)

开发工具

npm run ref             # CPU 参考渲染器输出 PNG(默认 docs/ref-preset0.png)
npm run docs:images     # 渲染 4 预设 + 湍流时序 + 调试视图到 docs/
npm run view            # 终端 ASCII 亮度图 + 径向/横向剖面
npm run check:served    # 通过运行中的服务器逐一校验 27 个资源
npm run shot            # 无头浏览器截图(需本机可运行 Chromium,见 §7)
npm run audio           # 重新合成氛围音乐 WAV
npm run vendor          # 重新下载并 vendor 指定版本的 Three.js

2. 操作与快捷键

输入 作用
拖拽 / 单指滑动 环绕旋转(OrbitControls,带阻尼)
滚轮 / 双指捏合 拉近拉远(2.6 – 260 r_s)
右键拖拽 / 双指拖动 平移观察目标
⇧1 – ⇧4 四个电影视角预设(1.6 s 缓动过渡)
Tab 循环切换视角预设
C 电影镜头循环开/关(缓慢公转 + 倾角呼吸 + 距离呼吸)
G 循环切换画质档
0 – 9 调试视图(见 §3.3)
P 参数面板开/关
H 隐藏 / 显示整个 HUD
空格 暂停 / 继续吸积盘时间演化
F 全屏
S 保存 PNG 截图
M 氛围音乐开/关
R / ⇧R 相机复位 / 全部参数复位
+ / - 曝光增减
Esc 关闭参数面板

参数面板中拖动滑块调节,双击滑块恢复该项默认值。


3. 画面控制

3.1 二十一项参数

# 键 名称 范围 默认 说明
1 diskInner 盘内半径 1.7 – 8 r_s 3.0 内缘(ISCO = 3 r_s)
2 diskOuter 盘外半径 5 – 30 r_s 12.0 外缘,带软边衰减
3 diskThick 盘厚度 0.02 – 1.2 0.22 半厚,随半径张开(flaring)
4 diskTemp 峰值温度 2000 – 30000 K 9400 驱动黑体色温
5 diskBright 发射亮度 0 – 8 1.50 辐射强度倍率
6 diskOpacity 光学厚度 0 – 8 2.60 τ,控制遮挡与半透明
7 turbAmp 湍流强度 0 – 1 0.66 噪声对密度的调制深度
8 turbScale 湍流尺度 0.6 – 8 4.40 噪声空间频率
9 diskSpin 旋转速度 0 – 4 × 1.00 开普勒角速度时间缩放
10 diskTilt 盘面倾角 −80 – 80° 0 盘法线相对世界 +Y 的倾斜
11 lensing 引力透镜强度 0 – 2 × 1.00 3M u² 项倍率,1 = 物理
12 doppler 多普勒增亮 0 – 1.5 × 1.00 g_doppler 指数,1 = 物理
13 redshift 引力红移 0 – 1.5 × 1.00 g_grav 指数,1 = 物理
14 steps 积分步数 80 – 700 320 每像素测地线积分预算
15 starDensity 星场密度 0.3 – 3 × 1.00 三层星场共用倍率
16 starBright 星场亮度 0 – 3 × 1.00
17 galaxyBright 银河亮度 0 – 3 × 1.00
18 exposure 曝光 0.05 – 4 1.00 线性 HDR 曝光
19 bloomStrength 泛光强度 0 – 3 0.92 UnrealBloomPass
20 bloomRadius 泛光半径 0 – 2 0.62
21 bloomThreshold 泛光阈值 0 – 3 0.85

另有 8 个不计入 21 项的开关:泛光 / 胶片颗粒 / 暗角 / 色散 / 渐进累积 AA / 盘面动画 / 氛围音乐 / 统计面板。暗角 0.62、颗粒 0.045、色散 0.55 是调校过的固定强度 (即"轻微色散"),随开关启停。

参数表、预设、调试视图、画质档全部集中在 js/params.js——HUD、持久化与 URL 接口都由它派生, 改一处即可全局生效。

3.2 四个视角预设

键 名称 距离 倾角 FOV 特点
⇧1 边缘掠影 EDGE-ON GRAND 26 r_s 86.5° 34° 近边缘视角,次级透镜像与光子环最清晰
⇧2 极轴俯瞰 POLAR CASCADE 30 r_s 22° 38° 近极轴,完整环形盘面与螺旋湍流
⇧3 光子环近观 PHOTON RING 15 r_s 84.5° 42° 贴近临界曲线,强透镜、多重像、大阴影
⇧4 远景全貌 WIDE VISTA 46 r_s 64° 28° 远景构图,银河背景与盘面全景

倾角定义:0° = 沿盘法线俯视(正面),90° = 与盘面共面(边缘)。

3.3 十个调试视图(0 – 9)

键 视图 内容
0 最终合成 ACES + Bloom + 暗角 + 颗粒 + 色散
1 原始 HDR 线性 HDR 缓冲,未经色调映射(查看真实动态范围)
2 透镜方向场 逃逸方向编码为 RGB,直接显示引力透镜如何扭曲天球
3 积分步数热图 每像素实际积分步数 / 预算;红 = 落入视界
4 吸积盘单独 关闭星空,只看盘面辐射与遮挡
5 多普勒因子 g 蓝 = 蓝移(接近侧),红 = 红移(远离侧)
6 引力红移因子 √(1 − r_s/r) 分布
7 捕获掩码 红 = 落入视界,蓝 = 逃逸到无穷远
8 星空与银河 关闭吸积盘,检查程序化天球
9 HDR 余量 蓝 < 1,绿 1…4,红 > 4(过曝区分析)

3.4 三档画质与设备适配

档 像素比上限 积分步数 步长 κ 渐进累积
Standard 标准 1.0 190 0.030 关
High 高 1.5 330 0.022 关
Cinematic 电影级 2.0 620 0.016 开(静止时最多 12 帧)
  • 启动时探测移动端 UA / 粗指针 / 软件渲染 / 核心数 ≤ 4 → 默认 Standard,否则 High。
  • devicePixelRatio 按档封顶,超大视口再做面积自适应缩放,保证片元着色器维持交互帧率。
  • 连续 4 次采样低于 24 FPS 时自动降档并提示(可手动 G 切回)。
  • 标签页隐藏时暂停渲染循环,回到前台自动恢复并丢弃累积的时间差。
  • 触摸设备 touch-action: none;窄屏下 HUD 折叠为底部控制条。

4. 自动化接口

4.1 URL 参数

任何参数都能写进 URL,用于复现一个确定性画面(?shot=1 会关闭电影镜头、开启累积 AA、冻结音频):

http://127.0.0.1:8123/?shot=1&preset=0&t=2&quality=cinematic&accum=24
http://127.0.0.1:8123/?debug=3&cam=18,75,210,28
http://127.0.0.1:8123/?p=diskTemp:14000,diskBright:3.2,doppler:1.4
http://127.0.0.1:8123/?reset=1&hud=0
参数 说明
shot=1 确定性截图模式:关闭电影镜头与音频,开启累积 AA,渲染完成后置 window.__GARGANTUA_READY__ = true
preset=0..3 视角预设
cam=r,inc,az,fov 直接指定相机球坐标
debug=0..9 调试视图
quality=standard|high|cinematic 画质档
t=<秒> 冻结盘面时间(可复现同一湍流形态)
p=key:value,key:value 覆盖任意参数(非法值自动钳制)
accum=1..64 累积帧数
hud=0 / cine=0 隐藏 HUD / 关闭电影镜头
reset=1 忽略并清除 localStorage 中的持久化状态
w= / h= 供自动化脚本记录目标分辨率(配合 CDP 视口覆盖使用)

4.2 页面内 API(window.GARGANTUA)

GARGANTUA.setParam('diskTemp', 12000);   // 任意 21 项参数
GARGANTUA.setQuality('cinematic');
GARGANTUA.setPreset(0);
GARGANTUA.setDebug(3);
GARGANTUA.setCamera({ distance: 20, inclination: 80, azimuth: 45, fov: 36 });
GARGANTUA.setTime(2.0);                  // 冻结湍流时刻
await GARGANTUA.renderFrames(24);        // 累积 24 帧(渐进 AA)
const stats = GARGANTUA.analyze();       // 画布像素统计
const png   = GARGANTUA.capture();       // data:image/png;base64,…
GARGANTUA.screenshot();                  // 触发下载
GARGANTUA.stats();                       // 帧率 / 分辨率 / 步数 / 渲染信息
GARGANTUA.build;                         // 当前模块版本戳

analyze() 返回 mean / max / p95 / p99 / darkFrac / brightFrac / center / ring / histogram, 可直接用于自动化视觉验收——本项目的验收脚本正是这么做的。

4.3 无头浏览器截图

node tools/shot.mjs --url "http://127.0.0.1:8123/?shot=1&preset=0&t=2" \
     --out docs/shot-preset0.png --w 1600 --h 900 --accum 24

脚本用零依赖 CDP 驱动本机 Chromium:等待 __GARGANTUA_READY__、收集控制台错误与未捕获异常、 调用 analyze() 输出像素统计并保存 PNG;任何控制台错误都会让退出码非 0。


5. 实现原理

完整推导与数值稳定性清单见 docs/PHYSICS.md。要点:

测地线积分. 由相机位置与视线确定轨道平面,以 u = 1/r 为变量积分 d²u/dφ² = −u + 3M u²(四阶 Runge–Kutta)。步长 h = clamp(κ/u, h_min, h_max), 并额外限制单步 |Δu| ≤ 0.45u——没有这一条,远场光线会一步跨到 u < 0 而被误判成落入视界。

终止条件. u ≥ 1/r_s → 落入事件视界,返回纯黑;到达逃逸半径且向外 → 采样天球。

光子环与多重像. 临界曲线附近的光线会在光子球附近绕转多圈,每次穿过盘面都被记一次, 于是次级、三级透镜像自动出现,极窄的亮环也自然成形。

吸积盘. 盘面是过原点的倾斜平面。每一步检测 dot(pos, n_disk) 是否变号, 插值出交点后做发射–吸收合成(前向透射率),因此近侧盘面会遮挡远侧与背景星空。

频移. 对开普勒圆轨道发射体,精确给出

g = ν_obs / ν_emit = √(1 − 3M/r) / (1 − Ω·b_φ),   Ω = √(M/r³),  b_φ = L_z/E(沿光线守恒)

观测温度 T_obs = g·T_emit,玻尔兹曼亮度按 g⁴ 缩放——这正是边缘视角下接近侧比远离侧亮数倍的原因。

其余. 盘面温度用 Shakura–Sunyaev 剖面 T ∝ r^(−3/4)(1−√(r_in/r))^(1/4); 颜色用 Kim 等人 CIE 色度拟合 + XYZ→线性 sRGB;天球用立方体面格点哈希星场(三层) 加银河带,并在逃逸渐近方向采样,因此引力透镜对星空的扭曲是免费且物理正确的; 盘面湍流在共转方位角 ψ' = ψ − Ω(r)·t 上采样 3D 噪声,较差自转自动把结构剪切成螺旋细丝。


6. 工程结构

gargantua/
├── index.html                 全屏 canvas + import map + 三层遮罩
├── README.md / README.en.md   中文 / English 说明文档
├── CHANGELOG.md               版本变更
├── LICENSE                    MIT
├── css/style.css              HUD / 面板 / 遮罩 / 响应式
├── js/
│   ├── main.js                入口:启动、window.GARGANTUA、首帧看门狗
│   ├── app.js                 渲染循环、状态机、上下文恢复、截图/分析 API
│   ├── params.js              21 项参数 + 4 预设 + 10 调试视图 + 3 画质档(唯一真源)
│   ├── build.js               版本戳(同时是模块缓存失效查询串的真源)
│   ├── store.js               localStorage 持久化(版本化、防御式读取)
│   ├── camera.js              OrbitControls + 球坐标 + 电影镜头 + 缓动过渡
│   ├── postfx.js              RenderPass → 累积 AA → UnrealBloom → 最终合成
│   ├── hud.js                 由参数表生成的 HUD 与面板
│   ├── audio.js               氛围音乐(WAV + WebAudio 处理,失败时程序化兜底)
│   ├── quality.js             设备探测与像素比/步数策略
│   └── shaders/
│       ├── common.glsl.js     哈希 / 噪声 / fbm / 黑体 / ACES / sRGB
│       ├── geodesic.glsl.js   ★ 测地线积分 + 吸积盘 + 星空 + 银河
│       └── post.glsl.js       色散 / 曝光 / ACES / 暗角 / 颗粒 / 累积 / HDR 余量
├── vendor/three/              本地 Three.js r160.1(构建产物 + 10 个 addon)
├── assets/
│   ├── audio/gargantua-ambient.wav   48 s 无缝循环无人机音(程序化合成)
│   └── img/favicon.svg
└── tools/                     开发/测试工具(不参与运行时)
    ├── serve.mjs              零依赖静态服务器
    ├── verify.mjs             一键全量验证流水线(--report 输出报告)
    ├── unit.mjs               41 项单元测试(在 Node 中跑真实模块)
    ├── glsl-check.mjs         真实 GLSL 解析器 + 作用域 + uniform 契约审计
    ├── api-check.mjs          THREE 符号 / 方法解析 / uniform 写通 / DOM-CSS 契约
    ├── refcheck.mjs           GLSL ↔ CPU 参考实现数值字面量一致性
    ├── accept.mjs             物理与图像结构验收(含弱场偏折、临界碰撞参数)
    ├── refrender.mjs          ★ 片元着色器的 CPU 逐行对照实现(可输出 PNG)
    ├── refview.mjs            终端 ASCII 亮度图 + 剖面分析
    ├── raydebug.mjs           单像素逐步光线追踪日志
    ├── shot.mjs               零依赖 CDP 无头浏览器截图/验收
    ├── check-served.mjs       通过运行中的服务器校验全部资源
    ├── make-docs-images.mjs   文档图像批量渲染
    ├── make-audio.mjs         氛围音乐 WAV 合成器
    ├── push-via-api.mjs       git 端点不可用时的 REST API 推送兜底
    └── fetch-vendor.mjs       Three.js vendor 下载器

docs/
├── PHYSICS.md                 完整物理推导与数值稳定性清单
├── TEST-REPORT.md             验证流水线的完整输出(--report 自动生成)
├── ref-preset0…3-*.png        4 个视角的 CPU 参考渲染
├── ref-timelapse-t*.png       盘面湍流时序(t = 0 / 2.5 / 5 / 7.5 s)
└── ref-debug0…8.png           0–8 号调试视图

7. 验证与测试

node tools/verify.mjs 一条命令跑完全部检查,当前 111/111 通过, 完整输出见 docs/TEST-REPORT.md。验证分为四层:

① 静态审计. 资源清单、无外部依赖(运行时零网络请求)、import map 正确性、 全部 JS 以 ESM 解析、相对 import 可解析、缓存版本图一致、vendored three.js 完整性、 WAV 头、CSS 括号与响应式断点。

② 跨模块 API 审计. 每个 THREE.* 符号都存在于 vendored 构建;每个 this.<协作对象>.<方法>() 都能解析到已声明的方法;每个被 JS 切换的 CSS 类都有样式规则; 每个 getElementById 都能在 HTML 中找到。 这条检查在开发中真实抓出过两个会导致黑屏的缺陷:uResolution 从未写入、 renderer.setSize() 从未调用(画布会停在默认 300×150 后备缓冲)。

③ GLSL 审计. 用真实 GLSL ES 1.0 语法分析器解析运行时真正交给 WebGL 的字符串 (不是重建的副本),检查语法、作用域、uniform 契约(名称/类型/是否真的被赋值), 并断言「着色器调用的每个辅助函数都存在于装配后的源码」与「不存在未定义函数调用」。 后者能精确复现后处理通道漏拼 GLSL_COMMON 时驱动报出的 linearToSRGB / acesFilmic / hash13 / luma 四个符号。

④ 物理与图像结构验收. tools/refrender.mjs 是片元着色器的 CPU 逐行对照实现, tools/refcheck.mjs 逐函数比对两者的数值常量(14/14 一致),保证它不会悄悄偏离; 在此基础上直接积分测地线方程验证物理量,并对渲染结果做结构断言:

physics · b < b_crit 被捕获                      captured=true, minR=1.000
physics · b > b_crit 逃逸                        φ=8.053 rad
physics · 临界曲线多次绕转                        φ=11.26 rad (1.79 圈)
physics · 弱场偏折角 ≈ 4M/b                      err=2.35%
physics · ISCO 轨道速度                           v=0.500000 c
image   · 事件视界阴影深黑                        0.0238 / 0.0039
image   · 多普勒增亮左右不对称                     ratio=2.05
image   · 多次吸积盘穿越(垂直切面)                5 条亮带
image   · 引力透镜次级像(阴影上下均有盘光)         top=0.0449 bottom=0.0987
image   · 星空背景极暗                            0.0129

关于 GPU 侧验证. 本项目的开发沙箱无法运行 Chromium(多进程 IPC 被环境策略拦截), 因此 GPU 侧由上述四层证据链替代;浏览器侧脚本(tools/shot.mjs、tools/check-served.mjs) 随源码交付,在本机执行即可得到真实截图与 HTTP 层校验。


8. 部署与缓存

本项目没有构建步骤,也就没有内容哈希文件名。为了不让「新 index.html + 旧模块」 这种混合状态出现,所有模块的相对 import 都带 ?v= 版本号,版本号只在 js/build.js 声明一次,tools/verify.mjs 会断言它们全部一致:

import { PostFX } from './postfx.js?v=1.0.1';   // 缓存键包含查询串

刷新发布版本时:改 js/build.js 的 VERSION,把 js/ 与 index.html 里的 ?v= 字面量一起替换(一次全局替换),再跑一次 verify 检查是否漏改。

部署到静态托管后如果仍然看到旧版本:

  • 最可靠:打开带查询串的地址,例如 https://<user>.github.io/gargantua/?v=1.0.1 —— 文档 URL 变化会绕过 HTML 缓存,模块 URL 自带版本号会绕过模块缓存。
  • 或者 Ctrl+Shift+R 强制刷新 / 使用无痕窗口。
  • 控制台首行会打印当前模块版本,可直接与部署版本比对;着色器编译失败时错误浮层也会打印。

9. 故障排查 FAQ

打开后弹出「无法启动渲染器 · 着色器编译/链接失败」,报 no matching overloaded function? 几乎总是浏览器缓存了旧模块,而不是代码问题。判断方法:three.js 会在片元着色器前 拼接约 55 行的 prefix,所以 驱动行号 − 55 = 源码行号。用带版本的地址打开即可绕过 (见 §8),或 Ctrl+Shift+R / 无痕窗口。

提示「无法创建 WebGL 上下文 / WebGL 初始化失败」? 浏览器未启用硬件加速,或设备不支持 WebGL2。Chrome/Edge 可检查 设置 → 系统 → 使用硬件加速,或在地址栏打开 chrome://gpu 查看 WebGL2 状态。

提示「渲染超时:GPU 未能在 30 秒内完成首帧」? 多见于软件渲染(SwiftShader)或驱动异常。页面有 30 秒看门狗以避免无声黑屏; 可先降到 Standard 档(G)再试。

用 file:// 直接双击打开没反应? ES Module + import map 在 file:// 下会被安全策略拦截,必须通过 HTTP 打开(见 §1)。

听不到氛围音乐? 浏览器禁止无交互自动播放。按 M 或点击 HUD 的「氛围音乐」按钮即可(这也构成用户手势); 音频资源不可用时会自动退化为程序化合成。

帧率低 / 画面很糊? 脚本会在帧率持续偏低时自动降档并提示;也可手动 G 循环三档。移动端默认从 Standard 起步。

怎么拿到干净的高分辨率截图? 按 S 直接下载;或用确定性模式 ?shot=1&preset=0&t=2&quality=cinematic&accum=24 (冻结湍流 + 累积 AA);脚本化见 §4.3。


10. 已知限制

  • 仅实现史瓦西(无自旋)度规;克尔黑洞的参考系拖拽不在范围内。
  • 吸积盘采用薄板 + 光学厚度近似(单次穿越的发射–吸收解析合成),不是完整的辐射转移求解器。
  • 湍流是程序化噪声而非磁流体力学模拟,仅用于视觉与演化感。
  • 积分步数有预算上限,极端贴近临界曲线的光线在低步数下可能少绕半圈(调试视图 3 可直接看到)。
  • 渐进累积 AA 在相机/参数变化时自动重置,因此只有静止时才会收敛到干净边缘。

11. 许可与更新日志

代码以 MIT 许可发布。vendor/three/ 为 Three.js r160.1 的原始构建产物, 遵循其 MIT 许可。版本变更见 CHANGELOG.md。

About

GARGANTUA — Schwarzschild black hole raytracer. Full-screen real-time GLSL null-geodesic integration, event horizon + photon ring + multiple disk crossings + Doppler beaming + gravitational redshift. No build step, vendored three.js.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages