Three.js 教學 – 貼圖 (Textures) 中的 Alpha、Normal、Roughness、Metalness
簡介
在 3D 網頁開發中,貼圖是讓模型表面呈現真實感的關鍵工具。單純的顏色貼圖只能提供基本的色彩資訊,而 Alpha、Normal、Roughness、Metalness 四種貼圖則分別負責透明度、法線細節、粗糙度以及金屬度,合力構築出光線交互的複雜效果。
掌握這四種貼圖的使用方式,不僅能讓你的 Three.js 場景看起來更具專業水準,還能在效能與視覺品質之間取得最佳平衡。本文將從概念說明、實作範例、常見陷阱到最佳實踐,完整帶領你在 Three.js 中運用這些貼圖。
核心概念
1. Alpha 貼圖 – 控制透明度
Alpha 貼圖是一張 灰階圖,白色代表完全不透明,黑色則是全透明。它常用於製作葉子、窗戶、旗幟等需要「洞」的物件。
在 Three.js 中,使用 alphaMap 屬性並將材質的 transparent 設為 true 即可啟用。
小提醒:若同時使用
opacity參數,最終透明度會是opacity * alphaMap的結果。
2. Normal 貼圖 – 增強表面細節
Normal 貼圖儲存 法線向量,讓光照在模型上產生更細緻的陰影與高光,而不必真的在幾何體上增加多邊形。
Three.js 使用 normalMap 屬性,貼圖的顏色 (RGB) 代表法線的 X、Y、Z 分量。
注意:Normal 貼圖必須是 線性空間 (Linear) 的圖像,若是 sRGB 需要在載入時轉換。
3. Roughness 貼圖 – 控制表面粗糙度
Roughness 表示表面散射光線的程度,值越低越光滑,值越高則越粗糙。對應的貼圖一般是 灰階圖,白色代表完全粗糙,黑色代表完全光滑。
在 PBR (Physically Based Rendering) 材質 MeshStandardMaterial 中,用 roughnessMap 來指定。
4. Metalness 貼圖 – 定義金屬屬性
Metalness 表示材質是否為金屬。金屬表面會根據環境光反射出金屬色澤,非金屬則保留基礎顏色。Metalness 貼圖同樣是 灰階圖,白色 = 完全金屬,黑色 = 完全非金屬。
使用 metalnessMap 屬性即可。
程式碼範例
以下示範 四張貼圖 同時應用於一個簡單的立方體,並說明每一步驟的意圖。
範例 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);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x202020);
const camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
100
);
camera.position.set(2, 2, 4);
new OrbitControls(camera, renderer.domElement);
// 載入貼圖
const loader = new THREE.TextureLoader();
const [
albedoMap, // 基礎色彩貼圖
alphaMap, // 透明度貼圖
normalMap, // 法線貼圖
roughnessMap,// 粗糙度貼圖
metalnessMap // 金屬度貼圖
] = [
'textures/wood_albedo.jpg',
'textures/wood_alpha.png',
'textures/wood_normal.jpg',
'textures/wood_roughness.jpg',
'textures/wood_metalness.jpg'
].map(url => loader.load(url));
說明:
TextureLoader會自動將圖片解碼為 sRGB,除非貼圖需要線性空間(如 Normal),可在載入後設定texture.encoding = THREE.LinearEncoding。
範例 2 – 建立 PBR 材質並套用四張貼圖
// 建立 PBR 材質
const material = new THREE.MeshStandardMaterial({
map: albedoMap, // 基礎色彩
alphaMap: alphaMap, // 透明度
transparent: true, // 必須開啟才能讓 alphaMap 生效
normalMap: normalMap, // 法線
roughnessMap: roughnessMap,
metalnessMap: metalnessMap,
// 為了避免金屬度貼圖被 sRGB 誤解,強制使用線性編碼
metalness: 1.0, // 預設金屬度 (若 metalnessMap 為全白可省略)
roughness: 1.0 // 預設粗糙度 (若 roughnessMap 為全白可省略)
});
// 若 normalMap 是 sRGB 圖檔,必須轉成 Linear
normalMap.encoding = THREE.LinearEncoding;
// 建立立方體幾何體
const geometry = new THREE.BoxGeometry(1, 1, 1);
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
重點:
transparent: true必須與alphaMap同時使用,否則透明度不會被渲染。metalness與roughness的預設值會與貼圖相乘,若貼圖已提供完整資訊,可將預設值設為1.0。
範例 3 – 加入環境貼圖提升金屬感
// 載入 HDR 環境貼圖(使用 RGBE / EXR 格式較佳)
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
new THREE.RGBELoader()
.setPath('textures/')
.load('studio.hdr', (hdrTexture) => {
const envMap = pmremGenerator.fromEquirectangular(hdrTexture).texture;
scene.environment = envMap; // 讓金屬材質反射環境光
hdrTexture.dispose();
pmremGenerator.dispose();
});
說明:
- 金屬材質的 反射 依賴環境貼圖 (
scene.environment),若未設定,金屬感會顯得黯淡。
範例 4 – 動態調整 Roughness 與 Metalness
// 使用 GUI 讓使用者即時調整
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
const gui = new GUI();
gui.add(material, 'roughness', 0, 1, 0.01).name('粗糙度 (Uniform)');
gui.add(material, 'metalness', 0, 1, 0.01).name('金屬度 (Uniform)');
// 若想同時控制貼圖的強度,可使用 .multiplyScalar()
gui.add(roughnessMap, 'repeat', 0, 4, 0.1).name('Roughness 重複');
gui.add(metalnessMap, 'repeat', 0, 4, 0.1).name('Metalness 重複');
技巧:透過 GUI 可快速驗證不同粗糙度、金屬度對最終渲染的影響,對於調參非常有幫助。
範例 5 – 使用自訂 Shader 結合 Alpha 與 Normal
若需要更細緻的控制(例如 雙面透明),可以自行撰寫 ShaderMaterial:
const customMaterial = new THREE.ShaderMaterial({
uniforms: {
map: { value: albedoMap },
alphaMap: { value: alphaMap },
normalMap: { value: normalMap },
lightDirection: { value: new THREE.Vector3(0.5, 1, 0.8).normalize() }
},
vertexShader: /* glsl */`
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vUv = uv;
vNormal = normalize(normalMatrix * normal);
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: /* glsl */`
uniform sampler2D map;
uniform sampler2D alphaMap;
uniform sampler2D normalMap;
uniform vec3 lightDirection;
varying vec2 vUv;
varying vec3 vNormal;
void main() {
vec4 baseColor = texture2D(map, vUv);
float alpha = texture2D(alphaMap, vUv).r;
vec3 normalTex = texture2D(normalMap, vUv).rgb * 2.0 - 1.0;
vec3 finalNormal = normalize(vNormal + normalTex);
float ndotl = max(dot(finalNormal, lightDirection), 0.0);
gl_FragColor = vec4(baseColor.rgb * ndotl, alpha);
}
`,
transparent: true,
side: THREE.DoubleSide // 雙面渲染,避免透明面被剔除
});
提醒:自訂 Shader 雖然彈性大,但也會失去 Three.js 內建的 PBR 計算,僅適合特殊需求。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 最佳實踐 |
|---|---|---|
| Alpha 與深度排序衝突 | 透明物件的渲染順序會依深度排序,若場景中有多個透明物件,可能出現「背後的物件被前面的透明物件遮住」的情況。 | 使用 renderer.sortObjects = true(預設),或將透明物件分層渲染;必要時使用 depthWrite: false 讓透明物件不寫入深度緩衝。 |
| Normal 貼圖色彩空間錯誤 | 大多數圖像檔是 sRGB,若直接作為 Normal 貼圖會導致光照錯位。 | normalMap.encoding = THREE.LinearEncoding,或在載入時使用 loader.setTextureEncoding(THREE.LinearEncoding)。 |
| Roughness / Metalness 貼圖不匹配 | 若貼圖的灰階分佈過於極端(全白或全黑),材質會失去細節。 | 在 Photoshop 或 GIMP 中調整曲線,使灰階分佈更平衡;或在程式碼中使用 texture.minFilter = THREE.LinearFilter 以避免 MIPMAP 產生過度平滑。 |
| 環境貼圖缺失導致金屬材質暗淡 | 金屬材質需要環境光反射,沒有環境貼圖會看起來像普通塑膠。 | 使用 HDRI 或 CubeTexture 作為 scene.environment,並透過 PMREMGenerator 產生 pre‑filtered mipmaps。 |
| 過度使用透明度 | 大量使用透明度會增加 GPU 的混合計算,降低效能。 | 只在必要的部位使用 Alpha,其他部位盡量使用 裁剪 (alphaTest) 或 貼圖遮罩。 |
實際應用場景
遊戲中的樹葉與草地
- 使用 Alpha 貼圖 讓葉子呈現不規則形狀,搭配 Normal 貼圖 增強光照細節,提升沉浸感。
金屬機械零件
- 透過 Metalness 與 Roughness 貼圖,配合 HDR 環境貼圖,呈現真實的金屬反射與磨損感。
產品展示(例如手機、手錶)
- 以 Normal、Roughness、Metalness 結合 Alpha (透過玻璃或螢幕的透明度),製作高質感的交互式 3D 模型。
建築可視化
- 牆面、窗戶、金屬框架分別使用不同的貼圖組合,使光線在室內外產生自然的反射與散射。
總結
- Alpha、Normal、Roughness、Metalness 四種貼圖是 PBR 工作流程的核心,分別負責 透明度、細節法線、表面粗糙度與金屬屬性。
- 在 Three.js 中,只要正確設定材質屬性 (
alphaMap,normalMap,roughnessMap,metalnessMap) 並注意 透明度開啟、色彩空間、環境貼圖,即可快速得到高度寫實的渲染結果。 - 常見的陷阱包括 深度排序、色彩空間錯誤、環境貼圖缺失,透過本文提供的最佳實踐可有效避免。
- 透過實作範例與 GUI 調參,你能在開發過程中即時驗證貼圖對光照的影響,快速迭代出符合需求的視覺效果。
掌握這些貼圖的運用,你的 Three.js 專案將從「看起來還行」升級為「專業級的真實感渲染」。祝你在 3D 網頁開發的路上越走越遠! 🚀