Three.js 課程 – 材質 (Materials)
主題:光照材質 – MeshStandardMaterial、MeshPhongMaterial
簡介
在 3D 網頁應用中,材質(Material)決定了模型表面的外觀與光照互動方式。光照材質是最常用的兩大類型:MeshStandardMaterial(基於 PBR 物理渲染)與 MeshPhongMaterial(傳統 Phong 演算法)。了解它們的差異與使用時機,能讓你的 Three.js 場景從「平板」躍升為「真實感十足」的作品。
本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,逐步帶領 初學者到中階開發者 掌握這兩種材質的核心要點,並提供可直接套用的程式碼片段,讓你在實務專案中快速上手。
核心概念
1. 為什麼要分「Standard」與「Phong」?
| 特性 | MeshStandardMaterial |
MeshPhongMaterial |
|---|---|---|
| 光照模型 | 基於 PBR(Physically Based Rendering),使用金屬度 (metalness) 與粗糙度 (roughness) 參數模擬真實光線反射 | 傳統 Phong 模型,以鏡面反射 (specular) 與高光 (shininess) 控制光澤 |
| 環境光遮蔽 | 支援 環境貼圖 (environment map)、IBL(Image Based Lighting) | 只能使用簡單的環境光 |
| 效能 | 相對較重,特別在大量物件或低階裝置上需要注意 | 較輕量,適合快速原型或低階裝置 |
| 適用情境 | 金屬、陶瓷、皮革等需要真實感的材質 | 卡通、低多邊形、需要快速渲染的場景 |
簡單來說,如果你追求真實感,首選 MeshStandardMaterial;如果你偏好渲染速度或風格化外觀,MeshPhongMaterial 仍然是可靠的選擇。
2. MeshStandardMaterial 的關鍵屬性
| 屬性 | 說明 | 常見值 |
|---|---|---|
color |
基礎顏色 (RGB) | 0xffffff |
metalness |
金屬度,0 (非金屬) → 1 (純金屬) | 0.0 ~ 1.0 |
roughness |
粗糙度,0 (光滑) → 1 (粗糙) | 0.0 ~ 1.0 |
map |
漫反射貼圖 (diffuse) | THREE.Texture |
normalMap |
法線貼圖,提高細節 | THREE.Texture |
envMap |
環境貼圖,用於反射 | THREE.CubeTexture |
transparent / opacity |
透明度控制 | true / 0.5 |
小技巧:金屬材質的
roughness設為 0.1 ~ 0.3,可得到光滑金屬的感覺;非金屬材質則把metalness設為 0,再調整roughness以模擬不同表面。
3. MeshPhongMaterial 的關鍵屬性
| 屬性 | 說明 | 常見值 |
|---|---|---|
color |
基礎顏色 | 0xffffff |
specular |
高光顏色,決定鏡面反射的顏色 | 0x111111 |
shininess |
高光強度,值越大光澤越明亮 | 0 ~ 100 |
map |
漫反射貼圖 | THREE.Texture |
normalMap |
法線貼圖 | THREE.Texture |
emissive |
自發光顏色 | 0x000000 |
wireframe |
是否以線框模式渲染 | true / false |
MeshPhongMaterial 的 shininess 與 specular 共同決定了光澤度與鏡面反射的顏色,對於卡通或簡易的 UI 元件非常實用。
4. 材質與光源的關係
- PBR 材質 需要 環境光 (Ambient Light)、方向光 (Directional Light)、點光源 (Point Light) 或 聚光燈 (SpotLight) 才能完整展現金屬感與粗糙感。
- Phong 材質 只要有 至少一個點光源或方向光,即可產生明顯的高光。
實務建議:在同一場景中混用時,先把 全域光源(Ambient)放在最底層,然後再根據需求加入 方向光(模擬太陽)與 點光源(局部光源),這樣兩種材質都能得到合理的光照。
程式碼範例
以下示範 5 個常見的實作情境,從最簡單的「純色材質」到「環境貼圖 + 法線貼圖」的完整範例。所有範例均假設已有基本的 Three.js 場景(scene、camera、renderer)設定完成。
範例 1:純色 MeshStandardMaterial
// 建立一個金屬感的球體
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshStandardMaterial({
color: 0xaaaaaa, // 基礎灰色
metalness: 0.9, // 高金屬度
roughness: 0.2 // 稍微粗糙,避免完全鏡面
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
說明:只需要三個屬性即可得到金屬球的感覺,適合快速測試光源配置。
範例 2:帶有貼圖的 MeshPhongMaterial
// 載入貼圖
const textureLoader = new THREE.TextureLoader();
const brickMap = textureLoader.load('textures/brick_diffuse.jpg');
// 建立磚牆平面
const planeGeo = new THREE.PlaneGeometry(10, 10);
const planeMat = new THREE.MeshPhongMaterial({
map: brickMap,
specular: 0x222222, // 稍微暗的高光
shininess: 30 // 中等光澤
});
const plane = new THREE.Mesh(planeGeo, planeMat);
plane.rotation.x = -Math.PI / 2; // 讓平面水平
scene.add(plane);
說明:
MeshPhongMaterial能直接使用貼圖,且shininess控制磚牆的光澤度,適合卡通或較簡單的渲染需求。
範例 3:環境貼圖 (CubeTexture) + MeshStandardMaterial
// 建立環境貼圖(六面體)
const cubeTextureLoader = new THREE.CubeTextureLoader();
const envMap = cubeTextureLoader.load([
'env/px.jpg', 'env/nx.jpg',
'env/py.jpg', 'env/ny.jpg',
'env/pz.jpg', 'env/nz.jpg'
]);
scene.environment = envMap; // 讓所有 PBR 材質自動使用
// 金屬球體
const metalGeo = new THREE.SphereGeometry(1, 64, 64);
const metalMat = new THREE.MeshStandardMaterial({
metalness: 1.0,
roughness: 0.05,
envMap: envMap,
color: 0xffffff
});
const metalSphere = new THREE.Mesh(metalGeo, metalMat);
metalSphere.position.set(2, 1, 0);
scene.add(metalSphere);
說明:設定
scene.environment後,所有MeshStandardMaterial都會自動使用環境貼圖產生反射。金屬度 1、粗糙度 0.05 產生鏡面反射效果。
範例 4:結合法線貼圖提升細節(PBR)
const textureLoader = new THREE.TextureLoader();
const woodAlbedo = textureLoader.load('textures/wood_albedo.jpg');
const woodNormal = textureLoader.load('textures/wood_normal.jpg');
const boxGeo = new THREE.BoxGeometry(2, 2, 2);
const woodMat = new THREE.MeshStandardMaterial({
map: woodAlbedo,
normalMap: woodNormal,
metalness: 0.0,
roughness: 0.6
});
const woodBox = new THREE.Mesh(boxGeo, woodMat);
scene.add(woodBox);
說明:法線貼圖 (
normalMap) 能在不增加多邊形數量的情況下,讓木材表面呈現凹凸感,提升真實感。
範例 5:動態切換材質(實務常見需求)
let currentMaterial = 'standard'; // 初始為 PBR
function toggleMaterial() {
if (currentMaterial === 'standard') {
// 換成 Phong
mesh.material = new THREE.MeshPhongMaterial({
map: textureLoader.load('textures/brick_diffuse.jpg'),
shininess: 50,
specular: 0x555555
});
currentMaterial = 'phong';
} else {
// 換回 Standard
mesh.material = new THREE.MeshStandardMaterial({
map: textureLoader.load('textures/brick_diffuse.jpg'),
metalness: 0.2,
roughness: 0.7,
envMap: envMap
});
currentMaterial = 'standard';
}
}
// 監聽鍵盤事件切換
window.addEventListener('keydown', e => {
if (e.key === 'm') toggleMaterial();
});
說明:在開發工具或 Demo 時,常需要即時切換材質觀察差異。這段程式碼示範如何在同一個
mesh上動態切換MeshStandardMaterial與MeshPhongMaterial。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳實踐 |
|---|---|---|
| 忘記加入光源 | MeshStandardMaterial 在沒有光源時會呈現全黑 |
必須至少配置 AmbientLight + DirectionalLight |
| 粗糙度與金屬度同時設為 1 | 會產生不可預期的「鏡面」效果,且不符合物理模型 | 金屬度 1 時把 粗糙度 設在 0~0.3;金屬度 0 時可自由調整粗糙度 |
| 環境貼圖尺寸過大 | 會造成記憶體占用過高、載入緩慢 | 建議使用 256×256 或 512×512 的 cube map,並使用 .setEncoding(THREE.sRGBEncoding) |
| 法線貼圖未正規化 | 產生奇怪的光照斑點 | 確保貼圖在圖像編輯軟體中已 標準化 (normalize),或在載入後使用 texture.encoding = THREE.sRGBEncoding |
| 材質切換忘記釋放舊貼圖 | 造成 GPU 記憶體泄漏 | 在切換材質前,使用 oldMaterial.dispose() 釋放貼圖與緩衝區 |
| 過度使用高多邊形模型 | PBR 計算已較耗資源,若再加上大量頂點會卡頓 | 盡量使用 LOD(Level of Detail) 或 InstancedMesh,把細節交給貼圖與法線貼圖補償 |
實際應用場景
產品展示(電商)
- 使用
MeshStandardMaterial搭配 HDR 環境貼圖,呈現金屬、玻璃、皮革等材質的真實光澤,提升客戶購買慾望。
- 使用
互動式教學或資料視覺化
MeshPhongMaterial速度快,適合在大量圖表、快速切換顏色與光澤的情況下使用。
遊戲與虛擬實境 (VR)
- 在需要高真實感的角色或道具上使用 PBR;UI 按鈕、指示牌則可採用 Phong 以降低效能負擔。
AR 應用
- 由於行動裝置效能有限,常見做法是 混合:主體使用
MeshStandardMaterial(金屬/玻璃),背景與輔助物件使用MeshPhongMaterial。
- 由於行動裝置效能有限,常見做法是 混合:主體使用
建築可視化
- 建築外牆、玻璃幕牆使用 PBR,地面或草坪則可用 Phong 以加速渲染。
總結
MeshStandardMaterial:基於 PBR,提供金屬感、粗糙度、環境反射等真實渲染效果;適合需要高寫實度的場景。MeshPhongMaterial:採用 Phong 模型,計算簡單、渲染快速,適合卡通、低多邊形或效能受限的情況。- 正確的 光源配置、貼圖管理 與 材質切換 是避免常見陷阱的關鍵。
- 在實務開發中,混合使用 兩種材質、根據裝置效能調整 金屬度、粗糙度,能兼顧畫質與效能。
掌握了這兩種光照材質的特性與最佳實踐後,你就能在 Three.js 中自由打造 逼真 或 風格化 的 3D 作品,無論是電商產品頁、互動教學還是遊戲開發,都能得心應手。祝你玩得開心、寫得順利!