本文 AI 產出,尚未審核

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 同時使用,否則透明度不會被渲染。
  • metalnessroughness 的預設值會與貼圖相乘,若貼圖已提供完整資訊,可將預設值設為 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 產生過度平滑。
環境貼圖缺失導致金屬材質暗淡 金屬材質需要環境光反射,沒有環境貼圖會看起來像普通塑膠。 使用 HDRICubeTexture 作為 scene.environment,並透過 PMREMGenerator 產生 pre‑filtered mipmaps。
過度使用透明度 大量使用透明度會增加 GPU 的混合計算,降低效能。 只在必要的部位使用 Alpha,其他部位盡量使用 裁剪 (alphaTest)貼圖遮罩

實際應用場景

  1. 遊戲中的樹葉與草地

    • 使用 Alpha 貼圖 讓葉子呈現不規則形狀,搭配 Normal 貼圖 增強光照細節,提升沉浸感。
  2. 金屬機械零件

    • 透過 MetalnessRoughness 貼圖,配合 HDR 環境貼圖,呈現真實的金屬反射與磨損感。
  3. 產品展示(例如手機、手錶)

    • NormalRoughnessMetalness 結合 Alpha (透過玻璃或螢幕的透明度),製作高質感的交互式 3D 模型。
  4. 建築可視化

    • 牆面、窗戶、金屬框架分別使用不同的貼圖組合,使光線在室內外產生自然的反射與散射。

總結

  • Alpha、Normal、Roughness、Metalness 四種貼圖是 PBR 工作流程的核心,分別負責 透明度、細節法線、表面粗糙度與金屬屬性
  • 在 Three.js 中,只要正確設定材質屬性 (alphaMap, normalMap, roughnessMap, metalnessMap) 並注意 透明度開啟色彩空間環境貼圖,即可快速得到高度寫實的渲染結果。
  • 常見的陷阱包括 深度排序、色彩空間錯誤、環境貼圖缺失,透過本文提供的最佳實踐可有效避免。
  • 透過實作範例與 GUI 調參,你能在開發過程中即時驗證貼圖對光照的影響,快速迭代出符合需求的視覺效果。

掌握這些貼圖的運用,你的 Three.js 專案將從「看起來還行」升級為「專業級的真實感渲染」。祝你在 3D 網頁開發的路上越走越遠! 🚀