跳到主要內容
前端開發

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);
}

positionrotation 都是三維向量,各自有 xyz 三個分量,每一影格微調數值就形成動畫。

物件群組

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》。

參考資料

分享這篇