1. 概述
上一篇文章《Unity3D學習筆記1——繪製一個三角形》中介紹了Unity3D的HelloWorld——繪製一個簡單的三角形。不過這個三角形太簡單了,連材質都沒有。那麼這裡就將三角形擴充套件為一個矩形的面,並且為這個面貼上紋理。
2. 詳論
2.1. 網格(Mesh)
前面說到網格是渲染物體的骨架,因此還是先要把渲染物體的架子搭好。改進一下上一篇文章中的建立Mesh的程式碼:
Mesh mesh = new Mesh();
mesh.name = name;
Vector3[] vertices = new Vector3[4]
{
new Vector3(-5, -5, 0),
new Vector3(-5, 5, 0),
new Vector3(5, -5, 0),
new Vector3(5, 5, 0),
};
mesh.vertices = vertices;
Vector2[] uv = new Vector2[4]
{
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 0),
new Vector2(1, 1),
};
mesh.uv = uv;
Vector3[] normals = new Vector3[4]
{
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
};
mesh.normals = normals;
//mesh.RecalculateNormals();
int[] triangles = new int[6] { 0, 1, 2, 1, 3, 2 };
mesh.triangles = triangles;
GameObject newGameObject = new GameObject(name);
MeshFilter mf = newGameObject.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
2.1.1. 頂點
因為我們要建立一個矩形的面,所以需要建立四個頂點。仍然是像之前建立三角面的頂點一樣,賦予頂點的空間位置屬性xyz座標。同時,我們還給Mesh賦予了4個uv座標,4個法向量normal。uv座標是用來計算紋理座標的,也就是當物體貼上紋理之後的紋理座標位置;法向量是用來參與光照計算的,如果缺少法向量,很多材質的效果不正確。可以通過mesh.RecalculateNormals()讓Unity3D自己計演算法向量。
位置(position/vertice)、紋理座標(uv/texCoord)、法向量(normal)是經常用到了三個頂點屬性,但是頂點屬性也不僅僅只有三個,甚至可以根據需要自定義。
2.1.2. 頂點索引
一個矩形面確定了四個頂點,但是需要劃分成兩個三角形,每個三角形引用3個頂點索引,也就是6個頂點索引。當然我們也可以使用6個頂點,按照自然順序來確定頂點索引。但是這樣一來,就浪費了空間儲存。這也是使用頂點索引的好處,可以節省空間,畢竟Mesh中的很多頂點是共用的。
2.2. 材質(Material)
接下來我們在Unity3D編輯器中建立一個材質,並且在C#指令碼中將這個材質給到我們建立的面上。
2.2.1. 建立材質
材質和紋理(圖片)在Unity3D中被認為是一種資源,要載入他們需要特定的辦法。一種比較簡單的辦法是使用Resources.Load。
在Assets目錄下建立一個名為Resources的資料夾,只有使用這個目錄下的資源,使用Resources.Load才能找到。在Resources資料夾下新建一個材質,並把想使用的紋理圖片檔案移到這個資料夾下:
點選新建的材質,在Inspector檢視中,將紋理圖片掛載到這個材質上:
Unity3D新建的材質預設為標準,是一種PBR材質,由多種貼圖混合而成。我們這裡暫時只設定Albedo貼圖,也就是基本顏色貼圖。實際使用時,右邊的顏色拾取也能影響到貼圖效果,在有貼圖時,可以將其拾取成白色。
2.2.2. 使用材質
在編輯器中把材質建立好之後,在指令碼中就可以直接使用建立好的材質了:
MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>();
Material material = Resources.Load<Material>("MaterialDemo");
meshRenderer.material = material;
2.3. 光照
點選Play,會發現雖然顯示了一個帶紋理的面,但是面的顏色顯得很暗:
這是因為光照的位置不對,材質缺少對光照的影響。那麼我們調整預設光照Directional Light的Transform,將其調整到和攝像機的位置一致:
這個時候的光照正好對準了面的正中間:
最終Game檢視中的面也按照正常亮度顯示了:
3. 程式碼
全部的C#指令碼程式碼如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
GameObject main = GameObject.Find("/Root");
if (main == null)
{
return;
}
GameObject triangleGameObject = GreateQuad();
triangleGameObject.transform.parent = main.transform;
}
GameObject GreateQuad()
{
string name = "quad";
Mesh mesh = new Mesh();
mesh.name = name;
Vector3[] vertices = new Vector3[4]
{
new Vector3(-5, -5, 0),
new Vector3(-5, 5, 0),
new Vector3(5, -5, 0),
new Vector3(5, 5, 0),
};
mesh.vertices = vertices;
Vector2[] uv = new Vector2[4]
{
new Vector2(0, 0),
new Vector2(0, 1),
new Vector2(1, 0),
new Vector2(1, 1),
};
mesh.uv = uv;
Vector3[] normals = new Vector3[4]
{
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
new Vector3(0, 0, -1),
};
mesh.normals = normals;
//mesh.RecalculateNormals();
int[] triangles = new int[6] { 0, 1, 2, 1, 3, 2 };
mesh.triangles = triangles;
GameObject newGameObject = new GameObject(name);
MeshFilter mf = newGameObject.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>();
Material material = Resources.Load<Material>("MaterialDemo");
meshRenderer.material = material;
return newGameObject;
}
// Update is called once per frame
void Update()
{
}
}