Unity的立体几何问题
今天我们来讲一点有趣的立体几何问题。在可视化与可视化分析中,基础的几何图形绘制是一切的基础,比如常见的饼状图由扇形组成,柱状图由长方形组成,玫瑰图由弧形组成。在2D的世界里,这些都是SVG和Canvas绘图的基础。在3D的世界中,前端库three.js也提供了与之对应的3D绘制方法,但是在Unity中对于3D基础模型的提供支持却相对较少,需要专门编写代码来生成。今天我们就讲一讲在Unity里面通过绘制这些基础3D结构的方法,同时也聊一聊这些基本3D结构的数学绘制逻辑。
在Unity绘图中,有Mesh和Render两部分组成,Mesh用于描述物体的结构,Render描述这个物体该绘制成什么颜色。今天主要讲Mesh。
Mesh是由顶点和这些顶点构成的基础三角形组成。
顶点à三角形。
正X面体
对于我们来说,最基础的3D结构应该是正方体,也就是正6面体。由8个顶点,6个正方形组成。这6个正方形又可以被拆分成12个等腰直角三角形。那么描述一个正方体可以表示为:
Vector3 p0 = new Vector3(0.5f, 0.5f, 0.5f).normalized;
Vector3 p1 = new Vector3(-0.5f, 0.5f, 0.5f).normalized;
Vector3 p2 = new Vector3(-0.5f, -0.5f, 0.5f).normalized;
Vector3 p3 = new Vector3(0.5f, -0.5f, 0.5f).normalized;
Vector3 p4 = new Vector3(0.5f, 0.5f, -0.5f).normalized;
Vector3 p5 = new Vector3(-0.5f, 0.5f, -0.5f).normalized;
Vector3 p6 = new Vector3(-0.5f, -0.5f, -0.5f).normalized;
Vector3 p7 = new Vector3(0.5f, -0.5f, -0.5f).normalized;
mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5 };
mesh.triangles = new int[]
{
0,1,2,
0,2,3,
1,5,6
1,6,2,
5,4,7,
5,7,6,
4,0,3
4,3,7,
0,4,1,
4,5,1,
3,2,6,
3,6,7
};绘制效果:
最基础的3D物体是正4面体,也就是每个面都是正三角形的三棱锥。由4个顶点,4个三角形组成。那么描述一个正4面体可以表示为:
Vector3 p0 = new Vector3(0, 0, 0);
Vector3 p1 = new Vector3(1, 0, 0);
Vector3 p2 = new Vector3(0.5f, 0, Mathf.Sqrt(0.75f));
Vector3 p3 = new Vector3(0.5f, Mathf.Sqrt(0.75f), Mathf.Sqrt(0.75f) / 3);
Vector3 center = (p0 + p1 + p2 + p3) / 4;
p0 = (p0 - center).normalized;
p1 = (p1 - center).normalized;
p2 = (p2 - center).normalized;
p3 = (p3 - center).normalized;
mesh.vertices = new Vector3[] { p0, p1, p2, p3 };
mesh.triangles = new int[]{
0,1,2,
0,2,3,
2,1,3,
0,3,1
};绘制效果:
正8面体,由8个正三角形组成,其中有6个顶点。那么一个正8面体可以描述为:
Vector3 p0 = new Vector3(1, 0, 0);
Vector3 p1 = new Vector3(0, 1, 0);
Vector3 p2 = new Vector3(0, 0, 1);
Vector3 p3 = new Vector3(-1, 0, 0);
Vector3 p4 = new Vector3(0, -1, 0);
Vector3 p5 = new Vector3(0, 0, -1);
mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5 };
mesh.triangles = new int[]
{
2,0,1,
3,2,1,
5,3,1,
0,5,1,
3,5,4,
5,0,4,
0,2,4,
2,3,4
};绘制效果:
前面都还比较简单,正20面体,由20个正三角形组成,其中有12个顶点。对于一般人而言,这个时候想在头脑中想象出来正20面体的样子还是有点难的,这里我教大家怎么绘制正20面体。
首先,准备3块一样大小的长方形纸板,其长宽比为(√5 + 1):2,按照如图的方式进行组合:
其顶点连接组成的形状就是一个正20面体,单独对正20面体的任意一个角进行观察,这个角周围贴合了5个正三角形。就像这个样子:
根据上面的三个纸板参考的方式,我们可以容易的描述一个正20面体:
Vector3 p0 = new Vector3(Mathf.Sqrt(5f) + 1f, 2f, 0).normalized;
Vector3 p1 = new Vector3(Mathf.Sqrt(5f) + 1f, -2f, 0).normalized;
Vector3 p2 = new Vector3(-Mathf.Sqrt(5f) - 1f, -2f, 0).normalized;
Vector3 p3 = new Vector3(-Mathf.Sqrt(5f) - 1f, 2f, 0).normalized;
Vector3 p4 = new Vector3(2f, 0, Mathf.Sqrt(5f) + 1f).normalized;
Vector3 p5 = new Vector3(-2f, 0, Mathf.Sqrt(5f) + 1f).normalized;
Vector3 p6 = new Vector3(-2f, 0, -Mathf.Sqrt(5f) - 1f).normalized;
Vector3 p7 = new Vector3(2f, 0, -Mathf.Sqrt(5f) - 1f).normalized;
Vector3 p8 = new Vector3(0, Mathf.Sqrt(5f) + 1f, 2f).normalized;
Vector3 p9 = new Vector3(0, Mathf.Sqrt(5f) + 1f, -2f).normalized;
Vector3 p10 = new Vector3(0, -Mathf.Sqrt(5f) - 1f, -2f).normalized;
Vector3 p11 = new Vector3(0, -Mathf.Sqrt(5f) - 1f, 2f).normalized;
mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11 };
mesh.triangles = new int[]
{
0,8,4,
0,4,1,
0,1,7,
0,7,9,
0,9,8,
2,6,10,
2,3,6,
2,5,3,
2,11,5,
2,10,11,
1,11,10,
1,4,11,
11,4,5,
5,4,8,
3,5,8,
3,8,9,
3,9,6,
6,9,7,
6,7,10,
10,7,1
};绘制出来效果如下:
好了,除了正12面体以外,基础的正X面体的绘制方法已经讲完了。
柱体
基础的3D图形还有哪些呢?
长方体,长方体可以通过正方体的Scale变换而轻易得到,这里不多说。
圆柱,怎么通过点和线来描述一个圆柱体呢?
首先我们知道一个圆可以拆分成很多个同心小扇形,在扇形角度很小的时候,可以用三角形来代替扇形。通过将圆形拉长就可以得到一个圆柱。描述代码如下:
mesh.vertices = new Vector3[(SectorNumber + 1) * 2];
mesh.triangles = new int;
Vector3[] vertices = mesh.vertices;
int[] triangles = mesh.triangles;
vertices.x = 0;
vertices.y = Height / 2;
vertices.z = 0;
float tmpArc;
int tmpCount = 0;
for (int i = 0; i < SectorNumber; ++i)
{
tmpArc = 2 * Mathf.PI * i / SectorNumber;
vertices.x = Mathf.Sin(tmpArc) * Radius;
vertices.y = Height / 2;
vertices.z = Mathf.Cos(tmpArc) * Radius;
triangles = 0;
triangles = i + 1;
triangles = i != (SectorNumber - 1) ? i + 2 : 1;
}
for(int i = SectorNumber; i < SectorNumber*2; ++i)
{
tmpArc = 2 * Mathf.PI * i / SectorNumber;
vertices.x = Mathf.Sin(tmpArc) * Radius;
vertices.y = -Height / 2;
vertices.z = Mathf.Cos(tmpArc) * Radius;
triangles = SectorNumber * 2 + 1;
triangles = i != (SectorNumber * 2 - 1) ? i + 2 : SectorNumber + 1;
triangles = i + 1;
}
vertices.x = 0;
vertices.y = -Height / 2;
vertices.z = 0;
for(int i = 1; i < SectorNumber; ++i)
{
triangles = i;
triangles = i + SectorNumber;
triangles = i + 1;
triangles = i + 1;
triangles = i + SectorNumber;
triangles = i + SectorNumber + 1;
}
triangles = SectorNumber;
triangles = 2 * SectorNumber;
triangles = 1;
triangles = 1;
triangles = 2 * SectorNumber;
triangles = SectorNumber + 1;
mesh.vertices = vertices;
mesh.triangles = triangles;绘制效果如下(可以通过控制SectorNumber参数来控制圆柱的精细程度,左边24个扇区,右边12个扇区):
同样,弧形柱的绘制方式也和圆柱相似,代码:
mesh.vertices = new Vector3[(pieces + 1) * 4];
Vector3[] vertices = mesh.vertices;
mesh.triangles = new int[(pieces*8 + 4)*3];
float CurrentArc = 0;
for (int i = 0; i <= pieces; ++i)
{
CurrentArc = i * Arc / pieces + StartArc;
vertices.x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
vertices.z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
vertices.y = - Height / 2;
vertices.x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
vertices.z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
vertices.y = Height / 2;
vertices.x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
vertices.z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
vertices.y = Height / 2;
vertices.x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
vertices.z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
vertices.y = - Height / 2;
}
mesh.vertices = vertices;
int[] triangles = mesh.triangles;
for (int i = 0; i < pieces; ++i)
{
triangles = i * 4;
triangles = (i + 1) * 4;
triangles = (i + 1) * 4 + 1;
triangles = i * 4;
triangles = (i + 1) * 4 + 1;
triangles = i * 4 + 1;
triangles = i * 4 + 1;
triangles = (i + 1) * 4 + 1;
triangles = (i + 1) * 4 + 2;
triangles = i * 4 + 1;
triangles = (i + 1) * 4 + 2;
triangles = i * 4 + 2;
triangles = i * 4 + 2;
triangles = (i + 1) * 4 + 2;
triangles = (i + 1) * 4 + 3;
triangles = i * 4 + 2;
triangles = (i + 1) * 4 + 3;
triangles = i * 4 + 3;
triangles = i * 4 + 3;
triangles = (i + 1) * 4 + 3;
triangles = (i + 1) * 4;
triangles = i * 4 + 3;
triangles = (i + 1) * 4;
triangles = i * 4;
}
triangles = 0;
triangles = 1;
triangles = 3;
triangles = 1;
triangles = 2;
triangles = 3;
triangles = 4 * pieces;
triangles = 4 * pieces + 2;
triangles = 4 * pieces + 1;
triangles = 4 * pieces;
triangles = 4 * pieces + 3;
triangles = 4 * pieces + 2;
mesh.triangles = triangles;绘制效果:
梯形柱的绘制逻辑就是只有一个扇面的弧形柱,因此在此就不再多加赘述。
球体
在Unity3D世界中,球体也是通过一个一个小三角形来表示的。怎么去生成一个由小三角形组成的球呢,常见的对于地球的划分是经纬度方法,如图:
这种分割球形表面的方法有个弊端,球靠近赤道两边的节点会很稀疏,靠近两极的节点会很密集,因此在这里不多做介绍。
比较好的做法是通过将正X面体每个面进行细分来拟近圆,对于正X面体的每个小三角面,按照每条边的中点将其拆分成四个三角形,拆分方式如图:
将拆分的新的四个三角形,贴合到球面上。然后依次迭代,就可以得到一个拟合不错的“球”。
其拆分逻辑如下:
Vector3[] vectices = mesh.vertices;
int[] triangles = mesh.triangles;
int size1 = vectices.Length;
int size2 = triangles.Length / 3;
Vector3[] vectices2 = new Vector3;
int[] triangles2 = new int;
for (int i = 0; i < size1; ++i)
{
vectices2 = vectices;
}
for (int i = 0; i < size2; ++i)
{
Vector3 center1 = Mid(vectices], vectices]).normalized;
Vector3 center2 = Mid(vectices], vectices]).normalized;
Vector3 center3 = Mid(vectices], vectices]).normalized;
vectices2 = center1;
vectices2 = center2;
vectices2 = center3;
triangles2 = triangles;
triangles2 = size1 + i * 3 + 2;
triangles2 = size1 + i * 3 + 1;
triangles2 = size1 + i * 3 + 2;
triangles2 = triangles;
triangles2 = size1 + i * 3;
triangles2 = size1 + i * 3 + 1;
triangles2 = size1 + i * 3;
triangles2 = triangles;
triangles2 = size1 + i * 3;
triangles2 = size1 + i * 3 + 1;
triangles2 = size1 + i * 3 + 2;
}
mesh.Clear();
mesh.vertices = vectices2;
mesh.triangles = triangles2;
各种类型的正面体拟合效果:
正4面体(4个基础三角面):
正8面体(8个基础三角面):
正6面体(正方体)(12个基础三角面):
正20面体:
看到这里有人可能就会问了,为什么没有讲正12面体,也就下面这位:
但是正12面体每个基本面是正五边形,再拆分成三角形的话至少会有36个基础三角形,因此不拿它来拟合球。
看到这,可能有人会问,小编讲了这么多到底要干嘛?
其实我就是突然有一天,想画一个海胆,就是下面这位:
但是Unity自带的几何结构很少,没法画。因此,自己动手写代码画。
要画这么一个海胆,首先要能画一个球,再将用于模拟球面的每个三角面通过下图中的变化来突出刺来。
代码逻辑如下:
Vector3[] vectices = mesh.vertices;
int[] triangles = mesh.triangles;
int size1 = vectices.Length;
int size2 = triangles.Length / 3;
Vector3[] vectices2 = new Vector3;
int[] triangles2 = new int;
for (int i = 0; i < size1; ++i)
{
vectices2 = vectices;
}
for (int i = 0; i < size2; ++i)
{
Vector3 center = Mid(vectices], vectices], vectices]).normalized;
//vectices2 = center * (Random.Range(0f,1f)<0.6? 1: outterRadius);
vectices2 = center * outterRadius;
triangles2 = triangles;
triangles2 = triangles;
triangles2 = size1 + i;
triangles2 = triangles;
triangles2 = size1 + i;
triangles2 = triangles;
triangles2 = size1 + i;
triangles2 = triangles;
triangles2 = triangles;
}
mesh.Clear();
mesh.vertices = vectices2;
mesh.triangles = triangles2;效果如下:
最后,可能有人想问,为什么要画这些?
怎么说呢,因为好玩,就是这么任性,有机会再跟大家分享一些其他的好玩的东西。
页:
[1]