在Unity中我们可以实现动态网格,并将部分信息写入(顶点,UV,法线信息,切线信息);
首先明确一点的是Unity采用的坐标系
坐标系
在unity当中,采用的 左手坐标系
顶点Vertex
因为Unity空间坐标系是基于左手,网格亦是遵循此规则,
在生成顶点Vertex时,是按照左下角开始,向右上角逐个生成.
其中Z分量越大,则深度越大,分量越小,则高度越高.
多边形Triangle
在完成顶点坐标的确定之后,需要对mesh的多边形进行设置,
这里要注意2个问题:
- 对图形内外的判断:
因为三角形是通过顶点索引数组定义的.由于每个三角形都有三个点,因此三个连续的索引描述一个三角形.
而这个方向是按照顶点序列的顺时针进行判断的,因此,我们可以通过左手定则进行判断内外.
所以要正确显示,就需要按照正确的顶点序列去构建三角形数组. - 构建三角形数组
在搞清楚顺序之后,我们可以先按照顺序构建一行4个三角形,这样便于我们发现规律
因为每两个相邻的三角形会共用两个顶点,且每6次就会迭代一次
这样我们得出这一行的规律:
int[] triangles = new int[xSize * 6];
for (int ti = 0, vi = 0, x = 0; x < xSize; x++, ti += 6, vi++) {
triangles[ti] = vi;
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
triangles[ti + 5] = vi + xSize + 2;
}
再按照这一规律拓展到每一行:
int[] triangles = new int[xSize * ySize * 6];
for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++)
{
for (int x = 0; x < xSize; x++, ti += 6, vi++)
{
triangles[ti] = vi;
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
triangles[ti + 5] = vi + xSize + 2;
mesh.triangles = triangles;//设置三角形
}
}
mesh.tangents = tangents;//设置切线
UV坐标
UV坐标一般是由两个分量构成,其范围为0~1,
如果要将UV信息录入Mesh网格,则需要将顶点信息进行归一化,每个顶点的位置映射在uv坐标中,对应顶点的分量取值范围为[0,1].
uv[i] = new Vector2((float)x / xSize, (float)y / ySize);//归一化得到uv坐标
mesh.uv = uv;//设置uv
法线Normal
法线的方向为垂直于三角形的切面,并且方向向外.
但是,默认的法线方向是(0, 0, 1),这与我们需要的完全相反.(根据左手原则,Z分量1的方向朝里)
因此我们需要unity辅助函数RecalculateNormals()计算法线.
mesh.RecalculateNormals();//重新计算法线
切线Tangent
切线的方向正好于发现正好垂直,我们取一个单位向量作为切线方向.
因为切线是需要4个分量作为计算参数,最后一个W分量取-1.
至于为什么要用4个分量,可能与图形基本变换矩阵有关,因为需要完成非线性变换,所以使用齐次
坐标.可以参考图形学中的基本变换.
可以发现tangent 和normal的点乘结果为0,即法线和切线为正交关系.
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
完整代码:
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class Grid : MonoBehaviour
{
public int xSize, ySize;
private void Awake()
{
StartCoroutine(Generate());
}
private Vector3[] vertices;
private Mesh mesh;
IEnumerator Generate()
{
WaitForSeconds wait = new WaitForSeconds(0.05f);
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Grid";
vertices = new Vector3[(xSize + 1) * (ySize + 1)];
Vector2[] uv = new Vector2[vertices.Length];
Vector4[] tangents = new Vector4[vertices.Length];//切线
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
for (int i = 0, y = 0; y <= ySize; y++)
{
for (int x = 0; x <= xSize; x++, i++)
{
vertices[i] = new Vector3(x, y);
uv[i] = new Vector2((float)x / xSize, (float)y / ySize);//归一化得到uv坐标
tangents[i] = tangent;
yield return wait;
}
}
mesh.vertices = vertices;//设置顶点
mesh.uv = uv;//设置uv
mesh.tangents = tangents;//设置切线
int[] triangles = new int[xSize * ySize * 6];
for (int ti = 0, vi = 0, y = 0; y < ySize; y++, vi++)
{
for (int x = 0; x < xSize; x++, ti += 6, vi++)
{
triangles[ti] = vi;
triangles[ti + 3] = triangles[ti + 2] = vi + 1;
triangles[ti + 4] = triangles[ti + 1] = vi + xSize + 1;
triangles[ti + 5] = vi + xSize + 2;
mesh.triangles = triangles;//设置三角形
yield return wait;
}
}
mesh.RecalculateNormals();//重新计算法线
}
private void OnDrawGizmos()
{
if (vertices == null)
{
return;
}
Gizmos.color = Color.black;
for (int i = 0; i < vertices.Length; i++)
{
Gizmos.DrawSphere(vertices[i], 0.1f);
}
}
}