Unity3D學習筆記2——繪製一個帶紋理的面

charlee44發表於2021-07-06

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資料夾下新建一個材質,並把想使用的紋理圖片檔案移到這個資料夾下:
imglink1

點選新建的材質,在Inspector檢視中,將紋理圖片掛載到這個材質上:
imglink2

Unity3D新建的材質預設為標準,是一種PBR材質,由多種貼圖混合而成。我們這裡暫時只設定Albedo貼圖,也就是基本顏色貼圖。實際使用時,右邊的顏色拾取也能影響到貼圖效果,在有貼圖時,可以將其拾取成白色。

2.2.2. 使用材質

在編輯器中把材質建立好之後,在指令碼中就可以直接使用建立好的材質了:

MeshRenderer meshRenderer = newGameObject.AddComponent<MeshRenderer>();                
Material material = Resources.Load<Material>("MaterialDemo");   
meshRenderer.material = material;

2.3. 光照

點選Play,會發現雖然顯示了一個帶紋理的面,但是面的顏色顯得很暗:
imglink3

這是因為光照的位置不對,材質缺少對光照的影響。那麼我們調整預設光照Directional Light的Transform,將其調整到和攝像機的位置一致:
imglink4

這個時候的光照正好對準了面的正中間:
imglink5

最終Game檢視中的面也按照正常亮度顯示了:
imglink6

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

相關文章