本文 AI 產出,尚未審核

Three.js 教學 – 貼圖 (Textures):載入 TextureLoader

簡介

在 3D 網頁開發中,貼圖是讓模型表面呈現真實感的關鍵。沒有貼圖的幾何體只是一個單色的平面或立方體,難以表達材質、顏色與細節。Three.js 提供了功能完整且易於使用的 TextureLoader,讓開發者只需幾行程式碼,就能把 JPEG、PNG、甚至 HDR 圖檔載入並套用到材質上。

本篇文章將從 概念說明、實作範例、常見陷阱與最佳實踐,一步步帶你掌握 TextureLoader 的使用方式,並展示在實務專案中常見的應用情境。即使你是剛接觸 Three.js 的新手,也能在閱讀完本篇後,快速在自己的作品裡加入高品質的貼圖。


核心概念

1. 為什麼使用 TextureLoader?

  • 非同步載入:瀏覽器會在背景下載影像檔,避免阻塞主執行緒。
  • 自動產生 MIPMAP:對遠距離或縮小的貼圖自動產生多層級影像,提高渲染效能與抗鋸齒。
  • 支援跨域:可設定 crossOrigin 屬性,載入 CDN 或其他網域的資源。

註:在 Three.js 0.150 之後,TextureLoader 預設使用 WebGL2 的 Sampler,若要手動調整過濾方式,需要透過 texture.minFilter、texture.magFilter 等屬性。

2. 基本使用流程

  1. 建立 TextureLoader 實例
  2. 呼叫 .load( url, onLoad, onProgress, onError )
  3. 在 onLoad 回呼中取得 THREE.Texture 物件
  4. 把貼圖指派給材質的 map(或 normalMap、roughnessMap 等)
const loader = new THREE.TextureLoader();
loader.load(
  'textures/brick_diffuse.jpg',
  ( texture ) => {
    // 貼圖載入完成
    const material = new THREE.MeshStandardMaterial({ map: texture });
    const mesh = new THREE.Mesh( geometry, material );
    scene.add( mesh );
  },
  undefined,
  ( err ) => console.error('貼圖載入失敗', err)
);

3. 進階設定:crossOrigin、encoding、flipY

  • crossOrigin:若貼圖位於不同網域,需要設定 loader.setCrossOrigin( 'anonymous' ),否則會因 CORS 政策被阻擋。
  • encoding:對於 sRGB 影像(如大多數 JPEG/PNG),應設定 texture.encoding = THREE.sRGBEncoding,讓顏色在 gamma 空間正確顯示。
  • flipY:WebGL 預設把貼圖 Y 軸翻轉,若貼圖已經做好翻轉(例如使用 Photoshop),可以 texture.flipY = false。

程式碼範例

以下提供 五個 常見且實用的範例,從最簡單的載入到使用 LoadingManager、重複貼圖與立方體貼圖(CubeTexture)等進階技巧,皆附上詳細註解說明。

範例 1:最簡單的貼圖載入

// 建立場景、相機、渲染器(略)
const geometry = new THREE.BoxGeometry(2, 2, 2);
const loader = new THREE.TextureLoader();

loader.load('assets/textures/wood.jpg', (tex) => {
  // 設定貼圖編碼為 sRGB,避免顏色偏暗
  tex.encoding = THREE.sRGBEncoding;

  const material = new THREE.MeshStandardMaterial({ map: tex });
  const cube = new THREE.Mesh(geometry, material);
  scene.add(cube);
});

重點:只要把 Texture 指派給 material.map,Three.js 會自動建立 UV 坐標(若模型已有 UV)。

範例 2:使用 LoadingManager 同步管理多張貼圖

// 建立一個 LoadingManager,所有貼圖的載入進度都會集中在這裡
const manager = new THREE.LoadingManager();
manager.onStart = () => console.log('開始載入貼圖...');
manager.onProgress = (url, itemsLoaded, itemsTotal) =>
  console.log(`已載入 ${itemsLoaded}/${itemsTotal}: ${url}`);
manager.onLoad = () => console.log('全部貼圖載入完成!');
manager.onError = (url) => console.error('載入失敗:', url);

const loader = new THREE.TextureLoader(manager);

// 同時載入三張貼圖
const diffuse = loader.load('textures/metal_diffuse.jpg');
const normal  = loader.load('textures/metal_normal.jpg');
const rough   = loader.load('textures/metal_roughness.jpg');

// 設定編碼與貼圖參數
diffuse.encoding = THREE.sRGBEncoding;
normal.encoding  = THREE.LinearEncoding;

// 建立材質
const material = new THREE.MeshStandardMaterial({
  map: diffuse,
  normalMap: normal,
  roughnessMap: rough,
});

技巧:LoadingManager 可與 GLTFLoader、OBJLoader 等其他載入器共用,便於顯示全局載入進度條。

範例 3:重複貼圖(Repeating Texture)

const loader = new THREE.TextureLoader();
loader.load('textures/tiles.png', (tex) => {
  // 讓貼圖在 U、V 方向各重複 4 次
  tex.wrapS = THREE.RepeatWrapping;   // 水平 (U) 包裝模式
  tex.wrapT = THREE.RepeatWrapping;   // 垂直 (V) 包裝模式
  tex.repeat.set(4, 4);               // 重複次數

  const material = new THREE.MeshStandardMaterial({ map: tex });
  const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(10, 10),
    material
  );
  scene.add(plane);
});

說明:若不設定 wrapS/T 為 THREE.RepeatWrapping,repeat 屬性不會生效,貼圖仍會只顯示一次。

範例 4:立方體貼圖(CubeTexture)製作環境貼圖

// 立方體貼圖需要六張影像:px, nx, py, ny, pz, nz
const path = 'textures/cube/skybox/';
const format = '.jpg';
const urls = [
  path + 'px' + format, path + 'nx' + format,
  path + 'py' + format, path + 'ny' + format,
  path + 'pz' + format, path + 'nz' + format,
];

const cubeLoader = new THREE.CubeTextureLoader();
const envMap = cubeLoader.load(urls, () => {
  console.log('環境貼圖載入完成');
});

// 設定環境貼圖的編碼
envMap.encoding = THREE.sRGBEncoding;

// 把環境貼圖套用到場景與材質
scene.background = envMap; // 背景顯示
const material = new THREE.MeshStandardMaterial({
  envMap: envMap,
  metalness: 1,
  roughness: 0,
});

應用:環境貼圖不只能作為背景,還能提供 鏡射反射(envMap)與 光照(PMREMGenerator)的資訊。

範例 5:自訂貼圖過濾與 MIPMAP 設定(適合高效能需求)

const loader = new THREE.TextureLoader();
loader.load('textures/grass.jpg', (tex) => {
  // 關閉自動產生 MIPMAP(若貼圖已是 MIPMAP)
  tex.generateMipmaps = false;

  // 設定過濾方式:放大使用線性過濾,縮小使用最近點過濾
  tex.magFilter = THREE.LinearFilter;
  tex.minFilter = THREE.NearestFilter; // 只在遠距離保留原始像素

  // 若貼圖尺寸不是 2 的次方,必須手動關閉 MIPMAP
  if (!THREE.MathUtils.isPowerOfTwo(tex.image.width) ||
      !THREE.MathUtils.isPowerOfTwo(tex.image.height)) {
    tex.generateMipmaps = false;
    tex.minFilter = THREE.LinearFilter;
  }

  const material = new THREE.MeshStandardMaterial({ map: tex });
  const plane = new THREE.Mesh(
    new THREE.PlaneGeometry(20, 20),
    material
  );
  scene.add(plane);
});

說明:非 2 的次方貼圖(如 1024×768)在 WebGL 中無法自動產生 MIPMAP,若不關閉會導致渲染錯誤或警告。


常見陷阱與最佳實踐

陷阱 可能的症狀 解決方案
未設定 encoding 顏色偏暗、對比度不正確 texture.encoding = THREE.sRGBEncoding(對於大部分 JPEG/PNG)
跨域 CORS 錯誤 Failed to load resource: net::ERR_FAILED 在伺服器端加上 Access-Control-Allow-Origin:*,或使用 loader.setCrossOrigin('anonymous')
貼圖尺寸非 2 的次方 MIPMAP 警告、模糊或條紋 使用圖形編輯工具調整為 256、512、1024…,或在程式中關閉 generateMipmaps
忘記更新 needsUpdate 改變貼圖屬性後無效 texture.needsUpdate = true;
同時載入過多貼圖 網路壅塞、載入時間過長 使用 LoadingManager 監控,或先載入低解析度貼圖作為佔位 (LOD)

最佳實踐

  1. 統一貼圖編碼:在專案的貼圖匯入流程中,統一使用 sRGBEncoding,避免顏色不一致。
  2. 使用 PMREMGenerator 產生環境貼圖的預濾波貼圖:對於 PBR 材質,envMap 必須經過預濾波才能正確呈現光照。
  3. 將常用貼圖放入快取(Cache):Three.js 內部會自動快取同一 URL 的貼圖,但在大型專案中可自行維護貼圖字典,避免重複載入。
  4. 結合 requestAnimationFrame 控制載入進度條:在 LoadingManager 的 onProgress 中更新 UI,提升使用者體驗。
  5. 適度壓縮貼圖:使用 WebP、Basis 或 KTX2(支援 GPU 壓縮)可大幅減少下載大小,尤其在行動裝置上效果顯著。

實際應用場景

場景 需求 使用 TextureLoader 的方式
建築可視化 大量磚牆、木材、金屬貼圖 透過 LoadingManager 同時載入多套材質,並使用 repeat 讓同一貼圖重複鋪陳於牆面
遊戲角色 皮膚、衣服的漫反射與法線貼圖 把 diffuseMap、normalMap、roughnessMap 結合在同一 MeshStandardMaterial,並設定 encoding
產品展示 金屬或玻璃的高光反射 使用 CubeTextureLoader 製作環境貼圖,再把 envMap 套用到金屬材質,配合 PMREMGenerator 取得平滑的光照
AR / VR 需要低延遲的貼圖載入 事先在 onBeforeRender 前先行載入貼圖,或在進入場景前完成全部貼圖的預載 (preload)
資料視覺化 以貼圖表示不同資料層級 依據資料值動態產生 CanvasTexture,然後使用 TextureLoader 載入作為材質的 map

總結

  • TextureLoader 是 Three.js 中最常用、最直觀的貼圖載入工具,支援非同步、跨域、編碼與翻轉等多項設定。
  • 透過 LoadingManager、repeat、CubeTextureLoader 等配套功能,我們可以在保持效能的同時,快速完成 PBR、環境映射、重複貼圖 等需求。
  • 常見的錯誤多與 CORS、貼圖編碼、非 2 的次方尺寸 相關,只要在開發流程中加入檢查與統一設定,就能避免大多數問題。
  • 最後,別忘了在專案中加入 貼圖快取、進度條 以及 GPU 壓縮貼圖(如 Basis/KTX2),讓你的 Three.js 應用在 桌面、行動 兩端都能保持流暢與美觀。

祝你在 Three.js 的世界裡玩得開心,創造出令人驚艷的 3D 體驗! 🎉