UV坐標
UV
是一種紋理坐標系統,與物體的空間坐標 (x, y, z) 屬於不同的坐標系。 UV 座標是用來映射紋理到物體表面,它與物體坐標之間存在映射關係,但這種關係不一定是線性或唯一的。
- (x, y, z) 是世界或局部空間座標,表示物體在 3D 空間中的位置(Transform 座標),使用笛卡兒坐標系
- (U, V) 是 2D 紋理座標,通常在 0~1 範圍內,用來對應貼圖的像素
假設紋理大小是 512x512 但我們不會用 512x512 去做映射,因為 UV 座標的範圍是 0 到 1 之間,所以 512x512 大小的圖片就會被轉換為 uv(0/512,0/512) 至 (512/512,512/512)。這麼做的話即使你的紋理不是 512x512,而是 1024x1024 或其他大小,UV 仍然適用,因為它們代表的是 相對位置,而不是像素座標。
當我們想要檢查 Mesh 的頂點索引、 UV 座標,或三角形組成方式,我們可以使用 MeshFilter 來取得 Mesh
- 使用 mesh.triangles 可以查看三角形索引
- mesh.triangles 是一個 整數陣列,每 3 個數字代表一個三角形的 3 個頂點索引。
- 這些索引對應到 mesh.vertices 陣列中的頂點。
- 使用 mesh.vertices 找到 頂點的 3D 位置
- mesh.vertices 是一個 Vector3[] 陣列,存放 Mesh 的每個頂點在 世界空間中的 3D 座標。
- 這些頂點的 順序對應 mesh.triangles 的索引
- 使用 mesh.uv 來查看 UV 座標
- mesh.uv 是一個 Vector2[] 陣列,存放 Mesh 每個頂點對應的 UV 座標。
- UV 是 貼圖座標,範圍通常是 (0,0) 到 (1,1),但如果是 貼圖 Atlas 或特殊映射方式,可能會超出這個範圍。
接下來我們建立一個 Quad 並設定他的 Materail 試試看。
- 建立一個 512x512 的圖片
- 建立一個 Material , 將其設為
Unlit/Texture
,在把 512x512 的圖片放到這個 Material 中。 - 建立一個 Quad , 並將剛剛建立的 Material 賦給這個 Quad
- Unity 中 Quad(矩形)通常有 4 個頂點,在 Unity 內部,它其實是由兩個三角形組成的。
- 在 Quad GameObject 上建立一個 Script
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
36public class Test : MonoBehaviour
{
Mesh mesh;
public Vector2[] uvs;
private void Awake()
{
uvs = new Vector2[4];
uvs[0] = new Vector2(0f, 0.75f); // 左下
uvs[1] = new Vector2(0.25f, 0.75f); // 右下
uvs[2] = new Vector2(0f, 1); // 左上
uvs[3] = new Vector2(0.25f, 1); // 右上
mesh = GetComponent<MeshFilter>().mesh;
for (int i = 0; i < mesh.vertexCount; i++)
{
Debug.Log($"頂點索引 {i} 的 3D 位置: {mesh.vertices[i]}");
}
for (int i = 0; i < mesh.triangles.Length; i += 3)
{
Debug.Log($"三角形 {i / 3}: {mesh.triangles[i]}, {mesh.triangles[i + 1]}, {mesh.triangles[i + 2]}");
}
}
private void LateUpdate()
{
mesh.SetUVs(0, uvs);
}
} - 執行,會發現顯示的是紅色1的區塊,這是因為我們設定了以下 UV 座標,對照下圖可以清楚看到
(0,0.75)
,(0.25,0.75)
,(0,1)
,(0.25,1)
就是 紅色1 的區塊。- uvs[0] = new Vector2(0f, 0.75f); // 左下
- uvs[1] = new Vector2(0.25f, 0.75f); // 右下
- uvs[2] = new Vector2(0f, 1); // 左上
- uvs[3] = new Vector2(0.25f, 1); // 右上
- 使用 mesh.triangles 可以查看三角形索引會印出
1
2
3
4for (int i = 0; i < mesh.triangles.Length; i += 3)
{
Debug.Log($"三角形 {i / 3}: {mesh.triangles[i]}, {mesh.triangles[i + 1]}, {mesh.triangles[i + 2]}");
}
- 三角形 0: 0, 3, 1
- 三角形 1: 3, 0, 2
這表示有兩個三角形, - 第一個三角形 0 的頂點索引是 0, 3, 1
- 第二個三角形 0 的頂點索引是 3, 0, 2
- 之後我們可以使用 mesh.vertices 找到 頂點的 3D 位置會印出
1
2
3
4for (int i = 0; i < mesh.vertexCount; i++)
{
Debug.Log($"頂點索引 {i} 的 3D 位置: {mesh.vertices[i]}");
}
- 頂點索引 0 的 3D 位置: (-0.50, -0.50, 0.00)
- 頂點索引 1 的 3D 位置: (0.50, -0.50, 0.00)
- 頂點索引 2 的 3D 位置: (-0.50, 0.50, 0.00)
- 頂點索引 3 的 3D 位置: (0.50, 0.50, 0.00)
- 將 Quad 放到坐標系上看就很清楚了
參考
Power 對 UV 的影響
當你對 UV 使用 Power,會導致貼圖的變形,因為 UV 不是線性的了。
舉例:UV 使用 Power
UV.xy → Power(UV.xy, 2)
Power(UV, 1) 👉 不變
Power(UV, 2) 👉 使貼圖集中在 (0,0),朝右上角壓縮
- (0.5,0.2) 經過 Power(UV, 2) 之後,變為 (0.025,0.04)
Power(UV, 0.5) 👉 擴展貼圖,使細節集中在 (1,1)