threejs宇宙层级之旅:从地球到可观测宇宙
基于 Three.js 构建的宇宙层级可视化页面,用 11 个独立的 3D 场景串起从地球到全宇宙的尺度序列,支持中英文切换与键盘导航。
宇宙层级之旅 (Cosmos Dimensions)
上一篇文章介绍了工程中的太阳系模拟,这一篇介绍同工程的「宇宙层级」模块。它回答一个问题:如果把视野不断放大,从脚下这颗行星一路拉到可观测宇宙之外,会依次看到什么?
目录结构
1 | |
架构说明
1. 层级数据 (levels.ts)
用一份 LevelInfo 数组描述整个尺度序列,每个层级包含:
- 中英文名称 与量级说明(如 “直径 ~930 亿光年”)
- 科学描述 与关键数据(用于信息面板展示)
- 主题色:驱动导航圆点、信息面板等 UI 的配色
2. 场景封装 (useThree.ts)
一个轻量的 Vue Composable,把 Three.js 的样板代码收敛成可复用的能力:
- 初始化:透视相机、WebGL 渲染器、带阻尼的轨道控制器
- 动画循环:
requestAnimationFrame驱动,每帧调用当前场景注册的update回调 - 场景清理:切换层级时递归移除场景对象并
dispose几何体与材质,避免显存泄漏 - 自适应:监听窗口变化,同步相机宽高比与渲染尺寸
3. 场景工厂 (scenes/index.ts)
每个层级对应一个 createXxxScene(scene, camera) 工厂函数:往场景里添加对象、摆好相机位置,返回一个每帧执行的动画函数。CosmosViewer 持有工厂函数数组,切换层级时 clearScene() 后重新加载。
4. 视图容器 (CosmosViewer.vue)
负责层级间的过渡:切换时先让遮罩层淡出(400ms),在遮罩完全不透明时替换场景,再淡入新场景,避免生硬跳变。
11 个层级
| # | 层级 | 尺度 | 场景亮点 |
|---|---|---|---|
| 0 | 地球 | 直径 ~12,742 km | 真实纹理 + 双层大气光晕 + 云层 |
| 1 | 地月系 | 地月距离 ~38.4 万 km | 月球公转动画 + 引力连线实时跟随 |
| 2 | 太阳系 | 奥尔特云 ~2 光年 | 八大行星轨道 + 土星环 + 太阳多层光晕 |
| 3 | 银河系 | 直径 ~10 万光年 | 5 万粒子旋臂 + 银心辉光 + 太阳位置标记 |
| 4 | 本星系群 | 直径 ~1000 万光年 | 银河系/仙女座/麦哲伦云 + 星系间引力连线 |
| 5 | 室女座超星系团 | 直径 ~1.1 亿光年 | 200 个星系 + 本星系群位置标记 |
| 6 | 拉尼亚凯亚超星系团 | 直径 ~5.2 亿光年 | 巨引源脉动 + 12 条丝状结构 |
| 7 | 双鱼-鲸鱼座复合体 | 长约 10 亿光年 | 星系长城丝状结构 |
| 8 | 宇宙大尺度丝状结构 | 百亿光年级别 | 40 条随机游走丝线 + 宇宙空洞 |
| 9 | 可观测宇宙 | 直径 ~930 亿光年 | 边界球 + 8000 星系粒子 + 中心观测者 |
| 10 | 全宇宙 | 无限 / 未知 | 多层渐隐球体模拟无限延伸 |
几个实现上比较有代表性的场景:
银河系:粒子旋臂
5 万个粒子按对数螺旋分布——每个粒子先随机归属四条旋臂之一,再根据到银心的距离 dist 计算偏转角 armAngle + dist * 0.06,叠加随机散布形成旋臂与核球。颜色上 30% 的粒子偏蓝(模拟年轻恒星),其余偏黄白,银心用半透明球体叠加出辉光。太阳的位置用黄色标记点 + 脉冲光环标出,直观展示”太阳在猎户座旋臂、距银心 2.6 万光年”。
宇宙网:随机游走丝线
40 条丝状结构用随机游走(random walk)生成:从随机起点出发,每步在三个方向上随机偏移并连线,天然形成”细长弯曲”的丝状形态;再叠加 200 个星系光点与 15 个暗色”空洞”球体,还原宇宙大尺度结构的骨架形态。
全宇宙:无限感
没有边界可画,就用 10 层半径递增、不透明度极低的球壳制造纵深渐隐,配合 1.5 万个亮度随距离衰减的星系粒子(brightness = 0.8 - dist * 0.7),让画面呈现”向无限延伸”的视觉暗示;中心保留一个小小的可观测宇宙光点,暗示我们所在的位置。
交互设计
导航方式
- 底部导航条:11 个层级圆点 + 名称,毛玻璃背景,移动端可横向滚动
- 左右箭头:底部中央的「缩小 / 放大」按钮
- 键盘:
←→(以及上下方向键)直接切换层级 - 箭头按钮在首尾层级自动禁用
场景切换
每个层级都是独立场景,切换采用”清空 + 重建”策略:
1 | |
配合 useThree 的资源回收,保证反复切换不会累积 GPU 资源。
信息面板
点击导航或切层级后,侧边信息面板展示当前层级的中英文名称、尺度、科学描述和关键数据,主题色随层级联动。
技术要点
- Vue Composable 封装 Three.js:
useThree把初始化、销毁、resize、动画循环全部收敛,场景代码只关注”建什么”和”怎么动” - 工厂函数 + 更新回调:每个场景返回闭包形式的
update函数,主循环只调用它,职责单一 - 显存管理:切换场景时遍历
scene.children,逐个geometry.dispose()/material.dispose() - 数据驱动 UI:层级名称、描述、主题色全部来自
levels.ts,新增层级只需加一份数据 + 一个工厂函数
Three.js 特性与实现
渲染基座(WebGLRenderer / PerspectiveCamera / OrbitControls)
- WebGLRenderer:
antialias: true抗锯齿,alpha: true透明背景让页面 CSS 背景透出,setPixelRatio(devicePixelRatio)保证高分屏清晰度 - PerspectiveCamera:远裁剪面开到 100000——因为”全宇宙”层级里最远的粒子在 800 单位外、近处天体只有几个单位,跨度大,必须放宽深度范围
- OrbitControls:
enableDamping+dampingFactor: 0.05开启阻尼,拖拽松手后视角带惯性缓停,手感比生硬跟随好很多;每帧必须调用controls.update()驱动阻尼
粒子与几何体
- BufferGeometry + BufferAttribute:所有星场(每层 2000~15000 粒子)都是手写
Float32Array填位置和颜色,再setAttribute('position'/'color'),配合PointsMaterial({ vertexColors: true })实现单次 draw call 渲染成千上万颗不同亮度的星 - 银河系旋臂:5 万粒子按对数螺旋公式布点,颜色上 30% 偏蓝模拟年轻恒星——纯数学生成,零模型文件
- 几何体多样性:
SphereGeometry(天体/星系光晕)、RingGeometry(月球轨道盘、太阳脉冲环)、Line+setFromPoints(轨道虚线、引力连线、丝状结构),基本覆盖了”点、线、面”三类基本图元
材质策略:两类材质各司其职
- MeshPhongMaterial:用在需要光照的实心天体(地球、行星),地球还开了
specular高光模拟海面反光 - MeshBasicMaterial:用在自发光体(太阳、星系光球、标记点)——不受光源影响,配
transparent+opacity就能做出辉光 - BackSide 光晕技巧:所有”光晕”都是一个比本体大的球,材质设
side: THREE.BackSide只渲染背面,套在本体外面形成柔和的发光边缘,比后期泛光(bloom)便宜得多 - LineDashedMaterial:轨道和引力连线用虚线,调用
computeLineDistances()才能生效(容易漏的坑)
场景切换与资源回收
层级切换是”清空重建”而非”预建多个场景切 visible”,这里用到一个必须做对的流程:
1 | |
不 dispose 的话,GPU 端的缓冲区不会释放,反复切换 11 个层级会把显存吃光。useThree Composable 把这个清理收敛成一个 clearScene(),同时处理了材质为数组的情况。
动画循环注册(闭包回调)
每个场景工厂返回一个 () => void 的更新函数(自转、公转、脉冲环缩放都在里面做),主循环每帧调用当前注册的那个——setUpdate(fn) 一行切换,比维护一个 if-else 分发表干净。
技术栈
- Vue 3: 组件框架(Composition API)
- Three.js: 3D 渲染引擎
- TypeScript: 类型安全
- OrbitControls: 轨道控制器