Mesh 翻译文章汇总
AmyBoy:Unity Mesh Basics(Unity Mesh基础)系列翻译汇总zhuanlan.zhihu.com![f73460ee30e218111af2c8e6d87afd97.png](https://img-blog.csdnimg.cn/img_convert/f73460ee30e218111af2c8e6d87afd97.png)
原作者:Jasper Flick
由于水平有限,可能翻译的会有错误,请大家在评论区指出,我会及时更新改正。
原文链接
Cube Sphere, a Unity C# Tutorialcatlikecoding.com![649ebddd8d10fbcd22fd342c079ebbd6.png](https://img-blog.csdnimg.cn/img_convert/649ebddd8d10fbcd22fd342c079ebbd6.png)
本教程的目标
- 把立方体变成球体
- 在Unity中观察转变过程
- 对转换过程进行研究
- 运用数学推理改进转换方法
在本教程中,我们将创建一个立方体为基础的球体网格,然后使用数学推理来改进它。
本教程使用的版本 Unity2018.4.1。
![e1078d4ef18d31b8277c05fd2ca7b92b.png](https://img-blog.csdnimg.cn/img_convert/e1078d4ef18d31b8277c05fd2ca7b92b.png)
1 调整圆角立方体
你可能已经注意到,你可以用一个圆形的立方体创建一个完美的球体。 确保它在所有三个维度中大小相等,并将其roundness设置为当前值的一半。
如果你想要的只是一个球体,那么圆角立方体所提供的灵活性只会起到阻碍作用。 因此,让我们为它创建一个单独的组件。 复制 RoundedCube 脚本并将其重命名为 CubeSphere。 然后用一个 gridSize 替换这三个尺寸,并删除roundness字段。
public class CubeSphere : MonoBehaviour {
public int gridSize;
// No more roundness.
private Mesh mesh;
private Vector3[] vertices;
private Vector3[] normals;
private Color32[] cubeUV;
…
private void Generate () {
GetComponent<MeshFilter>().mesh = mesh = new Mesh();
mesh.name = "Procedural Sphere";
CreateVertices();
CreateTriangles();
CreateColliders();
}
…
}
如何将三个大小字段替换为一个?
最方便的方法是使用编辑器重构变量名。这将确保对它们的所有引用也被重命名。首先将xSize重命名为gridSize,然后对ySize和zSize执行相同的操作。或者,执行多个搜索和替换操作。
您将得到一些格式为gridSize + gridSize的代码片段。您可以通过重写表达式来合并这些内容,但是并没有真正的必要,因此不必理会。
去除roundness字段将会导致报错,因为它仍然在一些地方使用,所以我们先保留它。让我们首先看看碰撞器的创建。
我们需要多个盒子和胶囊碰撞器来表示圆形立方体,但球体只需要一个球体碰撞器。 这意味着我们可以删除 AddBoxCollider 和 Addcapsulecolider 方法,并将 CreateColliders 减少到一行代码。
private void CreateColliders () {
gameObject.AddComponent<SphereCollider>();
}
接下来是顶点的位置,这也取决于roundness字段。
我们通过将立方体内部的球进行归一化处理,通过从球内部指向原始立方体某处的向量,将这些向量进行计算后,来形成球体的外表面。
如果我们的球体以它的原点为中心也会很方便。我们还没有在网格和圆角立方体上做这个,但是在这种情况下,它使球体的创建更简单,所以让我们把它居中。
要创建一个单位球体(一个半径为1单位的球体)我们需要对一个以原点为中心的立方体的顶点进行归一化。为了让立方体精确地包含球体,它需要边长为2。
![971dcb5848e56e5af413772180905c9b.png](https://img-blog.csdnimg.cn/img_convert/971dcb5848e56e5af413772180905c9b.png)
我们可以在 SetVertex 中创建这个立方体的顶点,方法是用网格大小除以原始坐标,将其加倍,然后减去单位向量。
public float radius = 1;
private void SetVertex (int i, int x, int y, int z) {
Vector3 v = new Vector3(x, y, z) * 2f / gridSize - Vector3.one;
normals[i] = v.normalized;
vertices[i] = normals[i];
cubeUV[i] = new Color32((byte)x, (byte)y, (byte)z, 0);
}
这将产生一个单位球体,但是你可能需要一个不同的半径。 所以让我们来设置一下。
public float radius = 1f;
private void SetVertex (int i, int x, int y, int z) {
Vector3 v = new Vector3(x, y, z) * 2f / gridSize - Vector3.one;
normals[i] = v.normalized;
vertices[i] = normals[i] * radius;
cubeUV[i] = new Color32((byte)x, (byte)y, (byte)z, 0);
}
现在所有的错误应该都消失了,我们可以在场景中放置一个由立方体生成的球体对象,或者从头开始,或者替换一个圆形立方体对象的组成部分。
![670339ae48d0ee890aa81e5fb1ae23c3.png](https://img-blog.csdnimg.cn/img_convert/670339ae48d0ee890aa81e5fb1ae23c3.png)
2 研究球体表面网格单元
我们有一种创建球体网格的方法,但是它们有多好呢?让我们从多个角度来看一个球体。网格有多均匀?
![a6a58e62be9cc629cb9d688163a02a98.png](https://img-blog.csdnimg.cn/img_convert/a6a58e62be9cc629cb9d688163a02a98.png)
网格单元的大小变化很大。 最大的单元格来自立方体面的中间。 它们大约是最小单元的四倍,最小单元来自立方体的角落。
为了弄清楚为什么会这样,我们来观察下一个正方形转换另一个圆形的过程。 我们将使用一个圆,因为这比一个球更容易操作。
我们可以使用Gizmos在Unity中创建可视化效果。我们将使用带有OnDrawGizmosSelected方法的自定义组件。它的工作方式与OnDrawGizmos相似,只是只有在选择对象时才会绘制Gizmos。这样,我们可以将可视化对象添加到场景中,除非选择它,否则它将保持不可见。
using UnityEngine;
public class CircleGizmo : MonoBehaviour {
private void OnDrawGizmosSelected () {
}
}
我们首先用黑色球体显示正方形边上的顶点,分辨率随意设置。 让我们从顶部和底部边缘开始。
public int resolution = 10;
private void OnDrawGizmosSelected () {
float step = 2f / resolution;
for (int i = 0; i <= resolution; i++) {
ShowPoint(i * step - 1f, -1f);
ShowPoint(i * step - 1f, 1f);
}
}
private void ShowPoint (float x, float y) {
Vector2 square = new Vector2(x, y);
Gizmos.color = Color.black;
Gizmos.DrawSphere(square, 0.025f);
}
![086d8f89773c5345991acc00a0630a73.png](https://img-blog.csdnimg.cn/img_convert/086d8f89773c5345991acc00a0630a73.png)
填充左边缘和右边缘完成正方形。 由于角点已经显示,我们现在可以跳过它们。
private void OnDrawGizmosSelected () {
float step = 2f / resolution;
for (int i = 0; i <= resolution; i++) {
ShowPoint(i * step - 1f, -1f);
ShowPoint(i * step - 1f, 1f);
}
for (int i = 1; i < resolution; i++) {
ShowPoint(-1f, i * step - 1f);
ShowPoint(1f, i * step - 1f);
}
}
![48899ab89d154987d2b35ae122e60a09.png](https://img-blog.csdnimg.cn/img_convert/48899ab89d154987d2b35ae122e60a09.png)
接下来,对于每个点,我们也将显示相应的圆顶点,使用一个白色的球体。然后用黄线表示这两个顶点之间的关系。最后是一条从圆顶点到圆心的灰线。
![f379590ff151c3c7f5841558e2f84499.png](https://img-blog.csdnimg.cn/img_convert/f379590ff151c3c7f5841558e2f84499.png)
现在我们可以看到正方形转换为圆形的工作原理。将正方形上的顶点坐标做归一化处理后,将会得到一个单位圆(半径为1),越靠近正方形角的位置,归一化得到的点与原顶点的距离会越来越大。实际上,恰好位于圆主轴上的顶点不会移动,而位于正方形对角线上的顶点需要移动√2−1单位长度。
3 使用数学推理出优化方法
现在我们知道为什么面上的元素最终会有不同的大小了。 我们能做点什么吗? 也许有一个不同的映射,产生更多的统一的元素。 如果是这样,我们如何找到这样的映射?
这实际上是一个数学问题,所以让我们以数学专业的角度重新来看待这个映射现象。我们一开始只是使用圆来验证,后面我们可以推广到球面上。
我们把一个正方形上的点映射到一个圆上的点。由于点是用向量来描述的,我们实际上是把一个向量映射到另一个向量。
具体地说,我们的圆是单位圆,但包裹它的是一个边长为2的正方形。 所以我们的映射只是向量
![aacf66a9746814d7a5f44b4a5b9f0b7a.png](https://img-blog.csdnimg.cn/img_convert/aacf66a9746814d7a5f44b4a5b9f0b7a.png)
向量的归一化是通过除以它自身的长度来完成的。
![b7ca7e17624d41d3c9d6949e7c68a22c.png](https://img-blog.csdnimg.cn/img_convert/b7ca7e17624d41d3c9d6949e7c68a22c.png)
你如何得到一个二维矢量的长度? 它由两个坐标组成。
![ad0f3cb6daa13768d4357bfe8ba4899e.png](https://img-blog.csdnimg.cn/img_convert/ad0f3cb6daa13768d4357bfe8ba4899e.png)
这些坐标定义了一个直角三角形,你可以将它应用到勾股定理上。
![30ad429d646fad54d3a26870d0fb29d6.png](https://img-blog.csdnimg.cn/img_convert/30ad429d646fad54d3a26870d0fb29d6.png)
![c729049851d419964697b79c024c4360.png](https://img-blog.csdnimg.cn/img_convert/c729049851d419964697b79c024c4360.png)
矢量的长度就是它的平方根。
![629f9acb9461fcf81e010576a8d8e73e.png](https://img-blog.csdnimg.cn/img_convert/629f9acb9461fcf81e010576a8d8e73e.png)
现在,我们可以以最明确的形式编写映射。
![cc2578a9cff8d7cebb6d496346dea848.png](https://img-blog.csdnimg.cn/img_convert/cc2578a9cff8d7cebb6d496346dea848.png)
这很好,但是我们如何证明
![0af75b566cb69c7c3c24c6fe2cf40ad2.png](https://img-blog.csdnimg.cn/img_convert/0af75b566cb69c7c3c24c6fe2cf40ad2.png)
这很难处理,所以我们把等式两边都平方。
![2fd91636387dca4b46555f1d4eaccbcd.png](https://img-blog.csdnimg.cn/img_convert/2fd91636387dca4b46555f1d4eaccbcd.png)
可以简化此过程,直到它变得很简单。
![4f4853d1c23145365d34dd7e33d6abc0.png](https://img-blog.csdnimg.cn/img_convert/4f4853d1c23145365d34dd7e33d6abc0.png)
因为最终表达式的分子和分母相等,所以结果必须是1。 也就是说,除非
这个证明有用吗?它告诉我们有一个公式对于正方形的每个点都是1。这个公式对应于一个从正方形到单位圆的映射。
我们能找到一个不同的公式来做同样的事情吗?如果是,也必须有一个不同的映射!让我们把这个过程反过来。提出一个公式,使我们的正方形上的每个点都是1。
如果我们尝试使其尽可能简单,那么该函数将是什么样?我们知道至少一个坐标始终为-1或1。因此,如果对两个坐标求平方,则至少得到一个1,或者我们可以通过
![bc4c483eaff5f1064a92700623e529d0.png](https://img-blog.csdnimg.cn/img_convert/bc4c483eaff5f1064a92700623e529d0.png)
为什么是用1减而不是加?
我们也可以使用两者均有效。在这种情况下,减法更为直观,因为该公式在原点处产生零,而在离原点更远的点处产生更大的结果。当我们重写它时,它也使我们可以消除1,这很方便。
这个公式可以改写成更简单的形式。
![3635b51d5e93cc57767ddf8553abcc15.png](https://img-blog.csdnimg.cn/img_convert/3635b51d5e93cc57767ddf8553abcc15.png)
你怎么重写它?
这里有两个中间步骤。![]()
最终结果是1减去这个。
现在我们有了一种新的方法来定义
![467cb85e5e4e81c8629621ccd9b3dfc3.png](https://img-blog.csdnimg.cn/img_convert/467cb85e5e4e81c8629621ccd9b3dfc3.png)
我们重新整理一下方程的右边,使它成为a+b的形式。
![da2aa21576e1e002ccadab753fbb711a.png](https://img-blog.csdnimg.cn/img_convert/da2aa21576e1e002ccadab753fbb711a.png)
我们现在可以把它分成两个坐标,
![aa6dc425921fd0f3bd4beb8bc3d208c1.png](https://img-blog.csdnimg.cn/img_convert/aa6dc425921fd0f3bd4beb8bc3d208c1.png)
你怎么重写它?
这是的过度。
![2adc119029559b3099b46cd1c3e980e6.png](https://img-blog.csdnimg.cn/img_convert/2adc119029559b3099b46cd1c3e980e6.png)
我们找到的这个向量给了我们一个新的映射从正方形到圆。让我们在ShowPoint函数上试试吧!
Vector2 circle;
circle.x = square.x * Mathf.Sqrt(1f - square.y * square.y * 0.5f);
circle.y = square.y * Mathf.Sqrt(1f - square.x * square.x * 0.5f);
![ef0160b3a05c435d56d6f6b208ef3df6.png](https://img-blog.csdnimg.cn/img_convert/ef0160b3a05c435d56d6f6b208ef3df6.png)
事实证明,此映射将点从对角线推向主轴方向。现在它们在轴附近最靠近。幸运的是,相邻点之间的距离比第一种方法更均匀,因此是一种改进。
4 调整映射公式
我们有了一个新的从正方形到圆形的映射公式,但是从立方体到球体呢? 我们可以使用同样的方法吗? 是的,但是我们需要加入第三个坐标,就像我们已经有的两个。 这样我们就得到了指向单位球面上点的向量的平方长度。
![3a4a0a5cf3c743ab7602ddc59d9e3148.png](https://img-blog.csdnimg.cn/img_convert/3a4a0a5cf3c743ab7602ddc59d9e3148.png)
实际上,这种方法适用于任意数量的坐标,因此我们可以从任意超立方体映射到任意维度的超球体。 扩展公式只会变得更加复杂。
![faa74ea8e9559b7db0cdeb910ee38e0b.png](https://img-blog.csdnimg.cn/img_convert/faa74ea8e9559b7db0cdeb910ee38e0b.png)
你要怎么重写?
我们已经知道如何处理两个坐标。
![159097339402cc7ec42a15b2123e919a.png](https://img-blog.csdnimg.cn/img_convert/159097339402cc7ec42a15b2123e919a.png)
中间步骤
![6c0a9ea83a414a7ae267d26d5bf04e4c.png](https://img-blog.csdnimg.cn/img_convert/6c0a9ea83a414a7ae267d26d5bf04e4c.png)
最终结果是1减去这个。
这个三维空间的公式有一个新项
![9f18eadef1bbd49c328a6edca90d8545.png](https://img-blog.csdnimg.cn/img_convert/9f18eadef1bbd49c328a6edca90d8545.png)
最后的映射仍然是一个平方根。
![3db6e63f69e290b76bcee757a8c5e044.png](https://img-blog.csdnimg.cn/img_convert/3db6e63f69e290b76bcee757a8c5e044.png)
让我们在 SetVertex 函数中使用它,看看它是什么样子的!
private void SetVertex (int i, int x, int y, int z) {
Vector3 v = new Vector3(x, y, z) * 2f / gridSize - Vector3.one;
float x2 = v.x * v.x;
float y2 = v.y * v.y;
float z2 = v.z * v.z;
Vector3 s;
s.x = v.x * Mathf.Sqrt(1f - y2 / 2f - z2 / 2f + y2 * z2 / 3f);
s.y = v.y * Mathf.Sqrt(1f - x2 / 2f - z2 / 2f + x2 * z2 / 3f);
s.z = v.z * Mathf.Sqrt(1f - x2 / 2f - y2 / 2f + x2 * y2 / 3f);
normals[i] = s;
vertices[i] = normals[i] * radius;
cubeUV[i] = new Color32((byte)x, (byte)y, (byte)z, 0);
}
![f00de70dd682537012011b8704a28ffb.png](https://img-blog.csdnimg.cn/img_convert/f00de70dd682537012011b8704a28ffb.png)
网格单元离对角线越近,就会变得越扭曲,这是无法避免的。 但是这种新的映射产生的单元比标准化方法大得多。 轴线和立方体拐角处的单元格现在看起来大致相同。 这比我们开始的时候好多了! 现在最大的单元格是沿着立方体边缘的那些单元格。 那些曾经被压扁的单元格,现在被拉长了。
你是怎么得出这样的数学公式的?
虽然它在这里是按逻辑顺序呈现的,但是自己搞清楚这些东西通常会走一条更不稳定的路。当机会出现时,遵循一个清晰的逻辑路径是很好的,但是直觉通常只有在实验之后才会出现。在此之前,调查一下其他人已经发现了什么。
互联网上有很多知识,好好利用吧!在本例中,Philip Nowell在他的博客中描述了立方体到球体的映射。映射背后的直觉来自一个数学堆栈交换问题的答案。
本文git仓库地址
Amy6922/UnityMeshDemogithub.com![fa6af0f22c95c72bac749c9dd0e735d9.png](https://img-blog.csdnimg.cn/img_convert/fa6af0f22c95c72bac749c9dd0e735d9.png)
接下来,我们模拟球体表面受外力的变形的状态。
AmyBoy:UnityMesh编程(4)网格变形zhuanlan.zhihu.com