前面幾天我們都是使用固定大小的 800 × 500 來顯示 Three.js 畫面。
從今天開始,我們要把原本的小型 Three.js 畫面改造成 全螢幕 3D 宇宙場景 同時把原本位於畫面上方的標題改成浮在 3D 場景上方。
今天完成之後,整個專案的外觀就會開始比較接近最後的成品。
今天我們會完成:
index.html
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>從 Web 到宇宙</title>
<link
rel="stylesheet"
href="css/style.css"
>
</head>
<body>
<div id="scene"></div>
<div class="title">
<h1>從 Web 到宇宙</h1>
<p>DAY 9|Full Screen Solar System</p>
</div>
<script
type="module"
src="js/main.js"
></script>
</body>
</html>
css/style.css
* {
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
body {
background: #000000;
color: white;
font-family: Arial, sans-serif;
}
/* Three.js 場景 */
#scene {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
}
#scene canvas {
display: block;
width: 100%;
height: 100%;
}
/* 標題 */
.title {
position: fixed;
top: 30px;
left: 40px;
z-index: 10;
pointer-events: none;
}
.title h1 {
margin: 0;
font-size: 36px;
letter-spacing: 2px;
}
.title p {
margin-top: 8px;
color: #aaaaaa;
font-size: 16px;
}
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.z = 8;
// ========================
// 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);
// ========================
// 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();
之前我們使用:
renderer.setSize(
800,
500
);
代表 Three.js 永遠只有 800 × 500
今天改成:
renderer.setSize(
window.innerWidth,
window.innerHeight
);
window.innerWidth 代表目前瀏覽器的寬度。window.innerHeight 代表目前瀏覽器的高度。
所以現在 Three.js 就會直接填滿整個瀏覽器。
const camera =
new THREE.PerspectiveCamera(
75,
800 / 500,
0.1,
1000
);
現在改成:
const camera =
new THREE.PerspectiveCamera(
75,
window.innerWidth /
window.innerHeight,
0.1,
1000
);
第二個參數就是畫面寬高比,也就是瀏覽器寬度、瀏覽器高度
這樣 Camera 看到的比例就會與瀏覽器一致。
HTML:
<div class="title">
<h1>從 Web 到宇宙</h1>
<p>DAY 9|Full Screen Solar System</p>
</div>
CSS:
.title {
position: fixed;
top: 30px;
left: 40px;
z-index: 10;
}
因為使用 position: fixed; 所以標題會直接浮在 Three.js 畫面上。
這個做法非常重要,因為之後開始製作 UI 時速度控制、播放、暫停、行星資訊
也會使用類似方式放在 Three.js 畫面上。
因為我們希望標題永遠顯示在 Canvas 上面。
不然 Three.js 的 Canvas 有可能把文字蓋住。
我們還加入pointer-events: none;目前標題只是文字,不需要點擊。
所以讓滑鼠事件直接穿過標題這對後面的滑鼠旋轉、滑鼠縮放會比較方便。
真正需要按的 UI 按鈕,之後再把 pointer-events 打開。
這是今天非常重要的新功能:
window.addEventListener(
"resize",
() => {
camera.aspect =
window.innerWidth /
window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(
window.innerWidth,
window.innerHeight
);
}
);
當使用者改變瀏覽器大小:
瀏覽器變大
↓
Camera 更新
↓
Renderer 更新
↓
Three.js 填滿畫面
所以不管 1920 × 1080、1366 × 768、2560 × 1440 Three.js 都會自動調整。
建立 Renderer 時:
const renderer =
new THREE.WebGLRenderer({
antialias: true
});
antialias 是抗鋸齒可以讓球體邊緣看起來比較平滑。
今天另外加入:
renderer.setPixelRatio(
Math.min(
window.devicePixelRatio,
2
)
);
它可以讓高解析度螢幕上的 Three.js 畫面更清楚。
但我們限制最高為 2 避免解析度太高造成 GPU 額外負擔。
這也是我們第一次開始稍微考慮後面的效能問題。
Day 9 最大的改變其實不是太陽,而是我們正式把練習畫面改造成最後太陽系會使用的全螢幕。
之前:
現在:
這個架構接下來就不要再改回 800 × 500。
從 Day 9 開始,我們直接在這個全螢幕場景上持續增加功能。
「八大行星:一次建立完整太陽系」
我們會一次加入八大行星,而且不只是單純放八顆球,我們會開始整理程式結構,用行星資料 + 迴圈建立行星,而不是複製貼上八份幾乎相同的程式。
也就是從 Day 10 開始,畫面會真正變成:
太陽 ─ 水星 ─ 金星 ─ 地球 ─ 火星 ─ 木星 ─ 土星 ─ 天王星 ─ 海王星