
简介本资源是一套基于Vue3与Three.js构建的三维Web前端实战项目面向前端开发者及三维可视化学习者系统讲解three.js核心概念与太阳系宇宙模拟的工程化实现。资源完整覆盖纹理映射、星球自转/公转动画、物理光照模型、精准空间定位、星系随机生成含星河、黑洞、恒星等等关键技术点兼顾理论基础与可运行代码。压缩包共2000个文件主体为1675个JavaScript模块含Vue组件、Three场景逻辑、工具函数、198份Markdown学习笔记含API说明与实现原理、116个JSON配置如行星参数、材质定义辅以HTML入口、CSS样式及少量Python脚本总大小57.66MB结构清晰便于分层学习与功能复用。已有46人学习下载提供开箱即用的宇宙模拟演示效果包含真实比例建模思路、星空背景动态渲染方案及Element Plus交互控制面板是掌握WebGL三维开发落地实践的优质参考案例。1. 从零到一为什么选择Three.js来构建你的第一个宇宙如果你是一个对Web 3D感兴趣的前端开发者或者是一个想用代码创造视觉奇观的创意程序员那么“用Three.js模拟太阳系”几乎是一个完美的入门项目。我第一次接触Three.js时也选择了这个方向。原因很简单它足够直观也足够复杂。直观在于太阳、地球、月亮这些概念我们从小就熟悉不需要额外解释复杂在于它几乎涵盖了Three.js所有最核心的基础概念——场景、相机、渲染器、几何体、材质、纹理、灯光、动画循环、坐标与父子关系。把这个项目跑通你对Three.js的理解就能超过80%的浅尝辄止者。很多人学Three.js一上来就对着文档看Scene、Camera、Renderer这几个类的API看完就懵了不知道从哪里下手。我的建议是直接动手。这个宇宙模拟项目就是一个绝佳的“做中学”场景。你不需要一开始就理解所有的数学原理比如矩阵变换、四元数你可以先从感性的“摆弄”开始把一个球体放到黑色背景里它就是一颗星星给它贴上图片它就有了纹理让它动起来它就开始了自转。在这个过程中你会自然而然地遇到问题“为什么我的星球不发光”灯光问题“为什么地球绕着太阳转的时候月亮跑丢了”坐标系与父子关系问题。带着这些问题再去查阅文档和原理印象会深刻十倍。从技术栈来看Three.js是目前WebGL领域事实上的标准库。它用面向对象的方式将晦涩难懂的WebGL API封装成了易于理解和使用的JavaScript类。你不需要直接去写着色器当然后期可以就能创造出令人惊叹的3D效果。对于前端开发者而言它无缝集成在现代前端工程化体系中Vue、React均可生态丰富社区活跃遇到问题很容易找到解决方案。所以无论你是想为产品增加3D展示还是想做数据可视化大屏或是纯粹的艺术创作Three.js都是你的首选武器库。2. 搭建舞台Three.js核心三要素的初始化与配置在让任何星球登场之前我们必须先搭建好3D世界的舞台。这个舞台由三个核心要素构成场景Scene、相机Camera和渲染器Renderer。你可以把它们想象成拍电影场景是包含所有道具和演员的摄影棚相机是导演取景的眼睛渲染器则是将这一切拍摄并输出成最终画面的摄影机。2.1 创建容器与引入Three.js首先我们需要一个HTML文件作为容器。现代前端项目通常使用构建工具但为了最直观地演示我们从最基础的HTMLScript标签开始。!DOCTYPE html html langzh-CN head meta charsetUTF-8 title我的太阳系/title style body { margin: 0; overflow: hidden; } #canvas-container { width: 100vw; height: 100vh; } /style /head body div idcanvas-container/div !-- 引入Three.js库 -- script srchttps://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js/script script src./main.js/script !-- 我们的主逻辑文件 -- /body /html这里我们通过CDN引入了Three.js版本r128并创建了一个铺满全屏的div容器。overflow: hidden是为了防止滚动条出现。2.2 初始化场景、相机与渲染器接下来在main.js中我们开始构建世界的基石。// main.js // 1. 创建场景 const scene new THREE.Scene(); // 给场景一个深邃的黑色背景模拟太空 scene.background new THREE.Color(0x000011); // 使用深蓝色而非纯黑更有层次感 // 2. 创建相机透视相机 const camera new THREE.PerspectiveCamera( 75, // 视野角度FOV单位是度。越大看到的范围越广类似广角镜头。 window.innerWidth / window.innerHeight, // 宽高比通常设为画布宽高比 0.1, // 近截面near。比这个距离近的物体不会被渲染。 1000 // 远截面far。比这个距离远的物体不会被渲染。 ); camera.position.set(0, 20, 50); // 将相机向后、向上移动以便看到整个场景 // 3. 创建渲染器 const renderer new THREE.WebGLRenderer({ antialias: true }); // 开启抗锯齿 renderer.setSize(window.innerWidth, window.innerHeight); // 设置渲染器输出尺寸 renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比避免高清屏模糊 // 将渲染器生成的canvas元素添加到页面容器中 document.getElementById(canvas-container).appendChild(renderer.domElement);关键点解析场景Scene它是一个容器用于存放所有需要渲染的物体网格Mesh、灯光Light等。scene.background可以设置纯色也可以设置为THREE.Texture来添加星空背景图这是我们后面可以优化的点。透视相机PerspectiveCamera这是最常用的相机模拟人眼看到的近大远小的透视效果。四个参数至关重要。near和far的值需要根据你的场景尺度谨慎设置。对于太阳系星球距离可能很大如果far设得太小远处的星球就会“消失”。这里我们先设为1000后续根据星球轨道大小调整。渲染器WebGLRenderer它负责将场景和相机看到的画面绘制到HTML的canvas元素上。setSize决定了输出画面的分辨率。setPixelRatio是为了适配高DPI屏幕如Retina屏让画面更清晰。2.3 实现动画循环让世界动起来静态的画面不是我们的目标。Three.js通过动画循环来持续更新和渲染画面这是实现星球自转公转的引擎。// 动画循环函数 function animate() { requestAnimationFrame(animate); // 在下次重绘前调用自身形成循环 // 在这里更新场景中的物体状态例如旋转 // 例如如果有一个地球物体 earth可以让它自转 earth.rotation.y 0.01; renderer.render(scene, camera); // 用相机拍摄场景并渲染出来 } animate(); // 启动动画循环 // 处理窗口大小变化保持画面比例 window.addEventListener(resize, () { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); // 相机参数改变后必须调用此方法 renderer.setSize(window.innerWidth, window.innerHeight); });requestAnimationFrame是浏览器提供的API它会与显示器的刷新率同步通常是60fps从而提供平滑的动画效果并且当页面不可见时会自动暂停节省资源。所有的物体运动、状态更新逻辑都应该放在这个循环函数里。3. 创造星球几何体、材质与纹理的实战应用舞台搭好演员该上场了。在Three.js中一个可视的物体通常是一个网格Mesh它由几何体Geometry和材质Material两部分构成。几何体定义了物体的形状比如球体、立方体材质定义了物体的表面外观比如颜色、光泽度、贴图。3.1 创建基础星球太阳与地球我们先从最简单的、没有纹理的彩色球体开始。// 创建太阳一个发光的黄色球体 const sunGeometry new THREE.SphereGeometry(5, 32, 32); // 半径5经纬分段数32 const sunMaterial new THREE.MeshBasicMaterial({ color: 0xFDB813 }); // 基础材质不受灯光影响 const sun new THREE.Mesh(sunGeometry, sunMaterial); scene.add(sun); // 创建地球一个蓝色的球体 const earthGeometry new THREE.SphereGeometry(2, 32, 32); // 半径2 const earthMaterial new THREE.MeshPhongMaterial({ color: 0x2233FF }); // Phong材质受灯光影响 const earth new THREE.Mesh(earthGeometry, earthMaterial); earth.position.x 20; // 将地球放置在X轴正方向20个单位的位置 scene.add(earth);关键点解析SphereGeometry球体几何体。三个参数分别是半径、宽度分段数、高度分段数。分段数越高球体越圆滑但顶点数越多性能开销越大。对于星球这种主要看外形的物体32是一个在效果和性能间不错的平衡点。MeshBasicMaterial基础网格材质。这种材质的特点是不受灯光影响颜色是恒定的。用它来做太阳很合适因为太阳本身是光源。MeshPhongMaterialPhong网格材质。这是一种受灯光影响的高光材质能模拟光滑表面的光泽感适合用来表现地球、金星等岩石或流体行星。mesh.position每个网格对象都有一个position属性是一个Vector3对象用于设置它在三维空间中的坐标(x, y, z)。3.2 为星球穿上“外衣”加载与应用纹理单色球体太简陋了。我们需要为星球贴上真实的表面纹理图片。这需要用到纹理加载器TextureLoader。首先你需要准备星球的纹理图片。可以从NASA等公开资源网站获取。假设我们已将earth.jpg地球表面、sun.jpg太阳表面等图片放在项目的textures/文件夹下。// 创建纹理加载器 const textureLoader new THREE.TextureLoader(); // 为地球加载纹理 const earthTexture textureLoader.load(textures/earth.jpg); const earthMaterialWithTexture new THREE.MeshPhongMaterial({ map: earthTexture, // map属性代表基础颜色贴图 // 可以添加其他贴图如 bumpMap凹凸贴图、specularMap高光贴图来增加细节 }); const earthWithTexture new THREE.Mesh(earthGeometry, earthMaterialWithTexture); earthWithTexture.position.x 20; scene.add(earthWithTexture); // 为太阳加载纹理注意太阳材质通常用BasicMaterial因为它自发光 const sunTexture textureLoader.load(textures/sun.jpg); const sunMaterialWithTexture new THREE.MeshBasicMaterial({ map: sunTexture, // 太阳可以加一点自发光颜色模拟光晕后期用点光源或镜头光晕实现更好 }); const sunWithTexture new THREE.Mesh(sunGeometry, sunMaterialWithTexture); scene.add(sunWithTexture);注意纹理加载是异步的。load方法会立即返回一个Texture对象但图片可能还在加载中。Three.js默认会等待纹理加载完成后再渲染。对于复杂的场景你可能需要添加加载管理器来显示进度。3.3 照亮世界灯光系统的配置技巧没有光受灯光影响的材质如MeshPhongMaterial就会一片漆黑。我们需要在场景中添加光源。// 1. 环境光均匀地照亮所有物体没有方向用于提亮阴影避免纯黑。 const ambientLight new THREE.AmbientLight(0x333333); // 颜色强度 scene.add(ambientLight); // 2. 平行光模拟远处光源如太阳所有光线平行照射。这是模拟太阳光照的最佳选择。 const directionalLight new THREE.DirectionalLight(0xffffff, 1.0); // 颜色强度 directionalLight.position.set(50, 50, 50); // 设置光源位置 scene.add(directionalLight); // 可选添加一个点光源在太阳位置模拟太阳自身发光对周围行星的影响 const sunLight new THREE.PointLight(0xFDB813, 1.5, 100); // 颜色强度照射距离 sunLight.position.copy(sunWithTexture.position); // 将光源位置与太阳网格位置同步 scene.add(sunLight);灯光选型心得环境光AmbientLight必不可少它能保证物体的背光面也有基础亮度但强度不宜过高否则会失去立体感。我通常从0x222222较暗开始调试。平行光DirectionalLight是主光源它的.position属性表示光线的方向向量而不是一个点。设置(50,50,50)意味着光从(50,50,50)坐标点方向射向原点(0,0,0)。你可以创建一个辅助对象来可视化光线方向scene.add(new THREE.DirectionalLightHelper(directionalLight));。点光源PointLight像灯泡向所有方向发光。放在太阳处可以让靠近太阳的行星如水星、金星更亮增加真实感。第三个参数distance是光的衰减距离超过此距离光照强度为0。4. 构建星系坐标系统、层级关系与动画实现单个星球是静态的太阳系的美在于运动与精妙的层级关系。这里涉及到Three.js中两个核心概念对象层级和动画循环更新。4.1 理解坐标系与父子关系实现公转在Three.js中每个对象Object3D都有自己的局部坐标系。当一个对象被添加到另一个对象下成为其子对象时子对象的变换位置、旋转、缩放将基于父对象的坐标系。这是实现公转的关键错误的做法是在动画循环里直接计算并设置地球的position.x和position.z来模拟圆周运动。虽然可行但代码会非常混乱特别是对于有卫星如月球的星球。正确的做法是使用空对象作为轨道容器。// 1. 创建地球的公转轨道容器一个空的Object3D const earthOrbit new THREE.Object3D(); scene.add(earthOrbit); // 将轨道容器添加到场景 // 注意此时earthOrbit的位置在场景原点(0,0,0) // 2. 将地球添加到它的轨道容器中 earthOrbit.add(earthWithTexture); // 注意这里add的是之前创建的有纹理的地球 // 此时地球的局部坐标是相对于earthOrbit的。我们把它放在X轴正方向20个单位处。 earthWithTexture.position.x 20; // 3. 在动画循环中旋转轨道容器地球就会绕着容器中心也就是场景原点公转 function animate() { requestAnimationFrame(animate); earthOrbit.rotation.y 0.01; // 每帧绕Y轴旋转一点点形成公转动画 renderer.render(scene, camera); }通过这种方式我们只需要旋转earthOrbit其子对象地球就会自然地绕着原点做圆周运动。太阳如果放在原点就形成了地球绕太阳公转的效果。4.2 实现自转与多层级关系以地月系统为例自转很简单直接旋转星球网格自身即可。复杂的是当地球有自己的卫星月球时月球既要绕地球公转又要随地球一起绕太阳公转。这需要构建一个三层嵌套的层级关系。// 1. 创建地球的自转公转容器同上 const earthOrbit new THREE.Object3D(); scene.add(earthOrbit); // 2. 创建地球网格并添加到earthOrbit const earth createPlanet(2, earth.jpg, false); // 假设有一个创建行星的封装函数 earthOrbit.add(earth); // 3. 创建月球的公转轨道容器并添加到“地球网格”上 // 注意是添加到earth而不是earthOrbit。因为月球应该绕着地球转而不是绕着地球的公转轨道中心转。 const moonOrbit new THREE.Object3D(); earth.add(moonOrbit); // 月球轨道是地球的子对象 moonOrbit.position.x 5; // 地月距离相对于地球的位置 // 4. 创建月球网格并添加到moonOrbit const moon createPlanet(0.5, moon.jpg, false); moonOrbit.add(moon); moon.position.x 3; // 月球在自身轨道上的位置 // 在动画循环中更新 function animate() { requestAnimationFrame(animate); // 地球公转 earthOrbit.rotation.y 0.01; // 地球自转 earth.rotation.y 0.02; // 自转通常比公转快 // 月球公转绕地球 moonOrbit.rotation.y 0.05; // 月球公转更快 renderer.render(scene, camera); }层级关系图解场景 (Scene) └── 地球公转轨道容器 (earthOrbit) // 控制绕太阳公转 └── 地球网格 (earth) // 控制自转 └── 月球公转轨道容器 (moonOrbit) // 控制绕地球公转 └── 月球网格 (moon)这种层级结构是Three.js中处理复杂对象关系的精髓。它让复杂的复合运动变得清晰且易于管理。4.3 完善太阳系批量创建与参数化管理按照上述模式我们可以创建出水星、金星、火星等行星。为了避免代码重复最好封装一个创建行星的函数。/** * 创建行星的工厂函数 * param {number} radius 半径 * param {string} textureUrl 纹理图片路径 * param {boolean} isEmissive 是否自发光如太阳 * param {number} orbitRadius 公转轨道半径 * param {number} orbitSpeed 公转速度系数 * returns {Object} { orbit, planet } 返回轨道容器和行星网格 */ function createPlanet(radius, textureUrl, isEmissive, orbitRadius, orbitSpeed) { const geometry new THREE.SphereGeometry(radius, 32, 32); let material; const texture textureLoader.load(textureUrl); if (isEmissive) { material new THREE.MeshBasicMaterial({ map: texture }); } else { material new THREE.MeshPhongMaterial({ map: texture }); } const planet new THREE.Mesh(geometry, material); const orbit new THREE.Object3D(); scene.add(orbit); orbit.add(planet); planet.position.x orbitRadius; // 将轨道和速度等信息挂载到对象上方便动画循环更新 orbit.userData { speed: orbitSpeed }; planet.userData { rotationSpeed: (Math.random() * 0.02 0.005) }; // 随机自转速度 return { orbit, planet }; } // 创建太阳太阳没有轨道直接放在场景中心 const sun createPlanet(5, textures/sun.jpg, true, 0, 0).planet; scene.add(sun); // 创建行星数据 const planetsData [ { name: mercury, radius: 0.8, texture: mercury.jpg, orbitRadius: 10, orbitSpeed: 0.04 }, { name: venus, radius: 1.5, texture: venus.jpg, orbitRadius: 15, orbitSpeed: 0.015 }, { name: earth, radius: 1.6, texture: earth.jpg, orbitRadius: 20, orbitSpeed: 0.01 }, { name: mars, radius: 1.2, texture: mars.jpg, orbitRadius: 25, orbitSpeed: 0.008 }, // ... 可以继续添加木星、土星等 ]; const planets []; // 用于保存所有行星的轨道对象 planetsData.forEach(data { const { orbit, planet } createPlanet( data.radius, textures/${data.texture}, false, data.orbitRadius, data.orbitSpeed ); planets.push(orbit); // 保存轨道用于动画更新 }); // 统一的动画循环 function animate() { requestAnimationFrame(animate); // 更新每个行星的公转和自转 planets.forEach(orbitObj { if (orbitObj.userData.speed) { orbitObj.rotation.y orbitObj.userData.speed; } // 遍历轨道的子对象即行星网格更新自转 orbitObj.children.forEach(planetMesh { if (planetMesh.userData.rotationSpeed) { planetMesh.rotation.y planetMesh.userData.rotationSpeed; } }); }); renderer.render(scene, camera); }通过这种参数化和数据驱动的创建方式代码变得非常清晰和易于扩展。你可以轻松调整planetsData数组中的数据来改变行星的大小、轨道和速度。5. 优化与进阶让宇宙更加真实动人基础功能实现后我们可以从视觉效果和交互上进一步提升这个太阳系模拟的质量。5.1 添加星空背景与行星光环星空背景将场景的背景从纯色改为立方体贴图CubeTexture可以营造出置身于无限星空的感觉。// 使用立方体贴图加载器 const cubeTextureLoader new THREE.CubeTextureLoader(); cubeTextureLoader.setPath(textures/skybox/); // 指向包含6张天空盒图片的目录 // 图片命名通常为px, nx, py, ny, pz, nz (分别代表正负X,Y,Z轴方向) const skyboxTexture cubeTextureLoader.load([ px.png, nx.png, py.png, ny.png, pz.png, nz.png ]); scene.background skyboxTexture; // 替换原来的纯色背景行星光环以土星为例土星的光环可以用一个扁平的圆环RingGeometry来实现。// 创建土星光环几何体 const ringGeometry new THREE.RingGeometry(innerRadius, outerRadius, thetaSegments); // innerRadius: 内半径 outerRadius: 外半径 thetaSegments: 分段数决定圆滑度 const ringMaterial new THREE.MeshBasicMaterial({ map: textureLoader.load(textures/saturn_ring.png), side: THREE.DoubleSide, // 双面渲染从两边都能看到 transparent: true, // 如果纹理有透明通道需要开启 opacity: 0.8 }); const ring new THREE.Mesh(ringGeometry, ringMaterial); ring.rotation.x Math.PI / 2; // 将圆环旋转到水平面默认是XY平面 saturn.add(ring); // 将光环作为土星的子对象5.2 实现轨道线可视化为了更直观地展示行星公转轨迹我们可以绘制出椭圆形的轨道线。// 为地球轨道创建一条线 const earthOrbitCurve new THREE.EllipseCurve( 0, 0, // 中心点x, y earthOrbitRadius, earthOrbitRadius, // x方向半径 y方向半径 0, 2 * Math.PI, // 起始角结束角完整一圈 false, // 是否顺时针 0 // 旋转角度 ); const earthOrbitPoints earthOrbitCurve.getPoints(50); // 获取曲线上的50个点 const earthOrbitGeometry new THREE.BufferGeometry().setFromPoints(earthOrbitPoints); const earthOrbitMaterial new THREE.LineBasicMaterial({ color: 0x444444 }); const earthOrbitLine new THREE.Line(earthOrbitGeometry, earthOrbitMaterial); earthOrbitLine.rotation.x Math.PI / 2; // 将曲线旋转到XZ平面Three.js中Y是向上的 scene.add(earthOrbitLine);每条轨道线都是一个独立的Line对象添加到场景中。你可以为每个行星创建不同颜色的轨道线。5.3 引入轨道控制器与交互目前我们的视角是固定的。引入轨道控制器OrbitControls可以让用户用鼠标和键盘自由旋转、缩放、平移视角来观察整个太阳系。// 首先需要在HTML中引入OrbitControls.js它是Three.js示例的一部分需单独引入 // script srchttps://cdn.jsdelivr.net/npm/three0.128.0/examples/js/controls/OrbitControls.js/script // 在main.js中初始化控制器 const controls new THREE.OrbitControls(camera, renderer.domElement); controls.enableDamping true; // 启用阻尼惯性效果操作更顺滑 controls.dampingFactor 0.05; controls.screenSpacePanning false; // 禁止在屏幕空间平移更符合天体观察 controls.minDistance 10; // 最小缩放距离 controls.maxDistance 500; // 最大缩放距离 controls.maxPolarAngle Math.PI; // 最大垂直旋转角度允许翻转到背面 // 在动画循环中更新控制器 function animate() { requestAnimationFrame(animate); controls.update(); // 必须在渲染前更新控制器 // ... 原有的公转自转更新逻辑 renderer.render(scene, camera); }OrbitControls极大地提升了项目的可交互性和探索乐趣。用户可以通过鼠标左键旋转视角右键平移滚轮缩放。5.4 性能优化与常见问题排查当星球数量增多、纹理分辨率变高时性能可能成为问题。纹理尺寸优化确保使用的纹理图片尺寸是2的幂次方如512x512, 1024x1024并且不要过度使用高分辨率纹理。对于远处的行星可以使用低分辨率纹理。几何体细节优化对于较小的或不重要的星球可以减少SphereGeometry的分段数如从32降低到16。渲染循环优化在animate函数中只更新需要变化的对象。如果场景静止可以考虑停止渲染循环。使用Stats.js监控性能这是一个轻量级的性能监控库可以显示帧率FPS、渲染时间等。// 引入Stats.js // script srchttps://cdnjs.cloudflare.com/ajax/libs/stats.js/r17/Stats.min.js/script const stats new Stats(); stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3: custom document.body.appendChild(stats.dom); function animate() { stats.begin(); // ... 更新与渲染逻辑 stats.end(); }常见问题排查星球是黑色的检查是否添加了灯光以及星球材质是否受灯光影响MeshPhongMaterial需要光。检查灯光位置和强度。纹理不显示/是黑色的检查图片路径是否正确控制台是否有404错误。检查图片格式是否被浏览器支持JPG PNG。可能是跨域问题如果通过file://协议打开建议使用本地服务器如VSCode的Live Server插件。动画卡顿打开浏览器开发者工具的Performance面板录制分析看是JavaScript执行时间过长还是渲染时间过长。使用Stats.js查看帧率。物体闪烁Z-fighting当两个面距离太近时深度缓冲区无法区分谁在前谁在后。可以适当增加相机near值或手动偏移物体的位置。走到这一步一个拥有自转公转、纹理贴图、灯光交互和轨道控制的简易太阳系就已经在你手中诞生了。这不仅仅是几个球体的运动而是你理解3D空间、层级变换和实时图形渲染的第一步。你可以在此基础上继续深化为气体巨星添加大气层着色器、实现小行星带、加入更精确的轨道力学计算、甚至用THREE.Lensflare为太阳添加光晕效果。Three.js的世界广袤无垠而这个小小的太阳系就是你探索它的起点和坚实的基石。本文还有配套的精品资源点击获取