用 p5.js 与 Processing 模拟黑洞:光子绕行轨迹 2D 可视化实战(Coding Challenge 144)

发布时间:2026/10/13 2:54:18
用 p5.js 与 Processing 模拟黑洞:光子绕行轨迹 2D 可视化实战(Coding Challenge 144)
教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载2019 年 4 月事件视界望远镜Event Horizon Telescope公布了 M87 星系中心黑洞 Powehi 的首张直接影像这张照片启发了 Coding Challenge #144用牛顿引力近似在 2D 画布上模拟光子光线沿弯曲时空环绕黑洞的轨迹。本文以仓库中 144-black-hole-visualization.md 的挑战说明为骨架结合其完整源码从物理模型、p5.js 实现拆解、Processing 移植到参数调优一步步带你复现一个可运行的黑洞光线偏折模拟器。一、挑战背景从 M87 黑洞照片到 2D 模拟原文档开篇点明了整个挑战的缘起Inspired by the recent image of the Powehi m87 black hole from the Event Horizon Telescope, I attempt to visualize the behavior or light (photons) following the path of spacetime around the black hole.即受 EHT 发布的 M87 黑洞Powehi影像启发尝试可视化光子沿黑洞周围弯曲时空运动的轨迹。这张照片首次直观展示了事件视界黑洞边界与周围发光吸积盘的轮廓——中央暗区、明亮的光环正是本项目在 2D 画布上努力复现的核心视觉要素。围绕这一主题原文档还关联了两个重要的参考视频与若干扩展资料可作为理解物理模型的背景铺垫Veritasium 的 How to Understand the Image of a Black Hole解释为什么黑洞照片看起来是那个样子涉及引力透镜、光子球等概念STEM Coding 的 Slingshot with Gravity!展示如何用万有引力做弹弓加速与本项目引力偏折光子的思路一脉相承Simulating a Black Hole!与How to draw a Black Hole等文章讨论了黑洞模拟与绘制的不同方法文档中的相关链接还区分了牛顿版本与相对论修正版本后者使用不同的积分方式见下文从牛顿近似到广义相对论一节。在本仓库中该挑战的实现位于 CodingChallenges/CC_144_Black_Hole_Newtonian同时提供p5.js与Processing两套等价实现源码完全对应适合对照学习。二、物理模型牛顿引力近似下的光线偏折在真正进入代码前先建立模拟所依赖的物理模型。本项目刻意采用牛顿引力近似仓库目录名CC_144_Black_Hole_Newtonian即表明这一点其核心假设是光子是携带质量的质点会被黑洞的引力吸引——用牛顿万有引力公式计算引力加速度光速恒定无论引力如何改变光子的运动方向其速度大小始终等于光速常量c事件视界即终点一旦光子进入黑洞的史瓦西半径Schwarzschild radius之内就认为它被黑洞吞噬停止运动。对应地源码在 sketch.js 顶部定义了三个全局常量const c 30; // 模拟世界中的光速 const G 2; // 万有引力常量 const dt 0.1; // 时间步长欧拉积分步长这里c、G都不是真实物理常数而是经过缩放、便于在画布像素尺度上呈现动画的模拟常量——这是本挑战的一个重要设计取向视觉真实性优先于数值保真度。黑洞的史瓦西半径由 blackhole.js 的构造函数计算class Blackhole { constructor(x, y, m) { this.pos createVector(x, y); this.mass m; this.rs (2 * G * this.mass) / (c * c); } // ... }即经典的史瓦西半径公式rs 2GM / c²在主程序中sketch.js黑洞被放置在画布正中心质量取 10000function setup() { createCanvas(windowWidth, windowHeight); m87 new Blackhole(width / 2, height / 2, 10000); // ... }m87这个变量名直接致敬了 M87 星系中心的真实黑洞。结合G 2、c 30可算出该黑洞的史瓦西半径约为rs (2 × 2 × 10000) / (30 × 30) ≈ 44.4像素也就是说黑洞事件视界在屏幕上是一个直径约 89 像素的黑色圆盘这个尺度既保证了视觉冲击力又为光子轨迹留出了充足的偏折空间。三、p5.js 实现拆解三个文件、三个职责p5.js 版本由三个源文件构成加载顺序见 index.htmlphoton.js光子、blackhole.js黑洞、sketch.js主流程。这种划分清晰体现了面向对象的设计思路光子是被引力作用的个体黑洞是引力场源主程序负责把它们组织到一起。3.1 主流程光子的发射与参考线sketch.js 的setup()完成两件事创建黑洞在画布左缘垂直方向上、从顶部到画布中线之间每隔 10 像素发射一束光子所有光子初始速度均为(-c, 0)即向左匀速运动start height / 2; end height / 2 - m87.rs * 2.6; for (let y 0; y start; y 10) { particles.push(new Photon(width - 20, y)); }draw()每一帧执行标准的三步循环黑洞对每个光子施力 → 光子更新位置 → 光子绘制同时画出两条水平参考线start与end方便观察光子轨迹被引力拉弯的程度function draw() { background(255); stroke(0); strokeWeight(1); line(0, start, width, start); line(0, end, width, end); for (let p of particles) { m87.pull(p); p.update(); p.show(); } m87.show(); }注意end height / 2 - m87.rs * 2.6这条线位于黑洞上方约 2.6 倍史瓦西半径处作为引力影响范围的视觉标尺。由于光子从左向右发射时只被施加以指向黑洞的加速度最终轨迹会呈现出不同程度的弯折——离黑洞越近的光子弯得越厉害直至坠入视界。3.2 Blackhole 类引力施力与事件视界判定黑洞的核心逻辑集中在pull(photon)方法blackhole.jspull(photon) { const force p5.Vector.sub(this.pos, photon.pos); const r force.mag(); const fg (G * this.mass) / (r * r); force.setMag(fg); photon.vel.add(force); photon.vel.setMag(c); if (r this.rs) { photon.stop(); } }这段代码按顺序完成了五件事求引力方向用p5.Vector.sub(this.pos, photon.pos)得到从光子指向黑洞的向量求距离force.mag()即光子到黑洞中心的距离r牛顿引力加速度fg G * mass / r²与距离平方成反比改变方向但不改变速率先photon.vel.add(force)让引力改变速度向量再photon.vel.setMag(c)把速度大小强制拉回光速c。这一步是光速恒定假设的实现关键——引力只弯曲光的传播方向不改变光速事件视界吞噬当r rs光子进入史瓦西半径内部时调用photon.stop()光子从此不再运动、不再累积轨迹。show()方法blackhole.js用三层同心圆绘制黑洞的视觉形态show() { ellipseMode(RADIUS); fill(0); noStroke(); ellipse(this.pos.x, this.pos.y, this.rs); // 黑色事件视界圆盘 noFill(); stroke(100, 100); strokeWeight(64); ellipse(this.pos.x, this.pos.y, this.rs * 3 32); // 灰色引力透镜光晕 stroke(255, 150, 0, 100); strokeWeight(32); ellipse(this.pos.x, this.pos.y, this.rs * 1.5 16);// 橙色吸积盘发光 }第一层半径rs的纯黑圆盘代表事件视界本身连光都无法逃脱的区域第二层半径rs*3 32、线宽 64 的半透明灰色圆环模拟光子球与强引力透镜造成的光晕第三层半径rs*1.5 16、线宽 32 的橙色半透明圆环对应真实黑洞照片中最醒目的高温吸积盘辐射。三者的叠色与半透明描边在白色背景上能产生类似黑洞周围发光的视觉层次这正是对 M87 照片暗影 亮环结构的高度抽象。3.3 Photon 类匀速运动、轨迹记录与停止photon.js 定义了光子对象构造函数初始化位置、速度(-c, 0)、轨迹历史数组与停止标志class Photon { constructor(x, y) { this.pos createVector(x, y); this.vel createVector(-c, 0); this.history []; this.stopped false; this.theta 0; } // ... }update()实现欧拉积分Euler method推进位置并维护一条最多 500 个点的轨迹update() { if (!this.stopped) { this.history.push(this.pos.copy()); const deltaV this.vel.copy(); deltaV.mult(dt); this.pos.add(deltaV); } if (this.history.length 500) { this.history.splice(0, 1); } }位置更新公式即pos vel × dt其中vel的方向在每一帧已被Blackhole.pull()修正过源码中保留了// if (frameCount % 5 0)的注释提示可以按帧间隔采样轨迹点以降低轨迹密度历史数组超过 500 个点时从头部移除保证轨迹线长度可控、内存占用稳定。show()先用 4 像素红色圆点绘制光子当前位置再用 2 像素红色折线把整条历史轨迹画出来show() { strokeWeight(4); stroke(255, 0, 0); point(this.pos.x, this.pos.y); strokeWeight(2); noFill(); beginShape(); for (let v of this.history) { vertex(v.x, v.y); } endShape(); }红色轨迹线正是整个可视化最直观的输出读者可以清楚看到左侧发射的一排光子在接近黑洞时轨迹逐渐弯曲靠近视界的光子被吸入稍远的光子则绕出一个弧线后继续向左下方飞出。四、Processing 移植逻辑一致、API 替换除了 p5.js仓库还提供了完整的 Processing 版本位于 Processing/CC_114_Black_Hole_Newtonian目录名沿用了历史编号 CC_114包含CC_114_Black_Hole_Newtonian.pde、blackhole.pde、photon.pde三个文件与 p5.js 版本逐行对应。两者差异集中在 API 与数据类型层面方面p5.js 版本Processing 版本画布createCanvas(windowWidth, windowHeight)fullScreen()主文件向量p5.VectorPVector粒子容器JS 数组[]push()ArrayListPhotonadd()主文件椭圆绘制ellipse(x, y, r)RADIUS 模式ellipse(x, y, r, r)以黑洞的pull()为例blackhole.pde 与 p5.js 版几乎逐行一致void pull(Photon photon) { PVector force PVector.sub(this.pos, photon.pos); float r force.mag(); float fg G * this.mass / (r * r); force.setMag(fg); photon.vel.add(force); photon.vel.setMag(c); if (r this.rs) { photon.stop(); } }p5.js 与 Processing 双版本并存的价值在于同一套物理与视觉设计可以在浏览器中零依赖运行也可以在 Processing IDE 中以全屏模式演示方便不同场景下的教学与二次开发。五、运行方式5.1 在浏览器中运行 p5.js 版本仓库中的 index.html 已经通过 CDN 引入 p5.js 1.4.1并把三个脚本按photon.js → blackhole.js → sketch.js的顺序加载。只需用浏览器打开该 HTML 文件即可看到效果script srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/p5.min.js/script script srcphoton.js/script script srcblackhole.js/script script srcsketch.js/script运行前提需要联网加载 p5.js CDN若离线环境可将 p5.min.js 下载到本地并修改src指向本地文件。页面样式设置canvas { display: block; }避免画布下方出现多余空白。5.2 运行 Processing 版本将 Processing/CC_114_Black_Hole_Newtonian 目录作为 Processing sketch 打开保持三个.pde文件在同一目录点击运行即可全屏演示。注意主文件使用fullScreen()运行环境需支持全屏窗口。六、参数调优指南模拟的观感完全由c、G、dt与黑洞质量m四个参数决定理解它们的作用即可自由调出不同的视觉效果参数位置作用调大后效果c光速sketch.js光子速度常量也是rs 2GM/c²的分母光子飞得更快轨迹更平直史瓦西半径变小G引力常量sketch.js引力加速度GM/r²的系数偏折更剧烈光子更容易被吸入视界m黑洞质量sketch.js直接线性放大rs与引力视界变大光晕与吸积盘随之放大dt时间步长sketch.js欧拉积分的步长运动速度变快过大时轨迹会出现明显锯齿失真光子间距 10sketch.js左侧发射光子的垂直间隔调小则光束更密集弯曲束流感更强调参建议若想让弯曲更明显可先增大G或减小c若想让事件视界更醒目直接增大质量m。由于牛顿模型下光子速度被强制恒定为c参数组合只要保持rs在几十像素量级通常都能得到稳定的动画。需要说明的限制欧拉积分是显式一阶方法dt过大或引力过强fg过大时光子在靠近黑洞的强场区域可能发生明显数值漂移。这也是原文档专门附上相对论修正版本web_editor: 4DvaeH0Ur的原因——更精确的模拟需要更换积分策略。七、从牛顿近似到广义相对论社区扩展方向原文档在contributions一节收录了大量社区衍生作品展示了这一模拟可以继续深挖的多个方向也印证了牛顿近似只是起点相对论修正使用测地线方程等广义相对论方法替代牛顿引力轨迹更贴近真实时空弯曲光子球与吸积盘细节模拟光子在黑洞周围的多圈环绕光子球轨道复现 M87 照片中光环的精细结构3D 化多个贡献者将模拟从 2D 画布扩展到 3D 场景在 Processing 与 p5.js 中渲染黑洞的立体形态旋转黑洞Kerr轨道加入角动量模拟旋转黑洞周围的光子轨道跨界融合有贡献者将 flocking 群体行为与黑洞引力结合FlockingBlackHole把粒子被引力偏折的模型复用到群体模拟中证明该引力-粒子框架具有通用性。这些方向与原文档visualize the behavior of light following the path of spacetime的主旨一脉相承——本项目的价值不仅是画出一张黑洞照片更是为理解引力透镜、事件视界、光子球等概念提供了一个可交互、可修改的最小模型。八、小结Coding Challenge #144 用约百行代码完成了三件事以rs 2GM/c²定义事件视界以fg GM/r²计算引力偏折以速度恒定、方向可变模拟光速不变原理。通过阅读 sketch.js、blackhole.js 与 photon.js你可以清楚地看到一条从物理公式到像素渲染的完整链路而 Processing 版本则提供了脱离浏览器、全屏演示的替代方案。在此基础上无论是调整参数观察偏折变化还是沿相对论修正 / 3D 化 / 旋转黑洞方向二次开发本项目都是一个理想的起点。赞分享教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载相关推荐2D 太阳系生成器递归行星轨道系统的 Processing 与 p5.js 实现Coding Challenge 72D 太阳系生成器递归行星轨道系统的 Processing 与 p5.js 实现Coding Challenge 7 本篇文章以 Coding Train教程文档示例工程Coding Challenge 27用 p5.js 从零实现 HTML5 Canvas 烟花模拟附 Processing 2D/3D 版本Coding Challenge 27用 p5.js 从零实现 HTML5 Canvas 烟花模拟附 Processing 2D/3D 版本 本篇技术指南教程文档示例工程洛伦兹吸引子可视化实战用 Processing 与 p5.js 实现 Lorenz 混沌系统Coding Challenge 12洛伦兹吸引子可视化实战用 Processing 与 p5.js 实现 Lorenz 混沌系统Coding Challenge 12 本文以 The Cod教程文档示例工程上一篇nixpkgs 中 nexusmods-app 的 vendored 游戏数据games.json 生成、缓存与构建集成机制解析下一篇网盘直链下载助手解锁9大网盘高速下载的终极免费方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考