iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Modern Web

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

Day 11 - 行星自轉:讓八大行星開始旋轉

  • 分享至 

  • xImage
  •  

Day 10 我們已經把太陽與八大行星全部建立完成。
今天開始讓太陽系真正「動起來」。
這次我們會讓八大行星都加入自己的 rotationSpeed
也就是自轉速度。

一、今天完成目標

今天會完成:

  • 在 planetData 加入 rotationSpeed 讓每顆行星都有自己的自轉速度
  • 把行星資料與 Mesh 一起保存
  • 在動畫迴圈中更新行星自轉
  • 讓金星加入反方向自轉,讓太陽也持續自轉
  • 加入白色網格跟著行星旋轉 ( 才能看出有在旋轉 )
  • 降低球體分段,讓網格更清楚

今天完成後每顆行星都會開始旋轉。

二、完整程式碼

index.html

與 Day 10 相同,不需要修改。(完整在 Day 9)

css/style.css

與 Day 10 相同,不需要修改。(完整在 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.45,
        color: 0xaaaaaa,
        distance: 4,
        rotationSpeed: 0.008
    },

    {
        name: "Venus",
        size: 0.65,
        color: 0xd9a066,
        distance: 6,
        rotationSpeed: -0.003
    },

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

    {
        name: "Mars",
        size: 0.55,
        color: 0xcc4422,
        distance: 10,
        rotationSpeed: 0.009
    },

    {
        name: "Jupiter",
        size: 1.3,
        color: 0xd8b38a,
        distance: 13,
        rotationSpeed: 0.02
    },

    {
        name: "Saturn",
        size: 1.1,
        color: 0xe6c87a,
        distance: 16,
        rotationSpeed: 0.018
    },

    {
        name: "Uranus",
        size: 0.9,
        color: 0x66ccdd,
        distance: 19,
        rotationSpeed: 0.012
    },

    {
        name: "Neptune",
        size: 0.85,
        color: 0x3355ff,
        distance: 22,
        rotationSpeed: 0.011
    }

];

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

const planets = [];

planetData.forEach(
    (data) => {

        // 行星球體
        const geometry =
            new THREE.SphereGeometry(
                data.size,
                12,
                8
            );


        // 行星本體材質
        const material =
            new THREE.MeshBasicMaterial({
                color: data.color
            });


        // 建立行星
        const planet =
            new THREE.Mesh(
                geometry,
                material
            );


        // 設定行星位置
        planet.position.x =
            data.distance;


        // ========================
        // Wireframe
        // ========================

        const wireMaterial =
            new THREE.MeshBasicMaterial({
                color: 0xffffff,
                wireframe: true
            });


        const wire =
            new THREE.Mesh(
                geometry,
                wireMaterial
            );


        // 稍微放大
        // 避免和行星表面重疊
        wire.scale.set(
            1.03,
            1.03,
            1.03
        );


        // 把網格加入行星
        planet.add(
            wire
        );


        // 加入場景
        scene.add(
            planet
        );


        // 保存資料
        planets.push({
            mesh: planet,
            rotationSpeed:
                data.rotationSpeed
        });
    }
);


// ========================
// 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;


    // 八大行星自轉
    planets.forEach(
        (planet) => {

            planet.mesh.rotation.y +=
                planet.rotationSpeed;

        }
    );


    renderer.render(
        scene,
        camera
    );
}

animate();

三、加入自轉速度

今天每顆行星都有 rotationSpeed
例如地球:

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

代表動畫執行時:

planet.mesh.rotation.y +=
    planet.rotationSpeed;

地球每一幀都增加 0.01
金星反方向旋轉 rotationSpeed: -0.003 使用負數。
也可以順便看到正負數對旋轉方向的影響。

四、程式碼簡單說明

  1. 為什麼不用 32 × 32?

原本球體是:

new THREE.SphereGeometry(
    data.size,
    32,
    32
);

這樣球體很圓,但是網格也會變得非常密反而不好觀察旋轉。
所以今天暫時改成:

new THREE.SphereGeometry(
    data.size,
    12,
    8
);

網格會變得比較大這樣旋轉就比較容易看出來。

  1. 再建立一個白色網格

另外建立:

const wireMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffffff,
        wireframe: true
    });

其中 color: 0xffffff 而 wireframe: true 代表只顯示球體的線。
我們並不會改變球體的顏色,其實我們只是在球體的外面增加白色網格,接下來就來說明

  1. planet.add(wire) 很重要

今天最重要的新觀念之一:

planet.add(
    wire
);

以前我們都是:

scene.add(
    planet
);

意思是 Wire 變成 Planet 的子物件所以當 planet.rotation.y += 0.01;
Planet 旋轉時 Wire 也會一起旋轉。
這正是我們今天需要的效果。
再加上這裡:

wire.scale.set(
    1.03,
    1.03,
    1.03
);

也就是比原本行星大一點點,如果彩色球和網格完全一樣大,兩個表面會重疊。
所以讓網格稍微大一點這樣線條就比較清楚。

五、小試身手

今天的文章裡只有說到八大行星的網格但並沒有說到太陽的,
大家可以動手試試看自己將太陽也套上網格,至於想要做好的我放在今日小節~

六、今天小結

今天最大的重點不只是行星會旋轉
而是讓旋轉看得出來
目前我們完成:

全螢幕場景、太陽、八大行星、不同大小、顏色、距離、自轉速度、白色網格

要注意,這個 Wireframe 是教學用的暫時效果。
到了後面的 Texture:

地球表面、木星條紋、海王星表面、...

加入之後,自轉就能直接從貼圖看出來,到那時候就可以把白色網格移除。
接下來附上太陽網格,把註解 Sun 那段的程式碼覆蓋掉即可

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

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

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

const sunwireMaterial =
    new THREE.MeshBasicMaterial({
        color: 0xffffff,
        wireframe: true
    });

const sunwire =
    new THREE.Mesh(
        sunGeometry,
        sunwireMaterial
    );

       sunwire.scale.set(
            1.03,
            1.03,
            1.03
        );

sun.add(
    sunwire
);

scene.add(
    sun
);

完成之後網頁成品如下圖:
Adobe Express - 螢幕錄製 2026-09-21 113836

Day 12 預告

「行星公轉:讓八大行星開始繞太陽運行」

Day 11 現在做的是自轉。
Day 12 要做的是公轉。
我們會加入 orbitSpeed 與 angle
並開始使用:

Math.cos()
Math.sin()

計算行星的位置。
這會是將整個網頁從「一排旋轉彩色球」真正變成「太陽系軌道運動」的重要一天。


上一篇
Day 10 - 八大行星:建立完整的太陽系成員
下一篇
Day 12 - 行星公轉:讓八大行星開始繞太陽運行
系列文
從 Web 到宇宙:Three.js × WebGL 互動式太陽系與光影實作 共 16 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言