五、纯代码动态创建网格
一般情况下,网格是事先制作好的资源,但也有一些特殊的需求需要在代码中动态创建网格。
现在我来教大家如何使用代码从零创建网格并将网格渲染出来,下文我以创建一个正方形网格为例进行讲解。
1、创建Mesh对象
第一步最简单,就是直接new
一个Mesh
,
var mesh = new Mesh();
2、顶点坐标
首先分析一下,一个四边形有四个顶点,假设正方形边长为1
,四个点的坐标如下,
写成代码就是这样:
// 构建顶点坐标
var vertices = new List<Vector3>();
vertices.Add(new Vector3(-0.5f, -0.5f, 0));
vertices.Add(new Vector3(-0.5f, 0.5f, 0));
vertices.Add(new Vector3(0.5f, 0.5f, 0));
vertices.Add(new Vector3(0.5f, -0.5f, 0));
// 将顶点坐标设置给Mesh
mesh.SetVertices(vertices);
3、UV坐标
UV
坐标就是纹理贴图坐标,它将纹理上每一个点精确对应到模型物体的表面上,注意U
和V
的取值范围是0~1
。
UV
坐标系原点在左下角,U
轴是水平轴,V
轴是竖直轴,如下:
对应到我们的上面那个正方向网格的话,四个点的UV
坐标如下:
写成代码就是这样:
// 构建UV坐标
var uvs = new List<Vector2>();
uvs.Add(new Vector2(0, 0));
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
uvs.Add(new Vector2(1, 0));
// 将UV坐标设置给Mesh
mesh.SetUVs(0, uvs);
4、三角形序列
网格需要切分成三角形,我们可以这样切分,
当然也可以这样切分,
两种切分方法对应不同的三角形序列,假设 法线方向 是垂直于屏幕从内指向屏幕外的话,第一种切分方式的三角形序列如下:
注:法线的方向就决定了表面正面,如果你的材质是单面渲染的话,那么只有从正面看才能看到网格被渲染。
即三角形序列为:{ 0, 1, 2, 0, 2, 3 }
,注意序号是从0
开始的。
为什么是这样的顺序呢?我教大家一个技巧,伸出你的左手,竖起大拇指,像这样子,
大拇指指向法线的方向,那么此时你的其余四根手指头环绕的方向就是三角形的序号的顺序,三个序号为一组按顺序塞入数组中即可,即得到的数组就是:{ 0, 1, 2, 0, 2, 3 }
,当然,以下数组最终的效果都是等价的,只要顺序一致即可:
{ 0, 1, 2, 0, 2, 3 }
,
{ 1, 2, 0, 0, 2, 3 }
,
{ 0, 2, 3, 1, 2, 0 }
,
…
我们现在写成代码,
// 设置三角形序列
var triangles = new int[] { 0, 1, 2, 0, 2, 3 };
mesh.SetTriangles(triangles, 0);
5、重新计算法线和包围体
当我们设置或修改了顶点数据后,需要调用Mesh
的Recalculate
方法来重新计算一些必要的信息,比如重新计算法线、包围体,代码如下
// 重新计算法线,注意,法线是根据共享的顶点计算出来的。
mesh.RecalculateNormals();
// 重新计算包围体,在修改顶点后需要这个函数以确保包围体是正确的
mesh.RecalculateBounds();
// 从法线和纹理坐标重新计算网格的切线(如果网格使用引用法线贴图的着色器进行渲染,则切线需要更新)
// 因为我们这里不使用法线贴图,所以就不调用它了
// mesh.RecalculateTangents();
6、完整版代码
以上代码封装成GenQuadMesh.cs
脚本,完整代码如下:
// 使用代码生成四边形网格
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class GenQuadMesh : MonoBehaviour
{
public MeshFilter mf;
private void Start()
{
mf.mesh = Build();
}
public static Mesh Build()
{
var mesh = new Mesh();
// 构建顶点坐标
var vertices = new List<Vector3>();
vertices.Add(new Vector3(-0.5f, -0.5f, 0));
vertices.Add(new Vector3(-0.5f, 0.5f, 0));
vertices.Add(new Vector3(0.5f, 0.5f, 0));
vertices.Add(new Vector3(0.5f, -0.5f, 0));
// 将顶点坐标设置给Mesh
mesh.SetVertices(vertices);
// 构建UV坐标
var uvs = new List<Vector2>();
uvs.Add(new Vector2(0, 0));
uvs.Add(new Vector2(0, 1));
uvs.Add(new Vector2(1, 1));
uvs.Add(new Vector2(1, 0));
// 将UV坐标设置给Mesh
mesh.SetUVs(0, uvs);
// 设置三角形序列
var triangles = new int[] { 0, 1, 2, 0, 2, 3 };
mesh.SetTriangles(triangles, 0);
mesh.RecalculateNormals();
mesh.RecalculateBounds();
return mesh;
}
}
7、测试
创建一个空物体,挂上MeshFilter
和MeshRenderer
组件。
再挂上我们上面写的GenQuadMesh
脚本,赋值mf
变量为MeshFilter
对象,如下
运行Unity
,看到一个紫色快,
将Scene
视图的模式设置为Wireframe
,如下
现在我们可以看到我们动态创建的网格啦,
上面之所以显示紫色块,是因为我们没有给MeshFilter
设置材质球,顺手做一个炮姐的材质球吧,
给MeshRenderer
设置材质球对象,
重新运行Unity
,效果如下,
8、项目源码
要用代码动态创建一个Mesh
,就是new
一个Mesh
,给它塞入顶点坐标、UV
坐标和三角形序列即可。再复杂的网格也可以通过这些步骤创建出来~
下面这些就是使用纯代码创建出来的几何体网格,感兴趣的同学可以下载项目源码下来学习。
项目源码:https://codechina.csdn.net/linxinfa/unity-mesh-builder
六、网格相关的开源项目
我再推荐一些网格相关的开源项目给大家~
1、2D网格涂鸦
项目地址:https://github.com/mattatz/unity-triangulation2D
2、3D网格涂鸦
项目地址:https://github.com/mattatz/unity-teddy
3、网格体素化
项目地址:https://github.com/Scrawk/Mesh-Voxelization
4、网格平滑算法
项目地址:https://github.com/mattatz/unity-mesh-smoothing
5、网格切割
项目地址:https://github.com/hugoscurti/mesh-cutter
6、网格合并
项目地址:https://github.com/sanukin39/UniMeshCombiner
七、未完的探险
好了,这次探险之旅就暂时到这里吧,还有很多内容需要探索,先保持体力,我们下次再见。