Unity中网格是如何绘制的(一)

Keep And Accept Change

Posted by 打个大西瓜 on August 30, 2022 21:20:22

基本概念

Mesh

网格 由N个三角形面组成

三角形 组成网格的基本单元,三角形是由三个顶点按特定顺序(顺时针、逆时针)组成

顶点 是组成三角形的基本单元,众所周知,最少三个点才能确定一个面

顶点索引 决定组成三角形的顶点和顺序,在Unity中顶点按顺时针连接为正面,逆时针为反面,遵循 左手法则

法线 垂直与三角面的向量,方向由顶点索引的顺序根据左手法则确定

UV坐标 贴图左下角为(0,0)右上角为(1,1),UV坐标和顶点坐标一一对应,确定三角面由贴图的哪部分渲染

切线 垂直于法线的向量,但是这样的向量有无数条,最终由UV中的U(水平方向)增长方向确定

还有其他数据,不一一列举

Mesh 就是这些信息的集合

总结一下:顶点数据提供了创建Mesh所需要的所有顶点坐标,顶点索引提供了哪三个顶点以顺时针还是逆时针来组成三角形,UV坐标提供了顶点在贴图的哪个位置开始取样

Vertices 顶点数组 Vector3[]

它存储的是顶点的相关信息,所谓点成线,线成面,可以理解为这里面存储的是构成网格面全部的点

Topology 拓扑类型

它存储的就是一个类型信息,可以理解为它是图形表面排列结构的组成方式,Unity给我们提供了5种拓扑类型,三角面、四边形、线条、虚线、点阵,最常用的则是三角面

Indices 索引数组 int[]

它是每个三角面顶点的索引,可以理解为他存储了构成网格三角面所用到的顶点数组的索引。

Normal 法线 Vector3[]

法线就是垂直于该顶点三角面的一条三维向量,它只有方向,没有大小。法线的方向就是顶点三角面朝外的方向。假设我们面前有一面镜子,它的正中心会有一条法线垂直于镜面指向我们,指向我们的面就是正面,相反就是背面,(由索引顺序根据左手法则确定)

Tangent 切线 Vector3[]

它是垂直于法线的一条向量,而由于垂直于法线的向量有无数条,所以切线最终是由UV坐标来决定朝向的

UV纹理坐标 Vector2[]

上面所说的UV坐标其实就是它,U增长的方向就是切线的方向,它和三维空间的X, Y, Z较为类似,它是一个二维的坐标系统,模型网格除了有三维空间的xyz坐标外,还有一个二维的UV坐标,在UV坐标中,U和V分别代表顶点在Texture水平和垂直方向上的采样坐标,这些坐标通常位于(0,0)和(1,1)之间,(0,0)代表最左下角,而(1,1)代表最右上角

这就跟平时装修房子贴墙纸一样,可以理解为它是Texture映射到模型表面的依据,模型顶点会依据UV坐标对Texture进行采样。比如,定点的UV坐标为(0,0),那么这个顶点就会从贴图的左下角开始取样

那可不可以大于1或者小于0呢,可以,但是一般没必要 大于1或者小于0时,贴图会重复平铺,比如uv=(2,2)时,就会重复贴上四张图片

如图: uv超出范围

示例

先画一个最简单的三角形面

using UnityEngine;

[ExecuteAlways]
public class MeshGenerate : MonoBehaviour
{
    //顶点数据
    [SerializeField]
    private Vector3[] vertices;
    //三角形索引
    [SerializeField]
    private int[] triangles;
    //uv坐标数据
    [SerializeField]
    private Vector2[] uvs;

    void Start()
    {
        //画三角形需要三个顶点,定义三个顶点坐标,这里的坐标是相对于物体的坐标,也就是LocalPosirion
        vertices = new Vector3[]{
            // 顶点1
            new Vector3(-2.0f, 5.0f, -2.0f),//[0]
            // 顶点2
            new Vector3(-2.0f, 0.0f, -2.0f),//[1]
            // 顶点3
            new Vector3(2.0f, 0.0f, -2.0f),//[2]
        };

        //定义顶点顺序,因为要绘制正面,所以按顺时针排序,记得是遵循左手坐法则,不理解左手法则的一定要理解
        triangles =new int[]{
             2,1,0,
        };

        uvs = new Vector2[]{
            // 顶点1的uv,对应上面的vertices[0]
            new Vector2(0.5f, 1.0f),
            // 顶点2的uv,对应上面的vertices[1]
            new Vector2(0.0f, 0.0f),
            // 顶点3的uv,对应上面的vertices[2]
            new Vector2(1.0f, 0.0f),
        };

        Generate();
    }

    private void OnValidate()
    {
        Generate();
    }
    void Generate()
    {
        // 新建一个Mesh
        Mesh mesh = new Mesh();
        // 用构建的数据初始化Mesh
        mesh.vertices = vertices;
        mesh.triangles = triangles;
        mesh.uv = uvs;
        // 法线是根据顶点数据计算出来的,所以在修改完顶点后,需要更新一下法线
        mesh.RecalculateNormals();
        mesh.RecalculateTangents();
        // 将构建好的Mesh替换上,节点上需要挂载MeshFilter组件和MeshRenderer组件
        gameObject.GetComponent<MeshFilter>().mesh = mesh;
    }

    private void OnDrawGizmos()
    {
        //这里是把法线和切线在Scene窗口上绘制出来
        var mesh = gameObject.GetComponent<MeshFilter>().mesh;
        if(mesh == null)
        {
            return;
        }
        var normals = mesh.normals;
        var vers = mesh.vertices;
        var tan = mesh.tangents;
        for (int i = 0; i < vers.Length; i++)
        {
            Gizmos.color = Color.blue;
            Gizmos.DrawLine(vers[i], vers[i] + normals[i]);
            Gizmos.color = Color.green;
            Gizmos.DrawLine(vers[i], vers[i] + new Vector3(tan[i].x, tan[i].y, tan[i].z));
        }
    }
}

顶点和顶点索引如下图所示

顶点

上面的示例绘制的是三角形,只有一个面,只需要三个顶点数据就够了,如果绘制一个矩形,则是两个面,需要几个顶点?如果是一个立方体呢,需要几个顶点