原文英文版链接https://catlikecoding.com/unity/tutorials/procedural-grid/,里面有每一部分的untiy工程链接,文章内容也更详实。
本章内容:
- 创建一系列点
- 使用协程实现他们的摆放位置
- 定义一个由三角形组成的平面
- 自动生成法线
- 添加纹理坐标和切线
1.渲染啥
你如果想在unity中看见什么东西,那你肯定需要一个mesh。它可能是一个由其他软件导出的3D模型或者是程序化生成的mesh。它可能是一个sprite,UI元素或者粒子特效,总之在unity里你想看到点东西你就得用mesh去渲染。
所以mesh是个啥?从概念上讲,一个mesh是图形硬件用于渲染复杂东西的一系列数据组合,它至少包括顶点的集合以及一系列三角形。三角形是最基本的2d图形,它连接了三个点,无论mesh是什么样,都是这些三角形组成了每个平面。
三角形平坦且边缘直,可以完美的展示平坦平直的东西,比如一个立方体的面。带弧度或者凹进去的平面只能通过大量小三角形近似模拟。如果这些小三角形足够小,小于一个像素,那你对这种近似就没有感知。但是对于实时的渲染不太可行,这些平面(圆弧的面)在某些角度看上去会有锯齿。
Unity's default capsule, cube, and sphere, shaded vs. wireframe
在unity中渲染3D物体需要两个组件mesh filter 和 mesh rendere,filter指向你想要渲染的mesh(渲染what),renderer定义如何渲染(how 怎么渲染)。
提供一个albedo颜色贴图可以是最简单便捷的方式使得mesh渲染时富有细节。通过UV坐标,可以实现贴图的采样从而进行渲染。
2.创建一系列顶点
如何创建自己的mesh?
创建C#Grid类
using UnityEngine; using System.Collections; public class Grid : MonoBehaviour {public int xSize, ySize; }
自动添加所需的component
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class Grid : MonoBehaviour {public int xSize, ySize; }
Awake方法里调用Genrate函数
private void Awake () {Generate();}
由下图所示,对于大小横向为x纵向为y的grid,实际需要的顶点数量是 (x+1) * (y+1)
创建顶点数组并赋予其每个点位置
private void Generate () {vertices = new Vector3[(xSize + 1) * (ySize + 1)];for (int i = 0, y = 0; y <= ySize; y++) {for (int x = 0; x <= xSize; x++, i++) {vertices[i] = new Vector3(x, y);}}}
Gizmos调试
private void OnDrawGizmos () {Gizmos.color = Color.black;for (int i = 0; i < vertices.Length; i++) {Gizmos.DrawSphere(vertices[i], 0.1f);}}
Gizmos的效果(x = 10 y = 5)
3.创建Mesh
之后需要为mesh设置三角形片元,这里设置的值其实是之前顶点数组的索引,也就是数组的下标。
上边这么设置三个点,是啥也看不到的,因为这三个点在一条线上,无法组成三角形
triangles[0] = 0; triangles[1] = 1; triangles[2] = xSize + 1;
这么设置组成了三角形,但是也看不到,这是因为这个顺序是逆时针顺序,会被判定为背面,渲染时会被剃除
按如下设置,我们将会得到第一个三角形
triangles[0] = 0;triangles[1] = xSize + 1;triangles[2] = 1;
有了第一个三角形后,我们可以绘制两个三角形拼成一个正方形
之后我们就可以生成每一行,完整代码如下
private void Awake () {Generate();}private void Generate () {GetComponent<MeshFilter>().mesh = mesh = new Mesh();mesh.name = "Procedural Grid";vertices = new Vector3[(xSize + 1) * (ySize + 1)];for (int i = 0, y = 0; y <= ySize; y++) {for (int x = 0; x <= xSize; x++, i++) {vertices[i] = new Vector3(x, y);}}mesh.vertices = vertices;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;}
最后效果如下:
4.生成其他顶点数据
法线
我们还没有给我们生成的mesh赋予法线,默认的法线方向是(0,0,1),这并不是我们想要的。
每个Vertex(顶点)都需要设置法线,我们可以自己再创建一个数组来设置法线。还有更好的办法,用unity提供的功能,调用mesh中的RecalculateNormals函数,它会根据之前设置好的三角形数据自动计算法线。对于某个vertex,法线的计算是对包含该vertex的所有三角形的法线取平均值,并归一化。
有了法线才能进行光照的计算
UV坐标
默认UV坐标都是0,所以我们必须为每个vertex顶点设置UV坐标,UV坐标范围是0到1,所以设置方法如下
上边的代码不会正确的显示结果,这是因为我们应该使用float来计算UV而不是整数除法
我们可以通过设置贴图的tiling属性来达到一些效果
法线贴图
我们可以用法线贴图来给渲染增加更多效果。关于法线贴图的原理和技术请自行学习吧
目前法线贴图的信息是存储在切线空间(切线空间的知识请自行学习),所以我们需要为mesh中的顶点设置切线信息。因为我们目前做出来的是一个平坦的平面,所以切线信息很好设置,平行于平面。
法线贴图在一个平面上的效果,明明是一个平面,但是视觉上却有凹凸起伏的感觉