Three.js 基礎入門:場景、光源、材質、攝影機
整理 Three.js 的核心概念——3D 場景四大主角(物件、光源、材質、攝影機)、幾種光源與渲染器類型、用 requestAnimationFrame 跑渲染迴圈、用 Raycaster 做滑鼠選取物件,這篇用 2020 年當時的全域 THREE 寫法示範。ESM 安裝方式整理在新的一篇。
W
Three.js 把「畫一個 3D 場景」拆解成幾個固定角色,這篇整理這套概念跟基本 API。
場景的四大主角
- 物件(object):實際的物體,畫面上看得到的東西
- 光源(light):照亮物體,沒有光源,物件會是全黑的
- 材質(material):決定每個面怎麼渲染——顏色、貼圖、反光程度
- 攝影機(camera):決定視角、視野跟渲染的範圍
建立場景與渲染器
// 攝影機 / 場景 / 渲染器
var camera, scene, renderer;
// 形狀 / 材質 / 物件
var geometry, material, mesh;
function init() {
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
init();
建立物件:形狀 + 材質
geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh(geometry, material); // 組合成物件
scene.add(mesh);
物件=形狀(geometry)+材質(material)——geometry 只定義形狀的頂點資料,實際看起來是什麼顏色、什麼質感,由 material 決定,兩者透過 THREE.Mesh 組合成一個能被加進場景的物件。
建立光源
Three.js 提供幾種常見光源,各自模擬不同的照明情境:
| 光源 | 特性 | 例子 |
|---|---|---|
| 點光源(PointLight) | 控制位置、有衰弱(falloff),從一個點往外放射,離光源越遠越暗 | 燈泡 |
| 聚光燈(SpotLight) | 控制位置、光束角度、有衰弱 | 手電筒 |
| 平行光(DirectionalLight) | 光線互相平行,沒有位置衰減 | 日照 |
| 環境光源(AmbientLight) | 均勻照亮整個場景,沒有方向性,通常當基礎補光 | — |
var light = new THREE.PointLight(0xff0000, 1, 100);
light.position.set(50, 50, 50);
scene.add(light);
建立材質
var sphereMaterial = new THREE.MeshLambertMaterial();
材質的幾個重點參數:反光(reflect)、散射(diffuse)、凹凸(bump)——反光越強烈,看起來越像金屬;凹凸貼圖能用一張平面圖模擬出表面的立體起伏,不需要真的建出凹凸的幾何形狀,是節省計算資源的常見做法。
建立攝影機
camera = new THREE.PerspectiveCamera(
70, // 視野角度(FOV)
window.innerWidth / window.innerHeight, // 長寬比
0.01, // 近截面
10, // 遠截面
);
camera.position.z = 1;
camera.lookAt(scene.position);
**視野(FOV,Field of View)**控制攝影機能看到多大的範圍——角度越大,看到的範圍越廣,但邊緣的透視變形也越明顯,跟廣角鏡頭是同樣的道理。渲染器(renderer)除了這篇用的 WebGL,還有 Canvas、CSS、SVG 這幾種選擇,各自的效能與相容性不同,Three.js 官方最推薦、支援度也最完整的是 WebGL 渲染器。
渲染場景
function render() {
renderer.render(scene, camera);
requestAnimationFrame(render); // 呼叫下一個渲染
}
requestAnimationFrame 讓瀏覽器在下一次重繪之前呼叫這個函式,形成一個持續更新畫面的渲染迴圈,比用 setInterval 手動控制更新頻率更順暢,也更省電(分頁不在前景時瀏覽器會自動降低頻率)。
移動物件
function animate() {
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.02;
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
position/rotation 都是三維向量,各自有 x/y/z 三個分量,每一影格微調數值就形成動畫。
物件群組
var sphereGeometry = new THREE.SphereGeometry(5, 60, 60);
var sphereMaterial = new THREE.MeshLambertMaterial();
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
var group = new THREE.Object3D(); // 空的物件,作為群組轉動的容器
group.add(sphere);
scene.add(group);
// group 的 position 跟 rotation 可以一起操控底下所有子物件
group 是一個空的 3D 物件,本身不會被畫出來,純粹當容器用——把多個物件都 add 進同一個 group,之後移動或旋轉這個 group,底下所有子物件會一起跟著動,不需要對每個子物件各自套用同樣的變換。
滑鼠定位:Raycaster
要判斷滑鼠點到了場景裡的哪個物件,靠的是射線偵測:從攝影機(或滑鼠位置)往場景裡射出一條看不見的射線,偵測這條射線跟哪些物件有交集。
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var intersects = raycaster.intersectObjects(scene.children);
if (intersects.length > 0) {
console.log('點到了', intersects[0].object);
}
}
intersectObjects() 回傳跟射線相交的所有物件,依距離攝影機由近到遠排序,intersects[0] 就是滑鼠實際點到、離攝影機最近的那個物件。
現在還適用嗎
場景四大主角、光源類型、渲染迴圈這些核心概念完全沒變,是 Three.js 從以前到現在的基礎骨架。改變的主要是安裝與引入方式——這篇用的是直接在瀏覽器全域掛一個 THREE 物件的 CDN 引入寫法,現在的專案幾乎都改用 npm 安裝加 ES Module 引入,整理在《Three.js 現在怎麼裝:從全域 THREE 到 ES Module》。
參考資料
- Three.js 官方網站(一直持續在改版)
- Three.js:Raycaster 範例