如何用 three.js 和真实的星历把音乐网站做成一座可运转的太阳系仪

原作者: davidlilja
原文链接: https://discourse.threejs.org/t/i-built-my-music-site-as-a-working-orrery-three-js-real-ephemeris/93649


I built my music site as a working orrery (three.js + real ephemeris)

项目概览

原文展示的是一个器乐电子音乐网站 Árstraumur · Instrumental electronic music,其核心创意是将整站做成一座可交互、可运转的太阳系仪(Orrery)。网站描述为“Instrumental electronic music shaped by atmosphere, not genre…”,强调氛围驱动而非流派定义。技术栈以 three.js 为主体,并引入 真实星历(real ephemeris) 数据,让行星位置并非简单循环动画,而是基于真实天文计算。

原文为 Discourse 帖子的精简摘要,未包含完整代码块;下文的“核心代码参考”部分是基于该类型实现(three.js + 真实星历 + Orrery)梳理出的典型关键片段,方便理解其技术内核。


核心代码参考

1. 场景初始化与行星网格创建

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
  45,
  innerWidth / innerHeight,
  0.1,
  1000,
);
camera.position.set(0, 20, 40);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(devicePixelRatio);
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;

// 加载真实星历数据,返回行星位置查询器
const ephemeris = await loadEphemeris(); // 例如解析 JPL DE440 或 Kepler 元素

const planets = createPlanetMeshes(scene); // 根据配置生成球体网格

2. 基于真实星历更新行星位置

function animate(time) {
  const t = time * 0.001; // 秒 -> 时间戳,按需缩放时间

  for (const planet of planets) {
    // 从星历获取当前行星的日心/地心坐标
    const position = ephemeris.getPosition(planet.name, t);
    planet.mesh.position.set(position.x, position.y, position.z);
  }

  controls.update();
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}

3. 可选:GPU 端着色器优化轨道计算

若将部分计算移至着色器,可在顶点着色器中解析轨道参数:

// vertex shader 片段
uniform float uTime;
attribute vec3 aKepler; // 半长轴、离心率、倾角等参数

void main() {
  vec3 pos = computeOrbitalPosition(aKepler, uTime);
  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

说明:以上代码为技术推演示例,原文未提供可直接复制粘贴的代码块,但这是该类型项目常见的实现骨架。


三大核心优化要点

  1. 按需加载天文数据与纹理资源
    真实星历数据通常体积较大,不宜一次性全部加载。应使用异步加载、分块解析,并在行星进入视口或交互聚焦时才加载高精度纹理。这样可以显著降低首屏初始化时间,避免卡顿。

  2. 减少绘制调用:合并几何体与使用 Instancing
    Orrery 中可能存在大量小行星、轨道环、粒子等。若每个轨道环单独一个 draw call,帧率会迅速下降。推荐使用 THREE.InstancedMesh 渲染同类型对象,或将静态几何体合并为单个 BufferGeometry,从而将 draw call 从数百次降低到个位数。

  3. 利用 LOD 与着色器分担 CPU 计算压力
    真实星历计算如果全部在 JavaScript 主循环中逐帧执行,会占用大量 CPU 时间。可以采用 细节层次(Level of Detail):远距离行星使用简化轨道模型,近距离时再切换真实星历;同时将重复性轨道参数计算转移到 GLSL 着色器,让 GPU 并行处理,保持主线程流畅。


GLSL / WebGL 专业术语检查

以下术语在解读与实现中高频出现,确保中文表达准确:

英文术语推荐中文翻译说明
Orrery太阳系仪用于演示行星相对运动的机械或数字模型
Ephemeris星历天体在特定时间的位置数据表或计算函数
GLSLGLSLOpenGL Shading Language,保留缩写
Shader着色器运行在 GPU 上的程序,分为顶点/片元着色器
Draw call绘制调用CPU 向 GPU 发起的单次渲染指令
Instancing实例化一次绘制多个相同几何体,降低 draw call
Level of Detail (LOD)细节层次根据距离或屏幕占比切换模型复杂度
Frame rate帧率每秒渲染帧数,衡量流畅度
Texture atlas纹理图集将多张纹理合并到一张,减少纹理切换
Pixel ratio像素比物理像素与 CSS 像素的比值,影响清晰度
Antialiasing抗锯齿减少边缘锯齿,提升视觉质量
Kepler elements开普勒轨道要素描述轨道形状、方向和时间的一组参数
Heliocentric日心的以太阳为中心的坐标系
Geocentric地心的以地球为中心的坐标系

结语

该项目将音乐网站与天文可视化巧妙结合,不仅展示了 three.js 在创意交互中的潜力,也体现了真实科学数据(星历)在 Web3D 中的应用价值。通过合理的资源管理、绘制调用优化和 GPU 并行计算,可以保证这种动态天体场景在浏览器中依然流畅运行。若你正计划类似项目,以上三个优化方向会是最值得优先投入的工程重点。