Mesh 翻译文章汇总
AmyBoy:Unity Mesh Basics(Unity Mesh基础)系列翻译汇总zhuanlan.zhihu.com原作者:Jasper Flick
由于水平有限,可能翻译的会有错误,请大家在评论区指出,我会及时更新改正。
这是篇译文,附上原文链接
https://catlikecoding.com/unity/tutorials/procedural-grid/catlikecoding.com本教程的目标
- 创建网格点
- 使用协程来分析它们的位置
- 用三角形定义一个曲面
- 自动生成法线
- 添加纹理坐标和切线
在本教程中,我们将创建一个简单的顶点和三角形网格。
本教程是采用Unity2018.4.1版本。
![acde80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/acde80c6-fe14-eb11-8da9-e4434bdf6706.png)
1 渲染事物
如果要在Unity中可视化某些内容,你需要使用到网格。这些网格可能是从另一个程序导出的3D模型,也可能是通过代码生成的网格。 它可能是Sprite,UI元素或粒子系统,Unity也为此使用了网格。甚至屏幕效果都用网格渲染。
那什么是网格呢?从概念上讲,网格是图形硬件用来绘制复杂图形的结构。它至少包含一组定义3D空间中的点的顶点集合,以及一组连接这些点的三角形(最基本的2D形状)。三角形构成网格所代表的任何表面。
由于三角形是平的,而且有直边,所以它们可以用来完美地显示平直的东西,就像立方体的面一样。曲面或圆面只能用许多小三角形拼接近似的表示。如果三角形看起来足够小(不超过一个像素)那么你就不会注意到这个近似值。通常这对于实时性能来说是不可行的,因此表面总是在某种程度上呈现锯齿状。
![b1de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/b1de80c6-fe14-eb11-8da9-e4434bdf6706.png)
![b3de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/b3de80c6-fe14-eb11-8da9-e4434bdf6706.png)
如何在unity显示线框图 选择场景视图在其工具栏左侧的显示模式。前三个选项是阴影线框和阴影线框。
如果你想让一个游戏对象显示一个3D模型,它需要两个组件。第一个是MeshFilter。 此组件包含对要显示的网格的引用。 第二个是MeshRender。 你可以使用它来配置网格渲染的方式,应该使用哪种材质、是否应该反射或接收阴影,等等。
![b6de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p02.5ceimg.com/content/b6de80c6-fe14-eb11-8da9-e4434bdf6706.png)
为什么会有一列的材质? 一个MeshRender(网格渲染器)可以有多个材质。这主要用于渲染具有多个独立三角形集的网格,称为子网格。这些主要用于导入的3D模型,本教程中不会介绍。
你可以通过调整材质来完全改变网格的外观。Unity的默认材质是纯白色。你可以通过 Assets /Create/Material 创建一个新的材质,然后拖拽到你的游戏对象上,用你自己的材质替换默认材质。新的材质默认使用 Unity 的标准着色器,它提供一组控件来调整表面的视觉效果。
为网格添加大量细节的一个快速方法是提供一个反照贴图。这是一种纹理,代表了一种材料的基本颜色。当然,我们需要知道如何将这个纹理投影到网格的三角形上。这是通过向顶点添加2D纹理坐标来实现的。纹理空间的两个维度被称为u和v,这就是为什么它们被称为UV坐标的原因。这些坐标通常位于(0,0)和(1,1)之间,它们覆盖了整个纹理。超出这个范围的坐标要么被限制,要么导致平铺,这取决于纹理设置。
![b9de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/b9de80c6-fe14-eb11-8da9-e4434bdf6706.png)
2 创建顶点网格
那么,如何制作自己的网格呢? 让我们通过生成一个简单的矩形网格来找出答案。网格将由单位长度的正方形块(四边形)组成。创建一个新的c#脚本,并将其转换为一个具有水平和垂直大小的网格组件。
using
当我们把这个组件添加到一个游戏对象,我们需要给它一个MeshFilter和MeshRender。我们可以添加一个属性到类中,让Unity自动为我们添加它们。
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
现在你可以创建一个新的空游戏对象,并将网格组件添加到其中,它还将拥有其他两个组件。设置渲染器的材质,保持MeshFilter的网格未定义。我将网格的大小设置为10×5。
![bdde80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/bdde80c6-fe14-eb11-8da9-e4434bdf6706.png)
当我们进入播放模式时,Grid脚本加载时,我们就生成实际的网格。
private
让我们先关注顶点位置,稍后再讨论三角形。我们需要一个三维向量数组来存储这些点。顶点的数量取决于网格的大小。我们需要在每个四边形的角上有一个顶点,但是相邻的四边形可以共享同一个顶点。因此,面上的每个维度上的顶点数都会多一个。
(#x+1)(#y+1)
![c0de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/c0de80c6-fe14-eb11-8da9-e4434bdf6706.png)
private
让我们把这些顶点可视化,这样我们就可以检查它们的位置是否正确。 我们可以通过添加 OnDrawGizmos 方法并在场景视图中为每个顶点绘制一个小黑球来实现。
private
什么是Gizmos?
Gizmos是可以在编辑器中使用的可视化提示。默认情况下,它们在场景视图中可见,而在游戏视图中不可见,但你可以通过它们的工具栏进行调整。Gizmos实用工具类允许你绘制图标、线条和其他一些东西。
可以在OnDrawGizmos方法中绘制Gizmos,该方法由Unity编辑器自动调用。另一种方法是OnDrawGizmosSelected,它只对选定的对象调用。
这将产生错误,当我们不在播放模式,因为OnDrawGizmos方法也被调用时,Unity在编辑模式,当我们没有任何顶点。要防止此错误,请检查数组是否存在,如果不存在则跳出方法。
private
![c3de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/c3de80c6-fe14-eb11-8da9-e4434bdf6706.png)
在播放模式中,我们在原点只能看到一个球体。这是因为我们还没有给顶点定位,所以它们都在那个位置重叠。我们必须使用双循环遍历所有位置。
private
为什么Gizmos不会随着物体移动?
Gizmos是直接在世界空间中绘制的,而不是在对象的局部空间中。如果你想让它们根据你的对象变化位置,你必须通过使用transform. transformpoint(vertices[i])来声明,代替vertices[i]。
我们现在可以看到顶点,但是它们被放置的顺序是不可见的。 我们可以使用颜色来显示这一点,但是我们也可以通过使用协程来减缓这一过程。
private
3 创建网格
现在我们知道顶点的位置是正确的,我们可以处理实际的网格。 除了在我们自己的组件中保存对它的引用之外,我们还必须将它分配给MeshFilter。一旦我们处理了顶点,我们就可以把它们放到网格里。
private
我们的组件是否需要持有网格的引用?
我们只需要在Generate方法中引用网格。但由于MeshFilter组件也有对网格引用,因此无论如何都需要将其持久化保存。我现在将其设置为全局变量,但因为本教程下一个逻辑步骤是对网格进行动画处理,所以我鼓励你先尝试一下将网格持久化。
ps:这的意思就是MeshFilter 在播放模式下才会有对网格的引用,这里将网格持久化保存,就是使MeshFilter组件在编辑模式也持有网格的引用。
![c7de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/c7de80c6-fe14-eb11-8da9-e4434bdf6706.png)
现在,我们在播放模式下有一个网格,但是它还没有显示出来,因为我们没有给它任何三角形。三角形是通过顶点索引数组定义的。由于每个三角形都有三个点,因此三个连续的索引描述一个三角形。让我们从一个三角形开始。
private
现在我们有一个三角形,但是我们正在使用的三个点全部位于一条直线上。这将生成一个不可见的退化三角形。前两个顶点很好,但是接下来我们应该跳到下一行的第一个顶点。
triangles
这确实给了我们一个三角形,但仅从一个方向可见。在这种情况下,仅当沿Z轴的相反方向看时才可见。因此,您可能需要旋转视图才能看到它。
三角形从哪一侧可见是由其顶点索引的方向确定的。默认情况下,如果它们按顺时针方向排列,则该三角形被认为是朝前的且可见。逆时针三角形被丢弃,因此我们不需要花时间渲染对象的内部,因为它们本来就不应该被看到。
![c9de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/c9de80c6-fe14-eb11-8da9-e4434bdf6706.png)
因此,当我们从z轴反方向(ps:就是默认unity方向)看时,要使三角形出现,我们必须更改其顶点的遍历顺序。我们可以通过交换最后两个索引来实现。
(ps:这里索引1与索引2交换位置,是受顶点位置的生成顺序影响,如下图,上面的点的对应顶点数组的索引为xSize+1,但按照顺时针顺序对应三角形对应索引为1。)
triangles
![cfde80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p05.5ceimg.com/content/cfde80c6-fe14-eb11-8da9-e4434bdf6706.png)
现在,我们有一个三角形,覆盖了网格第一个图块的一半。要覆盖整个图块,我们需要的是第二个三角形。
int
![d2de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/d2de80c6-fe14-eb11-8da9-e4434bdf6706.png)
由于这些三角形共享两个顶点,我们可以将其减少到四行代码,只显式地声明每个顶点索引一次。
triangles
![d5de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/d5de80c6-fe14-eb11-8da9-e4434bdf6706.png)
通过将其变成一个循环,我们可以创建整个第一行图块。在迭代顶点和三角形索引时,我们必须同时跟踪两者。让我们也将yield语句移入此循环,这样我们就不必等待顶点出现了。
int
现在将会显示所有的顶点,并且在短暂的等待后显示全部的三角形。为了看到图块一个接一个地出现,我们必须在每次迭代时更新网格,而不是仅在循环之后更新。
mesh
现在,通过将单循环变成双循环来填充整个网格。请注意,移至下一行需要将顶点索引增加一个。
int
![dade80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/dade80c6-fe14-eb11-8da9-e4434bdf6706.png)
正如你所看到的,整个网格现在充满了三角形,一次一行。 如果你对此效果满意,你可以删除所有的协同代码,这样网格将毫不延迟地被创建。
private
为什么不使用一个四边形?
当我们创建一个扁平的矩形表面时,只要两个三角形就足够了。这是绝对正确的。更复杂的结构的要点是它允许更多的控制和表达。
4 生成附加的顶点数据
当前,我们的网格以特殊方式显示。那是因为我们还没有给网格赋予任何法线。默认的法线方向为(0,0,1),与我们所需的方向完全相反。
法线是如何工作的?
法向量是垂直于曲面的向量。我们总是使用单位长度的法线它们指向表面的外面,而不是里面。
法线可以用来确定光线照射表面的角度(如果有的话)。具体如何使用取决于着色器。
由于三角形总是平坦的,因此不需要提供有关法线的单独信息。然而,这样做我们可以作弊。实际上,顶点没有法线的,但三角形有。通过将自定义法线附加到顶点并在三角形之间进行插值,我们可以假装我们拥有一个平滑的曲面而不是一堆三角形。只要不注意网格的鲜明轮廓,这种现象就可以让人相信。
每个顶点都定义了法线,因此我们必须填充另一个矢量数组。或者,我们可以要求网格根据其三角形计算出法线本身。
private
如何重新计算法线?
Mesh.RecalculateNormals方法通过确定哪些三角形与该顶点连接,确定那些三角形的法线,对其求平均并对结果进行归一化,来计算每个顶点的法线。
![dede80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/dede80c6-fe14-eb11-8da9-e4434bdf6706.png)
接下来是UV坐标。你可能已经注意到,尽管网格使用反照率纹理的材料,但当前具有统一的颜色。这是很容易理解,因为如果我们自己不提供UV坐标,那么它们全为零。
要使纹理适合整个网格,只需将顶点的位置除以网格尺寸即可。
vertices
![e1de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p01.5ceimg.com/content/e1de80c6-fe14-eb11-8da9-e4434bdf6706.png)
纹理现在显示出来,但它没有覆盖整个网格。它的准确外观取决于纹理的填充模式是设置为clamp还是repeat。这是因为我们现在用整数除整数,结果是另一个整数。为了在整个网格中得到0和1之间的正确坐标,我们必须确保使用浮点数。
uv
纹理现在被投影到整个网格上。 当我将网格的大小设置为10 × 5时,纹理将显示为水平拉伸。 这可以通过调整材质的填充方式设置来解决。 将其设置为(2,1) ,u 坐标将加倍。 如果纹理被设置为repeat,那么我们将看到它的两个方块。
![e4de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p04.5ceimg.com/content/e4de80c6-fe14-eb11-8da9-e4434bdf6706.png)
![e9de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p02.5ceimg.com/content/e9de80c6-fe14-eb11-8da9-e4434bdf6706.png)
另一种增加表面细节的方法是使用法线贴图。这些贴图包含用颜色表示的法向量。将它们应用到一个表面上将会产生比仅使用顶点法线创建更详细的光效果。
![ecde80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p03.5ceimg.com/content/ecde80c6-fe14-eb11-8da9-e4434bdf6706.png)
将这些材料应用到我们的网格中并没有给我们带来任何波动的感觉。 我们首先需要给网格添加切向量。
切线是如何工作的?
法线贴图在切线空间中定义。这是一个围绕对象表面流动的3D空间。这种方法使我们可以在不同的位置和方向上应用相同的法线贴图。
表面法线在此空间中表示向上,但是哪个方向是正确的?那是由切线定义的。理想情况下,这两个向量之间的角度为90°。它们的叉积产生定义3D空间所需的第三方向。实际上,角度通常不是90°,但结果仍然足够好。
因此,切线是3D向量,但是Unity实际上使用4D向量。它的第四个分量总是-1或1,用来控制第三个切线空间的方向-向前或向后。这有助于法线贴图的镜像,法线贴图通常用于具有双边对称性的事物(例如人)的3D模型中。 Unity着色器执行此计算的方式要求我们使用-1。
因为我们有一个平面,所以所有切线都指向同一方向,即右边。
![eede80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p02.5ceimg.com/content/eede80c6-fe14-eb11-8da9-e4434bdf6706.png)
网格除了需要顶点位置和三角形外,通常也需要UV坐标(最多四组)以及切线。你也可以添加顶点颜色,尽管Unity的标准着色器不使用这些颜色。
Unity包catlikecoding.com Amy6922/UnityMeshDemogithub.com![f1de80c6-fe14-eb11-8da9-e4434bdf6706.png](http://p02.5ceimg.com/content/f1de80c6-fe14-eb11-8da9-e4434bdf6706.png)
现在,你知道了如何创建一个简单的网格来形成一个面,那么接下来试试,组合这些面来创建一个3D的物体吧。
AmyBoy:Unity Mesh编程(2) 使用代码生成圆角立方体zhuanlan.zhihu.com