bcjohn's blog
理解 Three.js 中 texture 的 offset, repeat 及 wrap
發布於: 2024-02-18 更新於: 2024-02-18 分類於: Three.js 閱讀次數: 

前言

Threejs 中的 texture(貼圖) 可以設置 offset, repeat 及 wrap 屬性,但大部分文章都只有文字說明而沒有視覺化的範例,所以寫了一個視覺化範例讓自己能夠理解

Demo

圖片渲染在 (x,y) 平面上,右方橘色線為 x 軸,上方綠色線為 y 軸

範例程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import * as THREE from 'three';

export class Plane {
constructor(experience) {
this.experience = experience;

this.create();
this.createDebugGUI();
}

// 創建 PlaneGeometry 並設置 texture
create() {
this.geometry = new THREE.PlaneGeometry(8, 8);
this.loader = new THREE.TextureLoader();
const texture = this.loader.load(
'https://cdn.pixabay.com/photo/2018/07/09/17/44/baby-elephant-3526681_1280.png'
);

this.material = new THREE.MeshBasicMaterial({ map: texture });
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.experience.scene.add(this.mesh);
}

// 設置 debug gui
createDebugGUI() {
this.experience.debug.gui
.add(this.material.map.offset, 'x')
.name('offsetX')
.min(-1)
.max(1)
.step(0.1);
this.experience.debug.gui
.add(this.material.map.offset, 'y')
.name('offsetY')
.min(-1)
.max(1)
.step(0.1);

this.experience.debug.gui
.add(this.material.map.repeat, 'x')
.name('repeatX')
.min(-10)
.max(10)
.step(1);
this.experience.debug.gui
.add(this.material.map.repeat, 'y')
.name('repeatY')
.min(-10)
.max(10)
.step(1);

this.experience.debug.gui
.add(this.material.map, 'wrapS')
.options({
RepeatWrapping: THREE.RepeatWrapping,
ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
MirroredRepeatWrapping: THREE.MirroredRepeatWrapping
})
.onChange(() => {
this.material.map.needsUpdate = true;
});
this.experience.debug.gui
.add(this.material.map, 'wrapT')
.options({
RepeatWrapping: THREE.RepeatWrapping,
ClampToEdgeWrapping: THREE.ClampToEdgeWrapping,
MirroredRepeatWrapping: THREE.MirroredRepeatWrapping
})
.onChange(() => {
this.material.map.needsUpdate = true;
});
}
}

Offset - 偏移

藉由設置 offset 可以改變 uv 座標對應到的紋理圖片,預設的 offset 為 0

1
2
material.map.offset.x = 0;
material.map.offset.y = 0;

offset 為 0,原圖位於正中間

offset.x 為 -0.5,圖片會往右半邊偏移
offset.x 為 0.5,圖片會往左半邊偏移
offset.y 為 -0.5,圖片會往上偏移
offset.y 為 0.5,圖片會往下偏移

這裡我一直有個疑問,為什麼 offset.x 設為 -0.5 的時候圖片是往右半邊偏移而不是往左半邊偏移呢?後來才想出一個合理的解釋,uv 座標的原點在左下角,而 offset.x = -0.5 的意思是整個 uv 座標系.x - 0.5 = 紋理圖片的位置.x,所以以右上角 uv 座標系的 (1, 1) 來看,x 減掉 0.5 後變成 (0.5, 1) 而這個 (0.5, 1) 對應到的是紋理圖片的位置,所以 offset.x 設為 -0.5 看到的結果就是圖片往右偏移了一半

圖片來源 1
圖片來源 2

Repeat

控制圖片在 uv 方向會 repeat 多少次,預設值為 1

1
2
material.map.repeat.x = 1;
material.map.repeat.y = 1;

repeat 為 1,原圖不會重複

repeat.x 跟 repeat.y 都設為 2,所以圖片總共重複 4 次
(另外這裡還設定了 wrap 的方式為 THREE.RepeatWrapping,所以才有辦法重複)

repeat.x 為 -1,設成負數的話圖片會左右顛倒
(另外這裡還設定了 wrapS 的方式為 THREE.RepeatWrapping)

Wrap

wrap 處理圖片未填滿時,如何渲染剩餘空間的方式,對應到 x, y 軸設定的屬性名稱分別是 wrapS 及 wrapT

1
2
material.map.wrapS = THREE.ClampToEdgeWrapping;
material.map.wrapT = THREE.ClampToEdgeWrapping;

threejs 中總共有三個值可以做設定:

  • THREE.RepeatWrapping: 重複渲染圖片
  • THREE.ClampToEdgeWrapping: 最後一個像素點會延伸到邊界 (預設值)
  • THREE.MirroredRepeatWrapping: 跟 RepeatWrapping 很像,差別是會鏡像的重複渲染

這三個值乍看之下很抽象,以下我們來看不同範例的演示,會比較清楚

THREE.RepeatWrapping

repeat.x 跟 repeat.y 都設為 2,所以圖片總共重複 4 次

THREE.ClampToEdgeWrapping

repeat.x 跟 repeat.y 都設為 2,但因為 wrapS 及 wrapT 都設為 THREE.ClampToEdgeWrapping,未填滿的地方會參考最後一個像素點做延伸,而這張大象左邊的耳朵及尾巴分別是最遠的像素點,所以會從這兩點延伸到邊界

THREE.MirroredRepeatWrapping

repeat.x 跟 repeat.y 都設為 2,所以圖片總共重複 4 次,但因為是鏡像渲染,所以上下左右的圖片分別是對稱的

版本

  • threejs r159

參考資料

ThreeJs入门18-纹理的重复和纹理的回环
three.js UV映射简述