Unity3D學習筆記1——繪製一個三角形
- 2021 年 6 月 26 日
- 筆記
- helloworld, unity3d, 三角形, 教程
1. 緒論
最近想學習一下Unity3d,無奈發現現在大部分教程不僅是影片形式的,面對的也是美術、設計之類的非程式設計師,更多的時候都是把Unity3d當作PS一樣的工具來用,真正面對程式開發的教程反而非常少,更不用說希望能研究到一些底層圖形技術的技術工作者了。
說一下我看的兩本Unity3d書籍吧。第一本是《Unity 3D遊戲開發(第2版)》(宣雨松 著)。這本書算是大部分教程書籍中評價比較好的了,很多人推薦。不過個人感覺作者對Unity3D的知識有了太多的積累,已經忘記了初學者初學Unity3D的心態,知識也顯得比較零散。不過這也是中國大多數書籍的通病了,更像是作者對知識的總結而不是成體系的向讀者介紹知識。建議初學者看這本書一定要實操,喜歡頭腦風暴的同學不適合這本書。
看的第二本書是《Unity Shader入門精要》(馮樂樂 著)。令人佩服的是這本書是位程式媛寫的,可能正是因為如此,這本書寫的確實非常細緻到位。尤其是前面幾章對渲染管線的描述,從Unity3D圖形技出發,已然上升到電腦圖形學的高度上,對學習其他的圖形技術也有非常大的幫助(畢竟很多圖形技術都是通用的)。當時看了覺得確實很不錯,因此還送了同事一本。
最後就是自己也想總結一下Unity3D的相關知識吧,本身是個程式猿,當然更多的會偏向遊戲開發的程式設計師角度,或者圖形技術的程式設計師的角度一點。
2. 概述
圖形渲染技術的第一個HelloWorld當然應該就是繪製一個三角形了。在絕大多數情況下,三角面是渲染物體的基礎圖元。作為高級的渲染引擎,像三角面這樣的幾何體甚至不需要我們去通過程式碼來繪製,但是卻是我們學習的基礎,立足於這個基礎,我們以後能夠渲染更加複雜的圖形。
3. 詳論
3.1. 準備
通過Unity Hub創建一個3D工程:
進入Unity3D環境,通過右鍵菜單,在”Hierarchy”視圖中添加一個名為”Root”空的GameObject:
GameObject對象是Unity3D中得一個基礎類,Unity3D中得絕大部分對象都是基於它實現的,比如相機、燈光、或者模型等。所以我們這裡把創建的名為Root的GameObject對象作為場景的根節點。
在Root對象的Inspector面板中,可以看到一個”Add Component”按鈕:
也就是說,通過”Add Component”按鈕,我們可以掛接一些組件,這樣,空的GameObject對象就成為了其他類型的對象。例如,我這裡掛接一個C#腳本,通過C#腳本來繪製物體,那麼這個GameObject,表示的就是一個渲染的物體。
在”Project”視圖中,通過右鍵菜單創建一個C#腳本:
通過Root對象的Inspector面板中的”Add Component”按鈕,將這個腳本,掛接到Root對象下:
3.2. 實現
通過”Project”視圖的右鍵菜單中打開這個C#工程,可以看到我們添加的腳本”Main.CS”:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
這個腳本提供了兩個方法:
- Start()表示初始化(第一幀)的時候需要更新的內容,通常用於初始化之後不再更新的內容。比如我們會在這裡繪製一個物體。
- Update()表示每一幀都需要實時更新的內容,比如相機與滑鼠鍵盤事件的交互。
那麼就在Start()中進行繪製一個三角形的操作:
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 = GreateTriangle();
triangleGameObject.transform.parent = main.transform;
}
GameObject GreateTriangle()
{
string name = "triangle";
Mesh mesh = new Mesh();
mesh.name = name;
Vector3[] vertices = new Vector3[3]
{
new Vector3(0, 0, 0),
new Vector3(0, 10, 0),
new Vector3(10, 0, 0)
};
mesh.vertices = vertices;
int[] triangles = new int[3] { 0, 1, 2 };
mesh.triangles = triangles;
GameObject triangleGameObject = new GameObject(name);
MeshFilter mf = triangleGameObject.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
MeshRenderer meshRenderer = triangleGameObject.AddComponent<MeshRenderer>();
return triangleGameObject;
}
// Update is called once per frame
void Update()
{
}
}
3.3. 解析
3.3.1. 場景樹對象
在Start()函數中,首先我們找到了場景根節點Root,然後又通過調用GreateTriangle()函數,創建了一個三角形的GameObject對象,最後把這個三角形對象掛接到Root對象下:
void Start()
{
GameObject main = GameObject.Find("/Root");
if (main == null)
{
return;
}
GameObject triangleGameObject = GreateTriangle();
triangleGameObject.transform.parent = main.transform;
}
可以看到子對象掛接到父對象是通過GameObject對象中Transform對象來掛接的,這其實體現了一種思維的體現:Transform其實是表達GameObject對象空間位置的的4X4矩陣,父節點設置Transform會影響到子節點的位置,子節點的初始位置都是基於父節點的Transform開始計算的。通過這種方式,再複雜的場景也可以組織成一個場景樹節點:
3.3.2. 繪製方法
經過圖形技術的多年發展,現在大部分影像渲染引擎都會把渲染的物體封裝成兩種對象:渲染物體的骨架封裝成Mesh(網格),因為絕大多數物體都是通過一個個三角面片渲染出來的;渲染物體的血肉封裝成Material(材質),影響最終渲染的效果,如物體的光感、質地。
所以,為了繪製一個三角形,當然應該先繪製一個Mesh:
GameObject GreateTriangle()
{
string name = "triangle";
Mesh mesh = new Mesh();
mesh.name = name;
Vector3[] vertices = new Vector3[3]
{
new Vector3(0, 0, 0),
new Vector3(0, 10, 0),
new Vector3(10, 0, 0)
};
mesh.vertices = vertices;
int[] triangles = new int[3] { 0, 1, 2 };
mesh.triangles = triangles;
//...
}
這裡,我們給Mesh傳入了三個頂點,以及頂點的三角面索引。三角面索引表示的是按照索引的順序,通過頂點進行繪製,這樣就可以使用較少的頂點進行繪製,節約空間,畢竟Mesh中很多三角面片是共頂點的。
接下來,給GameObject增加一個MeshFilter組件,通過這個組件掛接剛創建的Mesh;給GameObject增加一個MeshRenderer組件,這個組件是用來掛接Material的,不過暫時沒有用上Material(但是必須增加MeshRenderer組件,否則不會顯示物體)。
GameObject GreateTriangle()
{
//...
GameObject triangleGameObject = new GameObject(name);
MeshFilter mf = triangleGameObject.AddComponent<MeshFilter>();
mf.sharedMesh = mesh;
MeshRenderer meshRenderer = triangleGameObject.AddComponent<MeshRenderer>();
return triangleGameObject;
}
4. 結果
點擊”Play”,運行結果如下: