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. 基本使用流程
- 建立
TextureLoader實例 - 呼叫
.load( url, onLoad, onProgress, onError ) - 在
onLoad回呼中取得THREE.Texture物件 - 把貼圖指派給材質的
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) |
最佳實踐
- 統一貼圖編碼:在專案的貼圖匯入流程中,統一使用
sRGBEncoding,避免顏色不一致。 - 使用
PMREMGenerator產生環境貼圖的預濾波貼圖:對於 PBR 材質,envMap必須經過預濾波才能正確呈現光照。 - 將常用貼圖放入快取(Cache):Three.js 內部會自動快取同一 URL 的貼圖,但在大型專案中可自行維護貼圖字典,避免重複載入。
- 結合
requestAnimationFrame控制載入進度條:在LoadingManager的onProgress中更新 UI,提升使用者體驗。 - 適度壓縮貼圖:使用 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 體驗! 🎉