UnityGPU渲染------------01网格材质

发布时间 2023-03-23 14:45:29作者: 打工仔-也想飞

在Unity这个大家庭里,也有一群经常容易被大家忽视却至关重要的小伙伴MeshFilter、MeshRenderer、Material

 是不是有些眼熟,但好像没怎么跟他们打过交道,那它们到底是做什么的呢?其实它们的工作和制造美食的厨师有异曲同工之处,正所谓民以食为天,要想使物体能够正确的显示在场景画面中,他们都是不可或缺的一员。
 
 
基础概念 
MeshFilter 网格过滤器
 
 
主要从众多的资源中挑选需要的Mesh,把它丢给MeshRender,这更像是大厨做美食前的准备工作,我们告诉大厨想吃的那道美食,这时大厨就会从仓库中挑选出所需的食材,为接下来的步骤做好准备工作
 
 
MeshRenderer 网格渲染器 
 
 
主要是负责把MeshFilter丢过来的Mesh,绘制显示到我们的场景中,当然这项工作是非常复杂的,就好比大厨需要经过许多步骤,才可以将准备好的食材做成香喷喷的美食端到我们面前。
 
 
Material 材质球 
 
 
MaterialMeshRenderer中非常重要的角色,它的配置决定了物体表面的外观将以怎样的质地呈现到我们眼前。如果有天MeshRenderer不小心弄丢了Material,那这个物体就会变成让人烦躁的品红色。其实它跟大家经常看到的网页Error404差不多,RGB调成101就是这个颜色啦。
 
 
 
 
 
如果说MaterialMeshRenderer的灵魂,那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;

最后把这个脚本挂载到我们的小兔子身上,运行游戏,就可以发现之前的小兔子不见了,变成了我们刚刚构建出来的立方体模型~