Three.js 課程 – 燈光 (Lights)
主題:PointLight、SpotLight、HemisphereLight
簡介
在 3D 場景中,燈光是決定畫面氛圍與物件可見度的關鍵因素。沒有適當的光源,模型只會呈現平淡的顏色,失去立體感與真實感。Three.js 提供了多種燈光類型,讓開發者可以依需求模擬自然光、聚光燈、環境光等不同效果。本單元聚焦於 PointLight、SpotLight、HemisphereLight 三種最常使用的燈光,說明它們的工作原理、使用時機以及實務上的最佳實踐,幫助你在 WebGL 專案中快速建立出具有層次感與氛圍的場景。
核心概念
1. PointLight(點光源)
PointLight 模擬一個無方向的光點,光線會向四面八方均勻散射,類似日常生活中的燈泡或螢火蟲。它的主要屬性包括:
| 屬性 | 說明 |
|---|---|
color |
光的顏色(THREE.Color 或十六進位字串) |
intensity |
強度,預設 1,數值越大光越亮 |
distance |
光照最遠距離,超過此距離光線會隨距離衰減,0 表示無限制 |
decay |
衰減指數,2 為物理上正確的平方衰減(預設) |
範例 1:基本 PointLight
// 建立場景、相機與渲染器(略)
const scene = new THREE.Scene();
// 1️⃣ 建立白色點光源,強度 2,距離 100
const pointLight = new THREE.PointLight(0xffffff, 2, 100);
pointLight.position.set(5, 10, 5); // 放置在場景上方
scene.add(pointLight);
// 為了看得更清楚,加入光源的輔助球體
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.2, 12, 12),
new THREE.MeshBasicMaterial({ color: 0xffff00 })
);
sphere.position.copy(pointLight.position);
scene.add(sphere);
小技巧:在開發階段加入
THREE.PointLightHelper可以即時觀察光源位置與範圍。
scene.add(new THREE.PointLightHelper(pointLight, 1));
2. SpotLight(聚光燈)
SpotLight 類似舞台燈光,從一個點向特定方向投射錐形光束,常用於聚焦某個物件或營造光斑效果。重要屬性:
| 屬性 | 說明 |
|---|---|
angle |
錐形半角(弧度),預設 Math.PI / 3 |
penumbra |
銳角的柔和程度,0~1,數值越大邊緣越柔和 |
target |
指向的目標 THREE.Object3D(預設為原點) |
decay、distance |
同 PointLight,控制衰減與範圍 |
範例 2:聚光燈聚焦模型
// 建立聚光燈,顏色淡藍,強度 1.5
const spotLight = new THREE.SpotLight(0x88ccff, 1.5, 200, Math.PI / 6, 0.3, 2);
spotLight.position.set(-10, 20, 10);
scene.add(spotLight);
// 設定聚光目標為場景中心 (0,0,0)
spotLight.target.position.set(0, 0, 0);
scene.add(spotLight.target);
// 加入輔助視覺化工具
scene.add(new THREE.SpotLightHelper(spotLight));
注意:
SpotLightHelper只在光源屬性改變後需要手動呼叫update(),否則不會即時反映。
spotLightHelper.update(); // 在每次改變 angle、penumbra 等時呼叫
3. HemisphereLight(半球光)
HemisphereLight 用來模擬天空與地面的環境光,光線從上方的「天空」顏色向下混合「地面」顏色,適合快速營造大範圍的柔和光照,常與其他光源結合使用。屬性:
| 屬性 | 說明 |
|---|---|
skyColor |
上方光的顏色 |
groundColor |
下方光的顏色 |
intensity |
強度 |
範例 3:半球光 + 環境光
// 天藍色的天空光、淡橘色的地面光
const hemiLight = new THREE.HemisphereLight(0x87ceeb, 0xffe0b2, 0.6);
hemiLight.position.set(0, 50, 0); // 位置不影響光照,只是視覺化
scene.add(hemiLight);
// 為了更自然,可再加上一盞弱的 AmbientLight
const ambient = new THREE.AmbientLight(0xffffff, 0.2);
scene.add(ambient);
實務小提醒:
HemisphereLight只能影響 材質支援的光照模型(如MeshStandardMaterial、MeshPhysicalMaterial),對MeshBasicMaterial無效。
程式碼範例(綜合實作)
以下示範一個完整的 Three.js 場景,結合三種光源,並展示如何在不同情況下切換或調整參數。
// ==================== 基本設定 ====================
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202030);
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(10, 10, 20);
const controls = new OrbitControls(camera, renderer.domElement);
// ==================== 物件 ====================
const geometry = new THREE.BoxGeometry(4, 4, 4);
const material = new THREE.MeshStandardMaterial({ color: 0x8888ff, metalness: 0.2, roughness: 0.7 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// ==================== 燈光 ====================
// 1. PointLight
const pointLight = new THREE.PointLight(0xffaa33, 1.8, 80);
pointLight.position.set(5, 12, 5);
scene.add(pointLight);
scene.add(new THREE.PointLightHelper(pointLight, 1));
// 2. SpotLight
const spotLight = new THREE.SpotLight(0xffffff, 1.2, 150, Math.PI / 8, 0.4, 1);
spotLight.position.set(-15, 20, 10);
spotLight.target = cube; // 聚焦在立方體
scene.add(spotLight);
scene.add(new THREE.SpotLightHelper(spotLight));
// 3. HemisphereLight
const hemiLight = new THREE.HemisphereLight(0x87ceeb, 0x444444, 0.5);
scene.add(hemiLight);
// ==================== 渲染迴圈 ====================
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.005;
cube.rotation.y += 0.01;
controls.update();
renderer.render(scene, camera);
}
animate();
// ==================== 互動調整(示範) ====================
// 按鍵 1~3 切換光源強度
window.addEventListener('keydown', (e) => {
switch (e.key) {
case '1': pointLight.intensity = pointLight.intensity === 0 ? 1.8 : 0; break;
case '2': spotLight.intensity = spotLight.intensity === 0 ? 1.2 : 0; break;
case '3': hemiLight.intensity = hemiLight.intensity === 0 ? 0.5 : 0; break;
}
});
重點說明
- 光源位置:
PointLight與SpotLight的位置會直接影響陰影與高光位置。- 目標設定:
SpotLight.target必須是THREE.Object3D,不設定則預設指向原點。- Helper 使用:開發階段加入
Helper,可快速定位光源與光錐範圍。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 光源太多導致效能下降 | 每個光源都會觸發著色器計算,過多會讓 GPU 負荷過大。 | 只保留必要光源,使用 AmbientLight 補足環境光;或利用 光照貼圖(Lightmap) 減少即時計算。 |
| 陰影未顯示 | 忘記啟用 renderer.shadowMap.enabled = true,或未為光源與物件設定 castShadow / receiveShadow。 |
在光源與需要投射/接受陰影的 Mesh 上分別設定 castShadow = true、receiveShadow = true。 |
| SpotLight 目標不正確 | spotLight.target 若未加入場景,光錐會指向錯誤位置。 |
scene.add(spotLight.target); 確保目標物件被加入場景。 |
| HemisphereLight 無效 | 使用了不支援光照的材質(如 MeshBasicMaterial)。 |
改用 MeshStandardMaterial、MeshPhysicalMaterial 或 MeshLambertMaterial。 |
| 光衰減不自然 | distance 設為 0 或過大,導致光線無法隨距離衰減。 |
設定合理的 distance 與 decay(建議使用 decay = 2),並根據場景大小調整。 |
最佳實踐:
- 分層光源:先使用
HemisphereLight或AmbientLight打底,再加上局部光(PointLight、SpotLight)做強調。 - 使用 Helper:開發階段加上
*Helper,上線前記得移除或隱藏。 - 動態調整:透過 UI(如 dat.GUI、Tweakpane)即時調整光源參數,快速找到最佳光照配置。
- 考慮實際場景尺度:光源的
distance、intensity應與相機遠近、模型大小成比例,避免過亮或過暗。
實際應用場景
| 場景 | 推薦光源組合 | 為何適合 |
|---|---|---|
| 室內住宅展示 | HemisphereLight(模擬天光)+ PointLight(吊燈)+ SpotLight(聚焦藝術品) |
半球光提供柔和的環境光,點光源模擬室內燈具,聚光燈突出關鍵擺設。 |
| 夜間城市街道 | SpotLight(街燈)+ PointLight(車燈)+ AmbientLight(微弱環境光) |
聚光燈營造街燈光斑,點光源模擬車頭燈,環境光提供整體暗色調。 |
| 遊戲角色聚焦 | SpotLight(聚光)+ HemisphereLight(天光) |
聚光燈將視線集中在角色身上,半球光保持場景自然的光照基調。 |
| 產品渲染(商品展示) | PointLight(環繞多點)+ HemisphereLight(基礎環境) |
多點光源避免陰影死角,半球光提供均衡的環境光,使產品材質呈現更真實。 |
總結
本篇文章從 概念、屬性、程式範例 逐步說明了 Three.js 中三種常用光源的使用方式,並提供了 實務上的最佳實踐與常見陷阱。掌握 PointLight、SpotLight、HemisphereLight 的特性後,你可以:
- 靈活組合光源,營造出日夜、室內外、聚焦與散射等多樣光照效果。
- 透過 Helper 與 UI 調整,即時微調光照參數,提升開發效率。
- 在效能與畫質之間取得平衡,避免過度使用光源造成的卡頓。
只要在實作時遵循「先環境光 → 再局部光 → 最後聚光」的層次思考,你的 Three.js 專案將會呈現更自然、更具沉浸感的視覺體驗。祝你在 3D 網頁開發的路上光芒四射! 🚀