Procedural Grid(程序网格)
原文 https://catlikecoding.com/unity/tutorials/procedural-grid/
1 | 1.创建点网格 |
在本教程中,我们将创建一个简单的顶点和三角形网格
复杂的外观之下是简单的几何形状
渲染物体
如果你想在 Unity 中可视化某些东西,你可以使用网格。 它可能是从另一个程序导出的 3D 模型。 它可能是程序生成的网格。 它可以是精灵、UI 元素或粒子系统,Unity 也为此使用网格。 甚至屏幕效果也使用网格渲染。
那么什么是网格? 从概念上讲,网格是图形硬件用来绘制复杂内容的构造。 它至少包含一组定义 3D 空间中的点的顶点,以及一组连接这些点的三角形(最基本的 2D 形状)。 三角形构成了网格所代表的任何表面。
由于三角形是平的并且有直边,它们可以用来完美地可视化平直的东西,比如立方体的面。 弯曲或圆形表面只能通过使用许多小三角形来近似。 如果三角形看起来足够小——不大于一个像素——那么你就不会注意到近似值。 通常这对于实时性能来说是不可行的,因此表面总是会出现某种程度的锯齿状。
Unity 的默认胶囊体、立方体和球体,着色模式与线框模式
1 | 如何显示线框? |
如果你想让一个游戏对象显示一个 3D 模型,它需要有两个组件。 第一种是 Mesh Filter
,该组件包含对您希望显示的网格的引用。 第二个是Mesh Render
,您可以使用它来配置网格的渲染方式。 应该使用哪种材质,是否应该投射或接收阴影,等等。
Unity 的默认立方体游戏对象
1 | 为什么会有一系列的材料? |
您可以通过调整其材质来完全改变网格的外观。 Unity 的默认材质只是纯白色。 您可以通过 Assets / Create / Material 创建一个新的材质资源并将其拖到您的游戏对象上来替换它。 新材质默认使用 Unity 的标准着色器,它为您提供一组控件来调整表面的视觉行为方式。
向网格添加大量细节的快速方法是提供反照率贴图。 这是表示材料基本颜色的纹理。 当然,我们需要知道如何将此纹理投影到网格的三角形上。 这是通过将 2D 纹理坐标添加到顶点来完成的。 纹理空间的两个维度被称为 U 和 V,这就是为什么它们被称为 UV 坐标。 这些坐标通常位于 (0, 0) 和 (1, 1) 之间,覆盖整个纹理。 根据纹理设置,超出该范围的坐标会被固定或导致平铺。
应用于 Unity 网格的 UV 测试纹理
创建顶点网格
那么如何制作自己的网格呢? 让我们通过生成一个简单的矩形网格来找出答案。 网格将由单位长度的方形瓷砖(四边形)组成。 创建一个新的 C# 脚本并将其转换为具有水平和垂直大小的网格组件。
1 | using UnityEngine; |
当我们将此组件添加到游戏对象时,我们还需要给它一个Mesh Filter
和Mesh Render
。 我们可以为我们的类添加一个属性,让 Unity 自动为我们添加它们。
1 | [ ] |
现在您可以创建一个新的空游戏对象,将网格组件添加到其中,它还将具有其他两个组件。 设置渲染器的材质并保留过滤器的网格未定义。 我将网格的大小设置为 10 x 5。
Grid物件
一旦对象Awake
,我们就会生成实际的网格,这发生在我们进入播放模式时。
1 | private void Awake() |
让我们首先关注顶点位置,然后将三角形留到后面。 我们需要保存一个 3D 向量数组来存储这些点。 顶点的数量取决于网格的大小。 我们需要在每个四边形的拐角处有一个顶点,但相邻的四边形可以共享同一个顶点。 因此,我们需要比每个维度中的图块多一个顶点。
(#x+1)(#y+1)
4 x 2 网格的顶点和四边形索引
1 | private Vector3[] vertices; |
让我们可视化这些顶点,以便我们可以检查我们是否正确定位它们。 我们可以通过添加一个 OnDrawGizmos
方法并在场景视图中为每个顶点绘制一个小的黑色球体来做到这一点。
1 | private void OnDrawGizmos() |
1 | 什么是 gizmos? |
当我们不处于播放模式时,这将产生错误,因为当 Unity 处于编辑模式时,当我们没有任何顶点时,也会调用 OnDrawGizmos 方法。 为防止出现此错误,请检查数组是否存在,如果不存在则跳出方法。
1 | private void OnDrawGizmos () { |
Gizmos
在播放模式下,我们只能在原点看到一个球体。 这是因为我们还没有定位顶点,所以它们都在那个位置重叠。 我们必须使用双循环遍历所有位置。
1 | private void Generate() |
1 | 为什么Gizmos不随物体移动? |
我们现在看到了顶点,但是它们的放置顺序是不可见的。 我们可以使用颜色来显示这一点,但我们也可以通过使用协程来减慢这个过程。 这就是我在脚本中使用 System.Collections
的原因。
1 | private void Awake() |
创建网格
现在我们知道顶点的位置正确,我们可以处理实际的网格。 除了在我们自己的组件中保存对它的引用之外,我们还必须将它分配给 Mesh Filter
。 然后,一旦我们处理了顶点,我们就可以将它们提供给我们的网格。
1 | private Mesh mesh; |
1 | 我们的组件是否需要抓住网格? |
在Play模式下
我们现在有一个网格处于播放模式,但它还没有出现,因为我们没有给它任何三角形。 三角形是通过顶点索引数组定义的。 由于每个三角形都有三个点,因此三个连续的索引描述了一个三角形。 让我们从一个三角形开始。
1 | private IEnumerator Generate () |
我们现在有一个三角形,但是我们使用的三个点都在一条直线上。 这会产生一个不可见的退化三角形。 前两个顶点很好,但是我们应该跳到下一行的第一个顶点。
1 | triangles[0] = 0; |
这确实给了我们一个三角形,但它只能从一个方向看到。 在这种情况下,它仅在从 Z 轴的相反方向看时可见。 因此,您可能需要旋转视图才能看到它。
三角形从哪一侧可见取决于其顶点索引的方向。 默认情况下,如果它们以顺时针方向排列,则三角形被认为是向前且可见的。 逆时针三角形被丢弃,因此我们不需要花时间渲染对象的内部,这些对象通常无论如何都不会被看到。
三角形的两条边
所以当我们向下看 Z 轴时,要让三角形出现,我们必须改变它的顶点遍历的顺序。 我们可以通过交换最后两个索引来做到这一点。
1 | triangles[0] = 0; |
第一个三角形
我们现在有一个三角形覆盖了我们网格的第一个图块的一半。 为了覆盖整个瓷砖,我们只需要第二个三角形。
1 | int[] triangles = new int[6]; |
由两个三角形组成的四边形
由于这些三角形共享两个顶点,我们可以将其减少到四行代码,明确地只提到每个顶点索引一次。
1 | triangles[0] = 0; |
第一个四边形
我们可以通过把它变成一个循环来创建整个第一行图块。 当我们迭代顶点和三角形索引时,我们必须跟踪两者。 让我们也将 yield 语句移动到这个循环中,这样我们就不再需要等待顶点出现了。
1 | int[] triangles = new int[xSize * 6]; |
顶点 Gizmo 现在立即出现,并且在短暂的等待后,所有三角形都立即出现。 要看到瓦片一张一张地出现,我们必须在每次迭代时更新网格,而不是仅在循环之后更新。
1 | mesh.triangles = triangles; |
现在通过将单循环变成双循环来填充整个网格。 请注意,移动到下一行需要将顶点索引增加一个,因为每行的顶点数比图块多一个。
1 | int[] triangles = new int[xSize * ySize * 6]; |
如您所见,整个网格现在都充满了三角形,一次一行。 一旦您对此感到满意,您可以删除所有协程代码,以便立即创建网格。
1 | private void Awake () { |
1 | 为什么不使用单个四边形? |
生成附加顶点数据
我们的网格目前以一种特殊的方式点亮。 那是因为我们还没有给网格提供任何法线。 默认的法线方向是 (0, 0, 1),这与我们需要的完全相反。
1 | 法线是如何工作的? |
法线是每个顶点定义的,所以我们必须填充另一个向量数组。 或者,我们可以要求网格根据其三角形来计算法线本身。 这次让我们偷懒吧。
1 | private void Generate () { |
1 | 如何重新计算法线? |
没有法线和有法线的区别
接下来是UV坐标。 您可能已经注意到网格当前具有统一的颜色,即使它使用具有反照率纹理的材质。 这是有道理的,因为如果我们自己不提供 UV 坐标,那么它们都为零。
要使纹理适合我们的整个网格,只需将顶点的位置除以网格尺寸即可。
1 | vertices = new Vector3[(xSize + 1) * (ySize + 1)]; |
不正确的 UV 坐标、夹紧与包裹纹理
纹理现在出现了,但它没有覆盖整个网格。 它的确切外观取决于纹理的环绕模式是否设置为钳制或重复。 发生这种情况是因为我们目前正在将整数除以整数,这会产生另一个整数。 为了在整个网格中获得零和一之间的正确坐标,我们必须确保我们使用的是浮点数。
1 | uv[i] = new Vector2((float)x / xSize, (float)y / ySize); |
纹理现在投影到整个网格上。 当我将网格的大小设置为 10 x 5 时,纹理将显示为水平拉伸。 这可以通过调整材质的纹理平铺设置来解决。 通过将其设置为 (2, 1),U 坐标将加倍。 如果纹理设置为重复,那么我们将看到它的两个方形图块。
正确的 UV 坐标,平铺 1,1 与 2,1
向表面添加更明显细节的另一种方法是使用法线贴图。 这些贴图包含编码为颜色的法线向量。 将它们应用于表面将产生比单独使用顶点法线创建的更详细的灯光效果。
凹凸不平的表面,采用金属制成,具有戏剧性的效果
将此材料应用于我们的网格会产生凹凸,但它们是不正确的。 我们需要将切线向量添加到我们的网格中以正确定位它们。
1 | 切线如何工作? |
由于我们有一个平面,所有切线都指向同一个方向,即向右。
假装凹凸不平的平坦表面
现在您知道如何创建简单的网格并使用材质使其看起来更复杂。 网格需要顶点位置和三角形,通常也是 UV 坐标 - 最多四组 - 通常也需要切线。 您也可以添加顶点颜色,尽管 Unity 的标准着色器不使用这些颜色。 您可以创建自己的着色器来使用这些颜色,但这是另一个教程的内容。