Unity3D學習筆記1——繪製一個三角形

charlee44發表於2021-06-26

1. 緒論

最近想學習一下Unity3d,無奈發現現在大部分教程不僅是視訊形式的,面對的也是美術、設計之類的非程式設計師,更多的時候都是把Unity3d當作PS一樣的工具來用,真正面對程式開發的教程反而非常少,更不用說希望能研究到一些底層圖形技術的技術工作者了。

說一下我看的兩本Unity3d書籍吧。第一本是《Unity 3D遊戲開發(第2版)》(宣雨鬆 著)。這本書算是大部分教程書籍中評價比較好的了,很多人推薦。不過個人感覺作者對Unity3D的知識有了太多的積累,已經忘記了初學者初學Unity3D的心態,知識也顯得比較零散。不過這也是國內大多數書籍的通病了,更像是作者對知識的總結而不是成體系的向讀者介紹知識。建議初學者看這本書一定要實操,喜歡頭腦風暴的同學不適合這本書。

看的第二本書是《Unity Shader入門精要》(馮樂樂 著)。令人佩服的是這本書是位程式媛寫的,可能正是因為如此,這本書寫的確實非常細緻到位。尤其是前面幾章對渲染管線的描述,從Unity3D圖形技出發,已然上升到計算機圖形學的高度上,對學習其他的圖形技術也有非常大的幫助(畢竟很多圖形技術都是通用的)。當時看了覺得確實很不錯,因此還送了同事一本。

最後就是自己也想總結一下Unity3D的相關知識吧,本身是個程式猿,當然更多的會偏向遊戲開發的程式設計師角度,或者圖形技術的程式設計師的角度一點。

2. 概述

圖形渲染技術的第一個HelloWorld當然應該就是繪製一個三角形了。在絕大多數情況下,三角面是渲染物體的基礎圖元。作為高階的渲染引擎,像三角面這樣的幾何體甚至不需要我們去通過程式碼來繪製,但是卻是我們學習的基礎,立足於這個基礎,我們以後能夠渲染更加複雜的圖形。

3. 詳論

3.1. 準備

通過Unity Hub建立一個3D工程:
imglink1

進入Unity3D環境,通過右鍵選單,在"Hierarchy"檢視中新增一個名為"Root"空的GameObject:
imglink2

GameObject物件是Unity3D中得一個基礎類,Unity3D中得絕大部分物件都是基於它實現的,比如相機、燈光、或者模型等。所以我們這裡把建立的名為Root的GameObject物件作為場景的根節點。

在Root物件的Inspector皮膚中,可以看到一個"Add Component"按鈕:
imglink3

也就是說,通過"Add Component"按鈕,我們可以掛接一些元件,這樣,空的GameObject物件就成為了其他型別的物件。例如,我這裡掛接一個C#指令碼,通過C#指令碼來繪製物體,那麼這個GameObject,表示的就是一個渲染的物體。

在"Project"檢視中,通過右鍵選單建立一個C#指令碼:
imglink4

通過Root物件的Inspector皮膚中的"Add Component"按鈕,將這個指令碼,掛接到Root物件下:
imglink5

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()
    {        
    }
}

這個指令碼提供了兩個方法:

  1. Start()表示初始化(第一幀)的時候需要更新的內容,通常用於初始化之後不再更新的內容。比如我們會在這裡繪製一個物體。
  2. 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開始計算的。通過這種方式,再複雜的場景也可以組織成一個場景樹節點:
imglink6

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",執行結果如下:
imglink7
imglink8

相關文章