在游戏开发过程中,模型一般由美术提供,程序一般只负责使用,以及优化。一般的优化手段就是mesh的合批,高低摸的转换之类,以及捏脸系统等等。但是大家要知道一点,Unity不仅可以处理模型,还可以创建模型,只是在正常的商业游戏开发中并不需要程序去用代码制作模型。但是知道Unity中模型是怎么构建的,非常有利于我们对开发的深入理解。
1.Unity中模型是怎么显示的
(1)简单来说,模型都是用三角面构成。在理解模型的时候,可以直接将模型认作为Mesh网格。所以Mesh网格就是由三角面片组成,如下:
这是一个我绘制的一个最简单的正方形面片,他由两个三角形组成,三角形124,和三角形234,注意一下这里的顺序,我们在设置点的时候,必须按照顺时针,因为通常情况下我们只绘制一面,另外一面不需要绘制,当然你也可以,只是会消耗更多的性能。
(2)看到上面,其实Mesh的原理就已经很清楚了,通过顶点,连接线组成三角形,然后由三角形构成面,最后得到各种各种的模型。当然,上面的例子只是为了让同学们知道Mesh组成原理是什么,因为哪怕是最简单的内建Plane面,也有着一百多个顶点。我们来建一个Plane,看看它的构成
我们可以看到,Unity内建的Plane大小为10x10,有着121个顶点。更丰富的顶点意味着我们可以做丰富的动画,扭曲,折叠等等操作。但是通常也意味着更多的计算量消耗。下面我们就来手动创建一个可以任意设置长宽的Plane面吧
准备工作
(1)Mesh,MeshFilter,MeshRender
Mesh:模型的顶点坐标、法线、uv坐标、三角面信息均包含在Mesh中
MeshFilter:掌管一个Mesh对象,并从中取得数据处理,最后交给MeshRender绘制
MeshRender:得到处理后的Mesh信息,并且绘制在空间中。
(2)我们的工作
创建一组顶点,按照规则写出三角形序列,然后将接受这个顶点和三角形序列的Mesh交给MeshFIlter即可。
代码部分
我会写的比较详细,因此可能会有一部分重复代码
(1)初始类
//如果添加的对象上没有MeshFilter,则给对象添加一个MeshFilter
[RequireComponent(typeof(MeshFilter))]
//如果添加的对象上没有MeshRenderer,则给对象添加一个MeshRenderer
[RequireComponent(typeof(MeshRenderer))]
public class DrawPlane{
//实际操作的MeshFilter
private MeshFilter filter;
//实际的Mesh
private Mesh mesh;
//记录顶点信息
private List<Vector3> vts = new List<Vector3>();
//自身的uv坐标
private int[] uvs;
//三角形数组
private int[] tris;
//x轴单位
public int xSize;
//y轴单位
public int ySize;
//x轴每单位大小,用于计算坐标
public float xSeg;
//y轴每单位大小,用于计算坐标
public float ySeg;
//x轴顶点个数 ==== xSize+1
private int xCount;
//y轴顶点个数 ==== ySize+1
private int yCount;
//初始化
private void Start(){
filter = GetComponent<MeshFilter>();
xCount = xSize + 1;
yCount = ySize + 1;
//得到每个顶点的坐标
vts = CalVts();
//设置Mesh
SetMesh();
}
//计算顶点信息,多少个顶点,每个顶点的坐标等
private List<Vector3> CalVts(){
//我们以挂载对象的位置作为起始点
Vector3 pos = =tranform.position;
List<Vector3> vals = new List<Vector3>();
for(int i = 0 ; i < xCount ; i++){
for(int j = 0 ; j < yCount ; j++){
Vector3 vector =pos + new Vector3(i*xSeg,j*ySeg,0);
vals.Add(vector);
}
}
return vals;
}
//对Mesh进行设置
private void SetMesh(){
mesh = new Mesh();
//因为每个有效点我们需要绘制两个三角形,共计六个顶点,所以对tris数组初始大小为
tris = new int[xSize*ySize*6];
//记录每个有效点的三角形起始下标,每个有效点需要画两个三角形,六个顶点,因此每次迭代triCount +=6
int triCount = 0 ;
for(int i = 0 ; i < ySize ; i++){
for(int j = 0 ; j < xSize ; j++){
//每个有效点绘制两个三角形
//每个顶点,在vts中的下标索引
int startIndex = i * xCount + j;
//第一个三角形,三个顶点
tris[triCount] = startIndex ;
//第一个三角形第二个点为初始点的正上方相邻点
tris[triCount+1] = startIndex + xCount ;
//第一个三角形第三个点为右边相邻的点
tris[triCount+2] = startIndex + 1;
//绘制第二个三角形,
//第二个三角形第一个点为初始点的正上方相邻的点
tris[triCount+3] = startIndex + xCount ;
//第二个三角形第二个点为初始点右上方的点
tris[triCount+4] = startIndex + xCount + 1;
//第二个三角形第三个点为初始点的右边相邻的点
tris[triCount+5] = startIndex + 1;
triCount += 6;
}
}
//将顶点传给mesh
mesh.vertices = vts.ToArray();
//将三角形的绘制序列传给mesh
mesh.triangles = tris;
//至此我们的三角形其实已经绘制成功了,但是面并没有uv坐标,如果了解uv的同学值得,纹理就是通过顶点之间uv坐标的插值来计算颜色值的。
//因此我们还需要计算一下uv坐标
//有多少个顶点,就有多少个uv坐标
//uv坐标的范围在0-1之间,其实就相当于给顶点坐标做一个归一化处理
uvs = new int[vts.Count];
float xOffset = 1.0f / xCount;
float yOffset = 1.0f / yCount;
for(int i = 0 ; i < yCount ; i++){
for(int j = 0 ; j < xCount; j++){
//设置每个顶点的uv坐标
//这里我们也可以做一些小巧思,比如y轴翻转,或者x轴翻转,大家能想到怎么做吗
uvs[i * xCount+ j] = new Vector2(j * xOffset , i * yOffset );
}
}
//将uv设置给mesh
mesh.uv = uvs;
//这三个方法是相当于对mesh用新数据进行重置
mesh.RecalculateBounds();
mesh.RecalculateNormals();
mesh.RecalculateTangents();
//将mesh交给filter
filter.mesh = mesh;
}
}
(2)将脚本挂载在一个空物体上,设置xSize,ySize,xSeg,ySeg即可,比如我们现在得到一个5x7,x,y轴单位长度为1的plane面
当然我们也可以将这个Plane导出作为一个模型,或者对这个Plane添加一些顶点动画,还是开头说的,学会用程序制作模型,对实际开发的意义并不大。但是能帮助我们更好的理解模型构建的流程,对模型的优化,使用有很大的帮助。