Three.js 與工具整合 – Skybox / HDR 環境製作
簡介
在 3D 網頁應用中,環境光是決定畫面真實感與沉浸感的關鍵因素。
傳統的光源(點光、平行光)只能提供局部的照明效果,而 Skybox 與 HDR (High Dynamic Range) 環境貼圖 則能一次性為整個場景提供全向的光線與反射,讓模型看起來彷彿真的置身於戶外或室內的特定氛圍中。
本篇文章將帶你從概念出發,一步步在 Three.js 中建構 Skybox 與 HDR 環境,並說明如何結合 dat.GUI、OrbitControls 等常用工具,快速產出既美觀又效能友善的 3D 場景。文章適合有基本 Three.js 使用經驗的初學者,也能為中階開發者提供實務上的最佳實踐與除錯技巧。
核心概念
1. Skybox 與環境貼圖的原理
- Skybox:利用六張正方形圖(正、背、左、右、上、下)組成一個立方體,將相機放在立方體中心,讓使用者永遠看見遠景。它本身不參與光照計算,只提供背景。
- HDR 環境貼圖:使用 32‑bit 浮點色深的全景圖(常見 equirectangular 格式),可直接作為 Environment Map,供 PMREMGenerator 產生預先濾波的環境貼圖,進而驅動 物理渲染 (PBR) 的金屬度、粗糙度等屬性,產生真實的 反射與折射。
重點:Skybox 只負責視覺上的「遠景」,而 HDR 環境貼圖才是「光照」的來源。兩者常會同時使用,Skybox 作為背景、HDR 作為光源。
2. 為什麼要使用 PMREMGenerator?
在 PBR 流程中,環境貼圖需要 多層 MIPMAP 以支援不同粗糙度的材質。THREE.PMREMGenerator 會將 HDR 圖轉換為 預濾波環境貼圖 (Prefiltered Mipmapped Radiance Environment Map),讓 MeshStandardMaterial、MeshPhysicalMaterial 在不同粗糙度下仍能得到正確的反射能量。
3. 常見檔案格式
| 類型 | 常見副檔名 | 說明 |
|---|---|---|
| Skybox (CubeTexture) | .png, .jpg |
6 張正方形圖,順序必須為 px, nx, py, ny, pz, nz |
| HDR (Equirectangular) | .hdr, .exr |
32‑bit 浮點圖,適合作為環境光源 |
| 低階備援 (LDR) | .jpg, .png |
若沒有 HDR,仍可使用 LDR equirectangular 產生環境貼圖(光照會較暗) |
程式碼範例
以下範例使用 Three.js r162(或更新版本)與 ES6 模組 寫法,請先在 HTML 中加入 type="module" 的 <script>,或使用 bundler (Vite、Webpack) 匯入。
1️⃣ 基本場景與相機設定
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);
renderer.outputEncoding = THREE.sRGBEncoding; // 正確的色彩空間
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
// 相機
const camera = new THREE.PerspectiveCamera(
60, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(0, 1.5, 3);
// 控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
提示:
renderer.outputEncoding = THREE.sRGBEncoding能確保 HDR 轉換後的顏色在螢幕上正確呈現。
2️⃣ 加載 Skybox(CubeTexture)
// Skybox 圖片路徑(請自行替換成自己的六張圖)
const skyboxPath = '/assets/skybox/';
const skyboxFiles = [
'px.jpg', 'nx.jpg',
'py.jpg', 'ny.jpg',
'pz.jpg', 'nz.jpg'
];
const loader = new THREE.CubeTextureLoader();
const skyboxTexture = loader.setPath(skyboxPath).load(skyboxFiles, () => {
console.log('Skybox loaded');
});
scene.background = skyboxTexture; // 設為背景
小技巧:若想讓 光照 也使用同一張 CubeTexture,只要把
scene.environment = skyboxTexture;即可,但建議還是使用 HDR 取得更自然的光照。
3️⃣ 加載 HDR 環境貼圖並產生 PMREM
import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
const rgbeLoader = new RGBELoader();
rgbeLoader.setDataType(THREE.UnsignedByteType); // 兼容舊版瀏覽器
rgbeLoader.load('/assets/hdr/royal_esplanade_1k.hdr', (hdrEquirect) => {
// 產生預濾波環境貼圖
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
const envMap = pmremGenerator.fromEquirectangular(hdrEquirect).texture;
// 設定場景環境光
scene.environment = envMap;
// 釋放原始 HDR 圖
hdrEquirect.dispose();
pmremGenerator.dispose();
console.log('HDR environment map ready');
});
注意:
RGBELoader會自動把.hdr讀成 linear 色彩空間,不要 再套用renderer.outputEncoding,否則會出現過曝。
4️⃣ 建立 PBR 材質的物件
// 讓所有材質自動使用環境貼圖
function createPBRMesh(geometry, color = 0xffffff) {
const material = new THREE.MeshStandardMaterial({
color,
metalness: 0.7, // 金屬度
roughness: 0.2, // 粗糙度
envMap: scene.environment,
envMapIntensity: 1.0
});
const mesh = new THREE.Mesh(geometry, material);
mesh.castShadow = true;
mesh.receiveShadow = true;
return mesh;
}
// 範例:放置一個金屬球
const sphere = createPBRMesh(
new THREE.SphereGeometry(0.5, 64, 64),
0xdddddd
);
sphere.position.set(-1, 0.5, 0);
scene.add(sphere);
// 範例:放置一個玻璃盒子
const box = createPBRMesh(
new THREE.BoxGeometry(1, 1, 1),
0xffffff
);
box.material.transparent = true;
box.material.opacity = 0.6;
box.material.roughness = 0.05;
box.material.metalness = 1.0;
box.position.set(1, 0.5, 0);
scene.add(box);
5️⃣ 使用 dat.GUI 動態調整環境強度
import GUI from 'lil-gui';
const gui = new GUI();
const params = {
envIntensity: 1.0,
exposure: 1.0
};
gui.add(params, 'envIntensity', 0, 2, 0.01).name('環境強度')
.onChange(v => {
// 所有使用環境貼圖的材質都會受影響
scene.traverse(obj => {
if (obj.isMesh && obj.material.envMap) {
obj.material.envMapIntensity = v;
obj.material.needsUpdate = true;
}
});
});
gui.add(params, 'exposure', 0.5, 2, 0.01).name('曝光')
.onChange(v => renderer.toneMappingExposure = v);
小提醒:
renderer.toneMapping = THREE.ACESFilmicToneMapping;可以讓 HDR 看起來更自然,若需要加入可自行啟用。
完整渲染迴圈
function animate() {
requestAnimationFrame(animate);
controls.update(); // 讓 OrbitControls 有阻尼效果
renderer.render(scene, camera);
}
animate();
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| HDR 讀取失敗 | RGBELoader 需要伺服器正確回傳 Content-Type: image/vnd.radiance,本機檔案直接 file:// 會 404。 |
使用本機開發伺服器(vite, http-server)或在 Node.js 搭配 express。 |
| 環境貼圖顏色過亮 | 未設定 renderer.toneMapping 與 renderer.toneMappingExposure。 |
建議使用 THREE.ACESFilmicToneMapping,並透過 renderer.toneMappingExposure 微調。 |
| CubeTexture 與 HDR 同時使用時衝突 | scene.background 與 scene.environment 只能分別設定,若同時給予同一個 CubeTexture,會導致光照不正確。 |
分別設定 scene.background = skyboxTexture; 與 scene.environment = envMap;。 |
| PMREM 產生過慢 | 大尺寸 HDR(4K+)在手機端會卡頓。 | 先在 Photoshop、HDRShop 等工具將 HDR 解析度降至 1K~2K,或使用 WebWorker 產生 PMREM。 |
| 材質沒有收到環境貼圖 | MeshStandardMaterial 的 envMap 為 null 或 envMapIntensity 為 0。 |
確認 scene.environment 已被設定,且在材質建立前已完成 HDR 加載(可使用 Promise.all)。 |
最佳實踐小結
- 先載入 HDR,等
PMREMGenerator完成後再建立需要環境光的 Mesh,避免「先渲染再換貼圖」的閃爍。 - 使用
sRGBEncoding與 線性空間(renderer.outputEncoding)保持一致,避免顏色偏差。 - 針對不同平台:桌面端使用 2K~4K HDR,手機端使用 1K LDR 或低解析度 HDR,以兼顧效能。
- 把環境參數抽成 UI(如
dat.GUI),方便即時微調,特別適合在開發階段快速找出最佳曝光與環境強度。
實際應用場景
| 場景 | 為何需要 Skybox / HDR | 實作要點 |
|---|---|---|
| 虛擬展覽(Art Gallery) | 需要真實的室內光線與牆面反射,使作品呈現自然光澤。 | 使用 HDR 室內環境貼圖(ex. studio.hdr),再加上簡單的白色 Skybox 作為背景。 |
| 車輛展示(Car Configurator) | 車體金屬與玻璃材質對光線非常敏感,環境反射決定外觀。 | 高解析度 HDR(4K) + PMREM,並在 UI 中提供「環境強度」滑桿讓使用者模擬不同天氣。 |
| 遊戲關卡(Adventure Game) | 大型戶外場景需要遠景(天空)與環境光的雙重效果。 | 結合 Skybox(雲層)與 HDR(日光)+ DirectionalLight 以加強陽光方向感。 |
| AR/VR 互動體驗 | 眼睛對光線變化極為敏感,HDR 能提升沉浸感。 | 在 WebXR 中使用 renderer.xr.enabled = true,同時保持 renderer.toneMapping 為 ACESFilmicToneMapping。 |
| 資料視覺化(Data Viz) | 3D 圖表放在有深度感的背景中更易閱讀。 | 只需要 Skybox 作為背景,搭配低強度 HDR 以免干擾資料顏色。 |
總結
- Skybox 與 HDR 環境貼圖 是提升 Three.js 場景真實感的兩大利器。
- 透過
CubeTextureLoader快速建立背景,並使用RGBELoader + PMREMGenerator產生可供 PBR 使用的光照貼圖。 - 正確的色彩空間(sRGB ↔ Linear)與 Tone Mapping 設定是避免過曝或暗沉的關鍵。
- 在開發過程中,UI 調整(dat.GUI)與 資源大小管理(HDR 解析度、PMREM 產生)能顯著提升使用者體驗與效能。
只要掌握以上概念與程式範例,你就能在 Three.js 中快速打造出 光影交織、反射自然 的 3D 場景,無論是遊戲、產品展示或是資料視覺化,都能讓作品在網頁上散發出專業級的視覺效果。祝開發順利,期待看到你用 Skybox & HDR 創造的精彩作品!