备注:希望和大家一起交流学习,如果有不同的观点欢迎一起讨论学习,不喜勿喷,谢谢。
图形渲染
Mesh
基本属性
前面文章中介绍了 Mesh 一些基本信息,本篇文章我们详细学习下 mesh 编程,已知 Mesh 中主要包含以下内容:
- 顶点数组 vertices :
- 主要存放一个模型中所有的顶点坐标。
- 数组类型:Vector3[]
- 三角形数组 triangles:
- 采用3个元素表示一个三角形的数组方式,所以数组的长度一定是3的倍数,里面主要存放和顶点数组对应的顶点下标。
- 数组类型:int[]
- 顶点的法线坐标信息 normals:
- 主要存放顶点数组中每个顶点的法线坐标(和顶点数组中的顶点必须一一对应)
- 数组类型:Vector3[]
- 顶点的UV纹理坐标 uv:
- 主要存放顶点数组中每个顶点在屏幕坐标中对应的uv坐标
- 数组类型:Vector2[]
- 颜色信息:
- 主要保存顶点数组中每个顶点对应的颜色信息(和顶点数组中的顶点必须一一对应)
- 数组类型:Color[]
- 切线信息 tangents:
- 主要保存每个顶点对应的切线坐标(和顶点数组中的顶点必须一一对应)
- 数组类型:Vector3[]
(还有很多其他的函数和属性,这里不过多介绍,如果有感兴趣的可以去阅读源码)
三角形顺序
渲染是只渲染三角形一面,如果我们的三角形数组中的三角形序列是顺时针,那么渲染的就是正面,如果三角形数组中的三角形序列是逆时针,那么渲染的就是反面。
那么如何区别三角形序列是顺时针还是逆时针呢?
因为 unity 使用左手坐标系,所以这里我们采用 “左手螺旋定则” 来区分方向:
首先,大拇指指向我们眼睛观察的方向,然后四指弯曲,四指指向的方向就是三角形序列的顺时针,否则就是逆时针,如图分析:
实战操作
渲染一个三角形面片
1.绘制目标
我们绘制两个三角形面片,一个表示正面渲染的三角形面片,一个表示背面渲染的三角形面片:
2.代码
//创建一个三角形
//isReverse 为true 表示背面,为false表示正面
public bool isReverse = false;
private void _MeshTriangle()
{
MeshFilter fliter = gameObject.AddComponent<MeshFilter>();
MeshRenderer render = gameObject.AddComponent<MeshRenderer>();
Mesh mesh = new Mesh();
mesh.name = "OneTriangleMesh";
//创建三角形顶点
Vector3[] ver = { Vector3.zero, Vector3.forward, new Vector3(1, 0, 0) };
//而且一定要先设置顶点,在设置三角形数组,否则会报错
mesh.vertices = ver;
//创建三角形数组,这里分析,一个三角形3个顶点,只需要设置一个三角形的顺时针下标
if (isReverse)
{
//背面
int[] triangles = { 0, 2, 1 };
mesh.triangles = triangles;
}
else
{
//正面
int[] triangles = { 0, 1, 2 };
mesh.triangles = triangles;
}
fliter.mesh = mesh;
}
3.渲染结果
正面渲染的三角形面片效果如下,此时我们是看不到右边背面渲染的三角形面片的:
背面渲染的三角形面片效果,此时我们也看不到左边正面渲染的三角形面片的:
渲染一个正方形面片
1.绘制目标
我们绘制两个三角形面片组成的正方形面片,
2.代码
public bool _isReverse = false;
//创建一个正方形
private void _MeshSquare()
{
MeshFilter filter = gameObject.AddComponent<MeshFilter>();
MeshRenderer render = gameObject.AddComponent<MeshRenderer>();
Mesh mesh = new Mesh();
mesh.name = "SquareMesh";
//正方形由两个三角形组成
Vector3[] ver = { Vector3.zero, Vector3.forward, new Vector3(1, 0, 1), Vector3.right};
mesh.vertices = ver;
//两个三角形,4个顶点,需要设置两个三角形的顺时针下标,所以三角形数组大小是6
if (_isReverse)
{
//反向
int[] triangle = { 0, 3, 1, 3, 2, 1 };
mesh.triangles = triangle;
}
else
{
//正向
int[] triangle = { 0, 1, 3, 1, 2, 3 };
mesh.triangles = triangle;
}
filter.mesh = mesh;
}
3.绘制结果
正面渲染的正方形结果,还是依然看不到右边背面渲染的正方形,
为了更清楚的确认图中的正方形是不是我们预期的那个样子,我们点开scene面板中左上角下拉框,选择 “wireframe” (线框),可以直接显示图中的网格线框信息,更利于我们观察。
这种情况下,我们可以清楚的看到这就是我们代码中绘制的正方形中的两个三角形,和我们预计的效果一致。
背面渲染的效果,同样看不到左边正面渲染的正方形面片:
只展示线框的情况下,我们可以看到这就是我们代码中绘制的正方形背面渲染的两个三角形:
渲染一个含有UV信息的正方形面片
1.绘制目标
还是按照 “渲染一个正方形面片” 的方式,但是这一次我们加上 “材质”,任意创建两个对象(一个显示正面,一个显示反面),我们给它们分别添加上 “Mesh Renderer” 组件,然后添加一个材质:
2.代码
因为我们手动添加了 “MeshRenderer” 组件,所以在代码中不需要再添加这个组件了,直接获取即可,同时还要设置它的UV数组:
public bool _isReverse = false;
//创建一个正方形
private void _MeshSquare()
{
MeshFilter filter = gameObject.AddComponent<MeshFilter>();
MeshRenderer render = gameObject.GetComponent<MeshRenderer>();
Mesh mesh = new Mesh();
mesh.name = "SquareMesh";
//正方形由两个三角形组成
Vector3[] ver = { Vector3.zero, Vector3.forward, new Vector3(1, 0, 1), Vector3.right };
mesh.vertices = ver;
//两个三角形,4个顶点,需要设置两个三角形的顺时针下标,所以三角形数组大小是6
if (_isReverse)
{
int[] triangle = { 0, 3, 1, 3, 2, 1 };
mesh.triangles = triangle;
}
else
{
int[] triangle = { 0, 1, 3, 1, 2, 3 };
mesh.triangles = triangle;
}
//设置uv坐标,默认把每个面的y轴去掉即可
//如果给了材质,但是没有设置 uv 信息,那么也无法显示出材质上面的贴图信息,只会是一个全黑状态
Vector2[] uv = { Vector2.zero, new Vector2(0, 1), new Vector2(1, 1), new Vector2(1, 0)};
mesh.uv = uv;
filter.mesh = mesh;
}
3.绘制结果
正面效果:
反面效果:
这里需要注意,这里的UV坐标不是真的像素坐标,它只是贴图的一个比率,为了清楚的表示,我们换一张材质贴图,并且修改代码,这一次我们不再从y轴的正方向向下观看(俯视),我们从z轴的负方向观看(其实效果一样,就是为了让大家多多尝试不同的观察角度书写代码),以下是我们需要绘制的贴图,
代码如下:
private void _MeshSquare2()
{
MeshFilter filter = gameObject.AddComponent<MeshFilter>();
Mesh mesh = new Mesh();
mesh.name = "_MeshSquare2";
//我们修改了顶点坐标,变成了长方形
Vector3[] ver = { Vector3.zero, Vector3.up, new Vector3(2, 1, 0), new Vector3(2, 0, 0)};
mesh.vertices = ver;
if (_isReverse)
{
//反向
int[] triange = { 0, 3, 1, 3, 2, 1};
mesh.triangles = triange;
}
else
{
//正向
int[] triange = { 0, 1, 3, 1, 2, 3};
mesh.triangles = triange;
}
//这里的uv坐标是一个比率,不是真的像素坐标
//注意这里的uv坐标
Vector2[] uv = { Vector2.zero, Vector2.up, new Vector2(2, 0.5f), new Vector2(2, 0)};
mesh.uv = uv;
filter.mesh = mesh;
}
效果图:
修改uv坐标后再观察效果:
Vector2[] uv = { Vector2.zero, Vector2.up, new Vector2(1, 1), new Vector2(1, 0) };
但是我们设置了uv信息后会发现,我们的图很黑,看不清楚,这是为什么?因为我们没有设置顶点法线。
绘制一个含有法线信息的正方形面片
1.绘制目标
就绘制上一个正方形的那个正向面片即可。
2.代码
//创建一个正方形
private void _MeshSquare()
{
MeshFilter filter = gameObject.AddComponent<MeshFilter>();
Mesh mesh = new Mesh();
mesh.name = "SquareMesh";
//正方形由两个三角形组成
Vector3[] ver = { Vector3.zero, Vector3.forward, new Vector3(1, 0, 1), Vector3.right };
mesh.vertices = ver;
int[] triangle = { 0, 1, 3, 1, 2, 3 };
mesh.triangles = triangle;
Vector2[] uv = { Vector2.zero, Vector2.up, new Vector2(1, 1), Vector2.right};
mesh.uv = uv;
//这是法线信息后图会更亮一些,有光照的效果
Vector3[] normal = { Vector3.up, Vector3.up, Vector3.up, Vector3.up };
mesh.normals = normal;
filter.mesh = mesh;
}
3.绘制结果
这是设置了法线信息的正方形:
这是没有设置法线信息的正方形:
很明显,设置了法线信息的正方形显得更加亮效果更好,所以法线信息和关照有关,具体内容我们后面再继续分析。
拓展训练
前面都是绘制2D图,这里我们绘制一个3D cube 作为拓展训练,我们将要绘制的 Cube 如图所示:
代码:
private void _MeshCube()
{
MeshFilter filter = gameObject.AddComponent<MeshFilter>();
Mesh mesh = new Mesh();
filter.mesh = mesh;
mesh.name = "mesh_cube";
//创建立方体的顶点信息,立方体有八个顶点,6个面,每个正方形面有2三角形面片
//我们这里只设置4个面的正方体,左面和下面没有设置
Vector3[] cubeVer = {
Vector3.zero, Vector3.up, new Vector3(1, 1, 0), Vector3.right, //正面ABCD
Vector3.up, new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 1, 0), //上面BFGC
Vector3.right, new Vector3(1, 1, 0), new Vector3(1, 1, 1), new Vector3(1, 0, 1), //右面 DCGH
Vector3.forward, new Vector3(0, 1, 1), new Vector3(1, 1, 1), new Vector3(1, 0, 1)}; //后面 EFGH
mesh.vertices = cubeVer;
//设置三角形数组
int[] triangle = {
//正面ABCD
0, 1, 3,
1, 2, 3,
//上面BFGC
4, 5, 7,
5, 6, 7,
//右面 DCGH
8, 9, 11,
9, 10, 11,
//后面 EFGH
15, 14, 12,
14, 13, 12
};
mesh.triangles = triangle;
Vector3[] cubeNor = { Vector3.back, Vector3.up, Vector3.up, Vector3.back, //正面ABCD
Vector3.up, Vector3.up, Vector3.up, Vector3.up,//上面BFGC
new Vector3(2, 0, 0), new Vector3(2, 1, 0), new Vector3(2, 1, 1), new Vector3(2, 0, 1),//右面 DCGH
Vector3.up, Vector3.up, Vector3.up, new Vector3(2, 0, 1)};//后面 EFGH
mesh.normals = cubeNor;
Vector2[] uv = { Vector2.zero, Vector2.up, new Vector2(1, 1), Vector2.right, //正面ABCD
Vector2.zero, Vector2.up, new Vector2(1, 1), Vector2.right,//上面BFGC
Vector2.zero, Vector2.up, new Vector2(1, 1), Vector2.right,//右面 DCGH
//这个坐标只是确定uv贴图显示的位置,下面两个设置方式可以让图显示位置不同
//Vector2.zero, Vector2.up, new Vector2(1, 1), Vector2.right,//后面 EFGH
Vector2.right, new Vector2(1, 1), Vector2.up, Vector2.zero,
};
mesh.uv = uv;
}
效果如下: