iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0
Modern Web

從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作系列 第 10 篇

Day 10 - 八大行星:建立完整的太陽系成員

  • 分享至 

  • xImage
  •  

今天開始加入太陽系最重要的成員「八大行星」。
今天不會直接複製八份 SphereGeometry + Material + Mesh,而是開始把程式整理成「資料 + 迴圈」的方式,這會讓後面的自轉、公轉、速度控制更容易處理。

一、今天完成目標

今天我們會完成:

  • 保留 Day 9 的全螢幕 Three.js 場景
  • 建立太陽和八大行星
  • 設定每顆行星不同大小及顏色
  • 設定每顆行星與太陽的距離
  • 使用陣列整理行星資料
  • 使用 forEach() 自動建立行星

二、完整程式碼 (說明跳第三段)

index.html

與 Day 9 相同,不需要修改。

css/style.css

與 Day 9 相同,不需要修改。

js/main.js

import * as THREE from
    "https://cdn.jsdelivr.net/npm/three@0.180.0/build/three.module.js";


// ========================
// Scene
// ========================

const scene = new THREE.Scene();

scene.background =
    new THREE.Color(0x000000);


// ========================
// Camera
// ========================

const camera =
    new THREE.PerspectiveCamera(
        75,
        window.innerWidth /
        window.innerHeight,
        0.1,
        1000
    );

camera.position.set(
    0,
    18,
    32
);

camera.lookAt(
    0,
    0,
    0
);


// ========================
// Renderer
// ========================

const renderer =
    new THREE.WebGLRenderer({
        antialias: true
    });

renderer.setSize(
    window.innerWidth,
    window.innerHeight
);

renderer.setPixelRatio(
    Math.min(
        window.devicePixelRatio,
        2
    )
);

document
    .getElementById("scene")
    .appendChild(
        renderer.domElement
    );


// ========================
// Sun
// ========================

const sunGeometry =
    new THREE.SphereGeometry(
        2,
        64,
        64
    );

const sunMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffaa00
    });

const sun =
    new THREE.Mesh(
        sunGeometry,
        sunMaterial
    );

scene.add(sun);


// ========================
// Planet Data
// ========================

const planetData = [

    {
        name: "Mercury",
        size: 0.35,
        color: 0xaaaaaa,
        distance: 4
    },

    {
        name: "Venus",
        size: 0.55,
        color: 0xd9a066,
        distance: 6
    },

    {
        name: "Earth",
        size: 0.6,
        color: 0x2277ff,
        distance: 8
    },

    {
        name: "Mars",
        size: 0.45,
        color: 0xcc4422,
        distance: 10
    },

    {
        name: "Jupiter",
        size: 1.2,
        color: 0xd8b38a,
        distance: 13
    },

    {
        name: "Saturn",
        size: 1.0,
        color: 0xe6c87a,
        distance: 16
    },

    {
        name: "Uranus",
        size: 0.8,
        color: 0x66ccdd,
        distance: 19
    },

    {
        name: "Neptune",
        size: 0.75,
        color: 0x3355ff,
        distance: 22
    }

];


// ========================
// Create Planets
// ========================

const planets = [];

planetData.forEach(
    (data) => {

        const geometry =
            new THREE.SphereGeometry(
                data.size,
                32,
                32
            );

        const material =
            new THREE.MeshBasicMaterial({
                color: data.color
            });

        const planet =
            new THREE.Mesh(
                geometry,
                material
            );

        planet.position.x =
            data.distance;

        scene.add(
            planet
        );

        planets.push(
            planet
        );
    }
);


// ========================
// Resize
// ========================

window.addEventListener(
    "resize",
    () => {

        camera.aspect =
            window.innerWidth /
            window.innerHeight;

        camera.updateProjectionMatrix();

        renderer.setSize(
            window.innerWidth,
            window.innerHeight
        );

        renderer.setPixelRatio(
            Math.min(
                window.devicePixelRatio,
                2
            )
        );
    }
);


// ========================
// Animation
// ========================

function animate() {

    requestAnimationFrame(
        animate
    );

    sun.rotation.y += 0.005;

    renderer.render(
        scene,
        camera
    );
}

animate();

三、程式碼簡單說明

  1. Camera 為什麼跟 Day 9 不一樣?

Day 9 我們使用 camera.position.z = 8; 因為當時只有一顆太陽。
但今天最遠的海王星已經放到 distance: 22 如果 Camera 還距離太近
就沒有辦法把整個太陽系看清楚,所以今天改成:

camera.position.set(
    0,
    18,
    32
);

也就是相機稍微往上、往後移。
接著:

camera.lookAt(
    0,
    0,
    0
);

告訴 Camera 看向太陽系中心,所以現在我們不是完全正面看,而是稍微從上方觀看。
這樣之後加入軌道時會更有 3D 感。

  1. 建立行星資料

今天最重要的新東西是:

const planetData = [
    {
        name: "Mercury",
        size: 0.35,
        color: 0xaaaaaa,
        distance: 4
    },

    ...
];

以前如果要建立八顆行星,可能會寫:

const mercuryGeometry = ...
const mercuryMaterial = ...
const mercury = ...

const venusGeometry = ...
const venusMaterial = ...
const venus = ...

const earthGeometry = ...
const earthMaterial = ...
const earth = ...

一直重複八次,雖然做得到,但是程式會越來越長。
所以今天開始把行星資料整理成:

Planet
│
├── name
├── size
├── color
└── distance

例如地球:

{
    name: "Earth",
    size: 0.6,
    color: 0x2277ff,
    distance: 8
}

名稱:Earth
大小:0.6
顏色:藍色
距離:8

  1. forEach() 建立八大行星

有了資料之後,就可以:

planetData.forEach(
    (data) => {
        ...
    }
);

把陣列裡的每一筆行星資料拿出來。
第一次 data = Mercury
第二次 data = Venus
第三次 data = Earth
一直執行到 data = Neptune。

  1. 每顆行星使用自己的大小及顏色

大小:

const geometry =
    new THREE.SphereGeometry(
        data.size,
        32,
        32
    );

這裡不再寫死而是使用 data.size。

new THREE.SphereGeometry(
    0.5,
    32,
    32
);

顏色:

const material =
    new THREE.MeshBasicMaterial({
        color: data.color
    });
  1. 行星與太陽的距離
planet.position.x =
    data.distance;

就是把行星放在不同 X 座標像是我們現在:

Sun X = 0
Mercury X = 4
Venus X = 6
Earth X = 8
Mars X = 10
Jupiter X = 13
Saturn X = 16
Uranus X = 19
Neptune X = 22

要注意這些不是太陽系真實距離,我們現在採用的是適合 3D 網頁展示的比例。

  1. 目前還沒有使用真正的行星貼圖。

為什麼還需要 planets?
我們另外建立 const planets = [];
每建立一顆行星

planets.push(
    planet
);

最後這個陣列裡面會有:

planets
│
├── Mercury
├── Venus
├── Earth
├── Mars
├── Jupiter
├── Saturn
├── Uranus
└── Neptune

今天看起來好像沒有什麼作用,但其實這是在幫 Day 11 做準備。
因為下一天我們就可以直接:

planets.forEach(
    (planet) => {

        planet.rotation.y += 0.01;

    }
);

讓八大行星全部開始旋轉,開始替後面的功能整理程式。

四、今天小結

目前程式結構已經從 Day 9 的 只有一顆太陽
進化成:
image
而程式也開始從,一個物件寫一段程式
改成用 Planet Data 和 forEach 去快速把每個星球的資料建立好
這個改變很重要,因為之後我們還會在 planetData 裡加入:

rotationSpeed
orbitSpeed
texture
description

到最後,planetData 就會變成整個太陽系的核心資料。

Day 11 預告

「行星自轉:讓八大行星開始旋轉」

Day 11 會開始使用今天建立的 const planets = []; 讓八大行星開始自轉。
但我們不會讓所有行星都用 rotation.y += 0.01; 完全一樣的速度。
我們會把資料進一步改成:

{
    name: "Earth",
    size: 0.6,
    color: 0x2277ff,
    distance: 8,
    rotationSpeed: 0.01
}

各自擁有自己的旋轉設定。


上一篇
Day 9 - 打造全螢幕 3D 太陽系
下一篇
Day 11 - 行星自轉:讓八大行星開始旋轉
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言