学习Three.js–太阳系星球自转公转

学习Three.js--太阳系星球自转公转

前置核心说明

开发目标

基于Three.js实现高还原度的太阳系演示场景,核心包含:

  1. 太阳(自发光+点光源)、地球(自转/公转+夜面灯光+云层)、月球(绕地球公转)的层级运动;
  2. 真实纹理贴图(星空背景、太阳/地球/月球高清纹理);
  3. 物理光照系统(太阳点光源+环境光,模拟真实光照);
  4. 后期辉光效果(Bloom)增强视觉体验;
  5. 流畅的交互控制(轨道控制器)和窗口适配。

核心技术栈

技术点 作用
Object3D 构建天体层级(太阳系→地球轨道→地球/月球),实现公转运动
MeshStandardMaterial PBR物理材质,支持纹理、自发光、法线贴图等真实效果
PointLight 模拟太阳光源,为行星提供光照
TextureLoader 加载高清纹理贴图(星空/太阳/地球/月球)
EffectComposer+UnrealBloomPass 后期处理,为太阳添加辉光效果
OrbitControls 鼠标交互(旋转/缩放视角)
动画循环 实现天体自转、公转的连续运动

分步开发详解

步骤1:基础环境搭建(核心模块引入+三大核心创建)

1.1 引入核心模块

首先导入Three.js核心及扩展模块(轨道控制器、GLTF加载器、后期处理):

javascript 复制代码
// 导入Three.js核心(CDN方式,无需本地安装)
import * as THREE from 'https://esm.sh/three@0.174.0';
// 轨道控制器(鼠标交互视角)
import { OrbitControls } from 'https://esm.sh/three@0.174.0/examples/jsm/controls/OrbitControls.js';
// 后期处理核心(辉光效果)
import { EffectComposer } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/EffectComposer.js';
import { RenderPass } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/RenderPass.js';
import { UnrealBloomPass } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/UnrealBloomPass.js';
1.2 创建三大核心(场景/相机/渲染器)
javascript 复制代码
// 1. 场景:所有天体的容器
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000000); // 初始纯黑背景(后续替换为星空)

// 2. 相机:透视相机(模拟人眼视角)
const camera = new THREE.PerspectiveCamera(
  75, // 视角(FOV)
  window.innerWidth / window.innerHeight, // 宽高比
  0.1, // 近裁切面(最近可见距离)
  2000 // 远裁切面(最远可见距离)
);
camera.position.set(0, 15, 25); // 调整相机位置,避免与天体重叠

// 3. 渲染器:将场景渲染到页面
const renderer = new THREE.WebGLRenderer({ antialias: true }); // 抗锯齿,提升画质
renderer.setSize(window.innerWidth, window.innerHeight); // 适配窗口尺寸
renderer.setPixelRatio(window.devicePixelRatio); // 适配高清屏幕
// 关键:颜色空间配置,避免纹理偏色
renderer.outputColorSpace = THREE.SRGBColorSpace;
// 色调映射:避免过曝,保留细节
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.4;
// 物理正确光照:让点光源衰减更真实
renderer.physicallyCorrectLights = true;
// 将渲染画布添加到页面
document.body.appendChild(renderer.domElement);

步骤2:天体层级设计

太阳系的核心运动逻辑是「层级嵌套」:

  • 太阳系(solarSystem):所有天体的根容器,可整体旋转;
  • 地球轨道(earthOrbit):绕太阳公转的轨道容器,地球/月球挂载到该容器上,容器旋转即实现公转;
javascript 复制代码
// 1. 创建太阳系根容器(所有天体的父节点)
const solarSystem = new THREE.Object3D();
// 2. 创建地球轨道容器(绕太阳公转)
const earthOrbit = new THREE.Object3D();
earthOrbit.position.x = 15; // 轨道半径(距离太阳15个单位)
// 3. 层级嵌套:地球轨道挂载到太阳系,太阳系挂载到场景
solarSystem.add(earthOrbit);
scene.add(solarSystem);

// 复用球体几何体(所有天体共用,通过scale调整大小)
const sphereGeometry = new THREE.SphereGeometry(1, 64, 64); // 半径1,分段64(平滑球体)

步骤3:纹理加载与颜色空间配置

纹理是实现真实效果的核心,需注意颜色空间配置(避免偏色):

  • 普通纹理(贴图):SRGBColorSpace
  • 法线贴图:LinearSRGBColorSpace(线性空间,计算法线更准确)。
javascript 复制代码
// 创建纹理加载器
const texLoader = new THREE.TextureLoader();

// 1. 星空背景纹理
const startTexture = texLoader.load('./2k_stars_milky_way.jpg');
startTexture.colorSpace = THREE.SRGBColorSpace;
scene.background = startTexture; // 设置场景背景

// 2. 太阳纹理
const sunTexture = texLoader.load('./2k_sun.jpg');
sunTexture.colorSpace = THREE.SRGBColorSpace;

// 3. 地球纹理(日间/夜间/法线)
const earthTexture = texLoader.load('./2k_earth_daymap.jpg');
earthTexture.colorSpace = THREE.SRGBColorSpace;
const earthNightTexture = texLoader.load('./2k_earth_nightmap.jpg'); // 夜面灯光
earthNightTexture.colorSpace = THREE.SRGBColorSpace;
const earthNormalTexture = texLoader.load('./2k_earth_normalmap.jpg'); // 法线(凹凸)
earthNormalTexture.colorSpace = THREE.LinearSRGBColorSpace;

// 4. 地球云层纹理
const cloudTexture = texLoader.load('./2k_earth_clouds.jpg');
cloudTexture.colorSpace = THREE.SRGBColorSpace;

// 5. 月球纹理
const moonTexture = texLoader.load('./2k_moon.jpg');
moonTexture.colorSpace = THREE.SRGBColorSpace;

步骤4:太阳创建

太阳是场景的核心光源,需实现「自发光效果」+「点光源照亮行星」:

javascript 复制代码
// 1. 太阳材质(PBR物理材质,自发光)
const sunMaterial = new THREE.MeshStandardMaterial({
  map: sunTexture, // 太阳纹理
  emissive: 0xffdd00, // 自发光颜色(暖黄色,贴近真实太阳)
  emissiveMap: sunTexture, // 自发光跟随纹理(而非纯色)
  emissiveIntensity: 0.6, // 自发光强度(避免过曝)
  roughness: 0.1, // 低粗糙度,模拟太阳光滑表面
  metalness: 0.0 // 非金属材质
});

// 2. 创建太阳网格模型
const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
sunMesh.scale.set(3, 3, 3); // 放大3倍(太阳尺寸远大于地球)
solarSystem.add(sunMesh); // 挂载到太阳系根容器

// 3. 太阳点光源(核心!照亮所有行星)
const sunLight = new THREE.PointLight(
  0xffffff, // 光源颜色(白色)
  20.0, // 强度(足够照亮地球轨道)
  200, // 照射距离
  1 // 衰减指数(线性衰减,更真实)
);
sunLight.position.set(0, 0, 0); // 光源与太阳位置重合
solarSystem.add(sunLight);

// 4. 环境光(补充暗部,让行星暗面可见)
const ambientLight = new THREE.AmbientLight(0x888888, 0.9);
scene.add(ambientLight);

步骤5:地球创建(基础模型+法线+夜面+云层)

地球需实现「日间纹理+夜面灯光+法线凹凸+透明云层」效果:

javascript 复制代码
// 1. 地球材质(PBR物理材质)
const earthMaterial = new THREE.MeshStandardMaterial({
  map: earthTexture, // 日间纹理
  normalMap: earthNormalTexture, // 法线贴图(模拟地形凹凸)
  normalScale: new THREE.Vector2(0.1, 0.1), // 降低法线强度(避免过度凹凸)
  emissive: 0x002288, // 夜面自发光颜色(蓝色城市灯光)
  emissiveMap: earthNightTexture, // 夜面纹理(暗面发光)
  emissiveIntensity: 0.2, // 夜面发光强度
  roughness: 0.4, // 中等粗糙度,模拟地球表面
  metalness: 0.1
});

// 2. 创建地球网格模型
const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
earthMesh.scale.set(0.8, 0.8, 0.8); // 缩小到0.8倍(地球比太阳小)
earthOrbit.add(earthMesh); // 挂载到地球轨道容器(实现公转)

// 3. 地球云层(透明效果)
const cloudMaterial = new THREE.MeshStandardMaterial({
  map: cloudTexture, // 云层纹理
  transparent: true, // 开启透明
  opacity: 0.5 // 透明度
});
// 云层球体略大于地球(半径1.01),模拟真实云层高度
const cloudMesh = new THREE.Mesh(new THREE.SphereGeometry(1.01, 64, 64), cloudMaterial);
earthMesh.add(cloudMesh); // 挂载到地球(随地球自转)

步骤6:月球创建

月球无大气,材质更粗糙,尺寸更小:

javascript 复制代码
// 1. 月球材质
const moonMaterial = new THREE.MeshStandardMaterial({
  map: moonTexture, // 月球纹理
  emissive: 0x111111, // 轻微自发光(灰色)
  emissiveIntensity: 0.1, // 低强度(仅辅助可见)
  roughness: 0.6, // 高粗糙度,模拟月球表面
  metalness: 0.1
});

// 2. 创建月球网格模型
const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
moonMesh.scale.set(0.25, 0.25, 0.25); // 缩小到0.25倍(月球比地球小)
moonMesh.position.x = 3; // 距离地球3个单位(月球轨道半径)
earthOrbit.add(moonMesh); // 挂载到地球轨道(随地球公转,同时绕地球旋转)

步骤7:后期处理(Bloom辉光,增强太阳视觉效果)

通过UnrealBloomPass为太阳添加辉光,模拟恒星的光晕效果:

javascript 复制代码
// 1. 创建渲染通道(渲染场景)
const renderScene = new RenderPass(scene, camera);
// 2. 创建辉光通道
const bloomPass = new UnrealBloomPass(
  new THREE.Vector2(window.innerWidth, window.innerHeight), // 分辨率
  0.7, // 辉光强度
  0.3, // 辉光半径
  0.2  // 辉光阈值(仅高亮区域发光)
);
// 3. 创建后期处理合成器
const composer = new EffectComposer(renderer);
composer.addPass(renderScene); // 添加场景渲染通道
composer.addPass(bloomPass); // 添加辉光通道

步骤8:交互控制(轨道控制器)

实现鼠标/触摸交互:旋转视角、缩放距离、阻尼效果(更顺滑):

javascript 复制代码
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // 开启阻尼(惯性)
controls.dampingFactor = 0.05; // 阻尼系数(越小越顺滑)
controls.minDistance = 10; // 最近可缩放距离(避免贴脸)
controls.maxDistance = 1000; // 最远可缩放距离

步骤9:动画循环(天体自转/公转)

核心是「更新天体旋转角度」+「渲染场景」,实现连续运动:

javascript 复制代码
function animate() {
  requestAnimationFrame(animate); // 浏览器刷新率同步
  
  // 天体运动(速度贴合真实比例)
  solarSystem.rotation.y += 0.0005; // 太阳系整体自转(慢)
  earthOrbit.rotation.y += 0.008;   // 地球公转(绕太阳)
  sunMesh.rotation.y += 0.005;      // 太阳自转
  earthMesh.rotation.y += 0.02;     // 地球自转(快于公转)
  moonMesh.rotation.y += 0.01;      // 月球自转
  cloudMesh.rotation.y += 0.025;    // 云层自转(略快于地球)

  controls.update(); // 更新控制器状态(阻尼生效)
  composer.render(); // 后期处理渲染(替代renderer.render)
}
animate(); // 启动动画循环

步骤10:窗口适配

窗口大小变化时,自动调整相机、渲染器、后期处理的尺寸:

javascript 复制代码
window.addEventListener('resize', () => {
  // 1. 更新相机宽高比
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  // 2. 更新渲染器尺寸
  renderer.setSize(window.innerWidth, window.innerHeight);
  // 3. 更新后期处理合成器尺寸
  composer.setSize(window.innerWidth, window.innerHeight);
  bloomPass.resolution.set(window.innerWidth, window.innerHeight);
});

关键技术点解析

1. 层级运动的核心:Object3D

场景
太阳系Object3D
太阳
地球轨道Object3D
地球
月球
云层

  • solarSystem.rotation.y:太阳系旋转 → 太阳/地球轨道整体旋转;
  • earthOrbit.rotation.y:地球轨道旋转 → 地球/月球绕太阳公转;
  • earthMesh.rotation.y:地球旋转 → 地球自转;

2. 自发光 vs 点光源

  • 太阳的emissive:视觉效果(自身发光),不影响其他物体;
  • 太阳的PointLight:物理光源(照亮行星),决定行星的明暗面;

3. 后期处理的作用

UnrealBloomPass仅对「高亮区域」(如太阳自发光)添加辉光,模拟恒星的光晕效果,提升视觉层次感,且不影响行星的细节。


完整示例代码

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Three.js 太阳系示例</title>
  <style>
    body { margin: 0; overflow: hidden; }
    canvas { display: block; }
  </style>
</head>
<body>
    <script type="module">
        // 1. 导入核心模块
        import * as THREE from 'https://esm.sh/three@0.174.0';
        import { OrbitControls } from 'https://esm.sh/three@0.174.0/examples/jsm/controls/OrbitControls.js';
        import { EffectComposer } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/EffectComposer.js';
        import { RenderPass } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/RenderPass.js';
        import { UnrealBloomPass } from 'https://esm.sh/three@0.174.0/examples/jsm/postprocessing/UnrealBloomPass.js';

        // 2. 初始化三大核心
        // 场景:所有天体的容器
        const scene = new THREE.Scene();
        // 相机:透视相机(模拟人眼)
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
        camera.position.set(0, 15, 25); // 调整视角,看清太阳系
        // 渲染器:抗锯齿+高清适配
        const renderer = new THREE.WebGLRenderer({ antialias: true });
        renderer.setSize(window.innerWidth, window.innerHeight);
        renderer.setPixelRatio(window.devicePixelRatio);
        renderer.outputColorSpace = THREE.SRGBColorSpace; // 颜色空间(避免偏色)
        renderer.toneMapping = THREE.ACESFilmicToneMapping; // 色调映射(避免过曝)
        renderer.toneMappingExposure = 1.4; // 曝光值(平衡亮度)
        renderer.physicallyCorrectLights = true; // 物理正确光照
        document.body.appendChild(renderer.domElement);

        // 3. 天体层级设计(核心:公转的基础)
        const solarSystem = new THREE.Object3D(); // 太阳系根容器
        const earthOrbit = new THREE.Object3D();  // 地球轨道容器
        earthOrbit.position.x = 15; // 地球轨道半径(距离太阳15单位)
        solarSystem.add(earthOrbit);
        scene.add(solarSystem);

        // 4. 纹理加载(所有纹理统一管理)
        const texLoader = new THREE.TextureLoader();
        // 4.1 星空背景
        const starTexture = texLoader.load('./2k_stars_milky_way.jpg');
        starTexture.colorSpace = THREE.SRGBColorSpace;
        scene.background = starTexture;
        // 4.2 太阳纹理
        const sunTexture = texLoader.load('./2k_sun.jpg');
        sunTexture.colorSpace = THREE.SRGBColorSpace;
        // 4.3 地球纹理(日间/夜间/法线)
        const earthDayTexture = texLoader.load('./2k_earth_daymap.jpg');
        earthDayTexture.colorSpace = THREE.SRGBColorSpace;
        const earthNightTexture = texLoader.load('./2k_earth_nightmap.jpg');
        earthNightTexture.colorSpace = THREE.SRGBColorSpace;
        const earthNormalTexture = texLoader.load('./2k_earth_normalmap.jpg');
        earthNormalTexture.colorSpace = THREE.LinearSRGBColorSpace;
        // 4.4 地球云层纹理
        const cloudTexture = texLoader.load('./2k_earth_clouds.jpg');
        cloudTexture.colorSpace = THREE.SRGBColorSpace;
        // 4.5 月球纹理
        const moonTexture = texLoader.load('./2k_moon.jpg');
        moonTexture.colorSpace = THREE.SRGBColorSpace;

        // 5. 创建球体几何体(复用,通过scale调整大小)
        const sphereGeometry = new THREE.SphereGeometry(1, 64, 64); // 高分段,球体更平滑

        // 6. 太阳创建(自发光+点光源)
        const sunMaterial = new THREE.MeshStandardMaterial({
            map: sunTexture,
            emissive: 0xffdd00, // 暖黄色自发光
            emissiveMap: sunTexture, // 自发光跟随纹理
            emissiveIntensity: 0.6, // 自发光强度
            roughness: 0.1,
            metalness: 0.0
        });
        const sunMesh = new THREE.Mesh(sphereGeometry, sunMaterial);
        sunMesh.scale.set(3, 3, 3); // 太阳放大3倍
        solarSystem.add(sunMesh);
        // 太阳点光源(照亮行星)
        const sunLight = new THREE.PointLight(0xffffff, 20.0, 200, 1);
        sunLight.position.set(0, 0, 0);
        solarSystem.add(sunLight);
        // 环境光(补充暗部)
        const ambientLight = new THREE.AmbientLight(0x888888, 0.9);
        scene.add(ambientLight);

        // 7. 地球创建(日间+夜面+法线+云层)
        const earthMaterial = new THREE.MeshStandardMaterial({
            map: earthDayTexture,
            normalMap: earthNormalTexture,
            normalScale: new THREE.Vector2(0.1, 0.1), // 降低法线强度
            emissive: 0x002288, // 夜面灯光颜色
            emissiveMap: earthNightTexture,
            emissiveIntensity: 0.2,
            roughness: 0.4,
            metalness: 0.1
        });
        const earthMesh = new THREE.Mesh(sphereGeometry, earthMaterial);
        earthMesh.scale.set(0.8, 0.8, 0.8); // 地球缩小0.8倍
        earthOrbit.add(earthMesh);
        // 地球云层
        const cloudMaterial = new THREE.MeshStandardMaterial({
            map: cloudTexture,
            transparent: true,
            opacity: 0.5
        });
        const cloudMesh = new THREE.Mesh(new THREE.SphereGeometry(1.01, 64, 64), cloudMaterial);
        earthMesh.add(cloudMesh);

        // 8. 月球创建
        const moonMaterial = new THREE.MeshStandardMaterial({
            map: moonTexture,
            emissive: 0x111111,
            emissiveIntensity: 0.1,
            roughness: 0.6,
            metalness: 0.1
        });
        const moonMesh = new THREE.Mesh(sphereGeometry, moonMaterial);
        moonMesh.scale.set(0.25, 0.25, 0.25); // 月球缩小0.25倍
        moonMesh.position.x = 3; // 月球轨道半径
        earthOrbit.add(moonMesh);

        // 9. 后期处理(辉光效果)
        const renderScene = new RenderPass(scene, camera);
        const bloomPass = new UnrealBloomPass(
            new THREE.Vector2(window.innerWidth, window.innerHeight),
            0.7, 0.3, 0.2
        );
        const composer = new EffectComposer(renderer);
        composer.addPass(renderScene);
        composer.addPass(bloomPass);

        // 10. 轨道控制器(交互)
        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true;
        controls.dampingFactor = 0.05;
        controls.minDistance = 10;
        controls.maxDistance = 1000;

        // 11. 动画循环(天体运动)
        function animate() {
            requestAnimationFrame(animate);
            // 天体旋转(速度贴合真实比例)
            solarSystem.rotation.y += 0.0005;   // 太阳系自转
            earthOrbit.rotation.y += 0.008;     // 地球公转
            sunMesh.rotation.y += 0.005;        // 太阳自转
            earthMesh.rotation.y += 0.02;       // 地球自转
            moonMesh.rotation.y += 0.01;        // 月球自转
            cloudMesh.rotation.y += 0.025;      // 云层自转
            // 更新控制器+渲染
            controls.update();
            composer.render();
        }
        animate();

        // 12. 窗口适配
        window.addEventListener('resize', () => {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
            composer.setSize(window.innerWidth, window.innerHeight);
            bloomPass.resolution.set(window.innerWidth, window.innerHeight);
        });
    </script>
</body>
</html>

总结与扩展建议

核心总结

  1. 层级设计 :通过Object3D嵌套实现天体公转,是Three.js实现复杂运动的核心思路;
  2. 纹理配置 :不同纹理需对应正确的颜色空间(SRGBColorSpace/LinearSRGBColorSpace);
  3. 光照系统:「点光源(太阳)+ 环境光」组合,模拟真实物理光照;
  4. 视觉增强:后期处理(Bloom)提升氛围感,自发光/法线贴图提升真实度;
  5. 动画逻辑 :通过requestAnimationFrame更新旋转角度,实现流畅的天体运动。

扩展建议

  1. 添加更多行星:复制地球轨道逻辑,创建火星/金星等轨道容器,调整轨道半径/尺寸/纹理;
  2. 轨道线可视化 :用LineCurve绘制行星轨道线,增强可读性;
  3. 鼠标交互:添加射线检测,点击行星显示名称/信息;
  4. 性能优化:降低非关键天体的几何体分段数,或添加LOD(细节层级);
  5. 氛围增强:添加太阳耀斑、行星环(土星)、小行星带等效果。
相关推荐
json{shen:"jing"}2 小时前
10_自定义事件组件交互
开发语言·前端·javascript
Jinuss2 小时前
源码分析之React中scheduleUpdateOnFiber调度更新解析
前端·javascript·react.js
一位搞嵌入式的 genius2 小时前
深入理解 JavaScript 异步编程:从 Event Loop 到 Promise
开发语言·前端·javascript
m0_564914922 小时前
Altium Designer,AD如何修改原理图右下角图纸标题栏?如何自定义标题栏?自定义原理图模版的使用方法
java·服务器·前端
方安乐2 小时前
react笔记之useCallback
前端·笔记·react.js
小二·3 小时前
Python Web 开发进阶实战:AI 伦理审计平台 —— 在 Flask + Vue 中构建算法偏见检测与公平性评估系统
前端·人工智能·python
走粥3 小时前
选项式API与组合式API的区别
开发语言·前端·javascript·vue.js·前端框架
We་ct3 小时前
LeetCode 12. 整数转罗马数字:从逐位实现到规则复用优化
前端·算法·leetcode·typescript
方安乐3 小时前
react笔记之useMemo
前端·笔记·react.js