Unity3D学习笔记2——绘制一个带纹理的面

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()
    {
        
    }
}
posted @ 2021-07-06 13:40  charlee44  阅读(924)  评论(0编辑  收藏  举报