在Unity这个大家庭里,也有一群经常容易被大家忽视却至关重要的小伙伴MeshFilter、MeshRenderer、Material
是不是有些眼熟,但好像没怎么跟他们打过交道,那它们到底是做什么的呢?其实它们的工作和制造美食的厨师有异曲同工之处,正所谓民以食为天,要想使物体能够正确的显示在场景画面中,他们都是不可或缺的一员。
基础概念
MeshFilter 网格过滤器
主要从众多的资源中挑选需要的Mesh,把它丢给MeshRender,这更像是大厨做美食前的准备工作,我们告诉大厨想吃的那道美食,这时大厨就会从仓库中挑选出所需的食材,为接下来的步骤做好准备工作
MeshRenderer 网格渲染器
主要是负责把MeshFilter丢过来的Mesh,绘制显示到我们的场景中,当然这项工作是非常复杂的,就好比大厨需要经过许多步骤,才可以将准备好的食材做成香喷喷的美食端到我们面前。
Material 材质球
Material是MeshRenderer中非常重要的角色,它的配置决定了物体表面的外观将以怎样的质地呈现到我们眼前。如果有天MeshRenderer不小心弄丢了Material,那这个物体就会变成让人烦躁的品红色。其实它跟大家经常看到的网页Error404差不多,RGB调成101就是这个颜色啦。
如果说Material是MeshRenderer的灵魂,那Shader就是Material的灵魂,但凡Shader哪里不开心了,即使MeshRenderer没有弄丢Material,物体依旧会变成前面所说的101颜色。不过说到灵魂,美食的灵魂可谓是色香味了吧,菜好不好吃全看它们了。那无论做什么美食,食材是最重要的基础~大致了解这几位小伙伴之后,我们再来了解最基础的Mesh(❌)食材(✔)。
Mesh 网格
Mesh指的就是模型的网格,它决定了物体的表面形状是怎样的,一个模型的表面大多是由多个彼此相连的三角面构成,当然也有其它类型。我们平时听到的建模,可以简单理解为就是在建网格,那为什么Unity中的网格大多都是三角形而不是四边形呢?正所谓一生二,二生三,三生万物。三角形可以说是最为基础的面了,可以简单理解为三角形具有更广泛的适用性,而Mesh则是构成这些三角面所需的信息集合。
通过 Mesh data - Unity 手册 我们可以看到构成这些三角面所需的信息。
1. Vertices 顶点数组 Vector3[]
顾名思义它存储的是顶点的相关信息,所谓点成线,线成面,可以理解为这里面存储的是构成网格面全部的点
2. Topology 拓扑类型
它存储的就是一个类型信息,可以理解为它是图形表面排列结构的组成方式,Unity给我们提供了5种拓扑类型,三角面、四边形、线条、虚线、点阵,最常用的则是三角面。
3. Indices 索引数组 int[]
它是每个三角面顶点 的索引,可以理解为他存储了构网格三角面所用到的顶点索引。
4. Vertex data 顶点数据
它包含了顶点的位置、法线、切线、UV等属性
1.Normal 法线 Vector3[]
法线就是垂直于该顶点三角面的一条三维向量,它只有方向,没有大小。法线的方向就是顶点三角面朝外的方向。假设我们面前有一面镜子,它的正中心会有一条法线垂直于镜面指向我们,指向我们的面就是正面,相反就是背面
2.Tangent 切线 Vector3[]
它是垂直于法线的一条向量,而由于垂直于法线的向量有无数条,所以切线最终是由UV坐标来决定朝向的
3.UV 纹理坐标 Vector2[]
上面所说的UV坐标其实就是它,U增长的方向就是切线的方向,它和三维空间的X, Y, Z较为类似,它是一个二维的坐标系统,模型网格除了有三维空间的xyz坐标外,还有一个二维的UV坐标,在UV坐标中,U和V分别代表顶点在Texture水平和垂直方向上的采样坐标,这些坐标通常位于(0,0)和(1,1)之间,(0,0)代表最左下角,而(1,1)代表最右上角。这就跟平时装修房子贴墙纸一样,可以理解为它是Texture映射到模型表面的依据,模型顶点 会依据UV坐标对Texture进行采样。
4.Index data 索引数据
这个数据取决于拓扑类型,如果是三角面他储存的就是[0,1,2],四边形储存的就是[0,1,2,3],这个索引数值对应的就是顶点数组的下标。
实战案例
对Mesh有了基本的了解后,我们可以动手尝试一下,构建一个简单的立方体Mesh,替换掉替换掉原来的小兔子,来个大变活兔。
定义立方体顶点数组
正常来说立方体共有6个面,每个面由2个三角面组成,三角面有3个顶点数据,所以正常来说每个面需要有6个顶点数据,一共需要36个三角面顶点索引。那为什么这里我们每个面只用到了4个顶点数据呢,这是因为每个面的2个三角面顶点数据中有两个顶点它们的数据是共同的,Unity会直接通过索引找到相对应的数据。
看到这里,可能又会有人觉得奇怪,既然共用的顶点数据可以通过索引找到,为什么一个立方面只有8个顶点,为什么这里不直接用8个顶点数据,而是需要24个?
这是因为Unity中不仅依靠这个三角面的索引数组索引三角面的顶点坐标,而且索引纹理坐标,索引法线向量。而立方体的每个顶点都参与了3个平面,而这个顶点相对于这3个平面来说,虽然顶点数据相同,但它们的法线向量是不同的,这个顶点在渲染这3个平面的时候则需要索引到不同的法线向量。而在Unity中由于顶点坐标和法线向量是由同一个索引值取得的,所以这里立方体一共8个顶点,每个顶点我们要存3份,刚好是24个顶点数据:
// 顶点数组 模型空间顶点坐标
Vector3[] vertices = { // Front point1,2,3,4 new Vector3(-2.0f, 5.0f, -2.0f), new Vector3(-2.0f, 0.0f, -2.0f), new Vector3(2.0f, 0.0f, -2.0f), new Vector3(2.0f, 5.0f, -2.0f), //Left new Vector3(-2.0f, 5.0f, -2.0f), new Vector3(-2.0f, 0.0f, -2.0f), new Vector3(-2.0f, 0.0f, 2.0f), new Vector3(-2.0f, 5.0f, 2.0f), //Back new Vector3(-2.0f, 5.0f, 2.0f), new Vector3(-2.0f, 0.0f, 2.0f), new Vector3(2.0f, 0.0f, 2.0f), new Vector3(2.0f, 5.0f, 2.0f), //Right new Vector3(2.0f, 5.0f, 2.0f), new Vector3(2.0f, 0.0f, 2.0f), new Vector3(2.0f, 0.0f, -2.0f), new Vector3(2.0f, 5.0f, -2.0f), //Top new Vector3(-2.0f, 5.0f, 2.0f), new Vector3(2.0f, 5.0f, 2.0f), new Vector3(2.0f, 5.0f, -2.0f), new Vector3(-2.0f, 5.0f, -2.0f), //Bottom new Vector3(-2.0f, 0.0f, 2.0f), new Vector3(2.0f, 0.0f, 2.0f), new Vector3(2.0f, 0.0f, -2.0f), new Vector3(-2.0f, 0.0f, -2.0f), };
定义三角面索引数组
要注意之前提到的绕序,这里按顺时针构建的原则,将外面的渲染,里面的剔除。此外要注意和上面构建的顶点数据顺序要一致。
// 索引数组 int[] triangles = { // Front 2,1,0, 0,3,2, // Left 4,5,6, 4,6,7, // Back 9,11,8, 9,10,11, // Right 12,13,14, 12,14,15, // Top 16,17,18, 16,18,19, // Buttom 21,23,22, 21,20,23, };
定义UV坐标数组
同样这里的UV坐标的顺序也要与顶点数据的顺序相对应
// UV数组 Vector2[] uvs = { // point[1] new Vector2(0.0f, 1.0f), // point[2] new Vector2(0.0f, 0.0f), // point[3] new Vector2(1.0f, 0.0f), // point[4] new Vector2(1.0f, 1.0f), // Left new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), new Vector2(0.0f, 1.0f), // Back new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), new Vector2(0.0f, 1.0f), // Right new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), new Vector2(0.0f, 1.0f), // Top new Vector2(0.0f, 1.0f), new Vector2(1.0f, 1.0f), new Vector2(1.0f, 0.0f), new Vector2(0.0f, 0.0f), // Bottom new Vector2(0.0f, 0.0f), new Vector2(1.0f, 0.0f), new Vector2(1.0f, 1.0f), new Vector2(0.0f, 1.0f), };
构建Mesh
// 新建一个Mesh Mesh mesh = new Mesh(); // 用构建的数据初始Mesh mesh.vertices = vertices; mesh.triangles = triangles; mesh.uv = uvs; // 法线是根据顶点数据计算出来的,所以在修改完顶点后,需要更新一下法线 mesh.RecalculateNormals(); // 将构建好的Mesh替换上 gameObject.GetComponent<MeshFilter>().mesh = mesh;
最后把这个脚本挂载到我们的小兔子身上,运行游戏,就可以发现之前的小兔子不见了,变成了我们刚刚构建出来的立方体模型~