|
今天我们来讲一点有趣的立体几何问题。
在可视化与可视化分析中,基础的几何图形绘制是一切的基础,比如常见的饼状图由扇形组成,柱状图由长方形组成,玫瑰图由弧形组成。在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[SectorNumber * 12];
- Vector3[] vertices = mesh.vertices;
- int[] triangles = mesh.triangles;
- vertices[0].x = 0;
- vertices[0].y = Height / 2;
- vertices[0].z = 0;
- float tmpArc;
- int tmpCount = 0;
- for (int i = 0; i < SectorNumber; ++i)
- {
- tmpArc = 2 * Mathf.PI * i / SectorNumber;
- vertices[i + 1].x = Mathf.Sin(tmpArc) * Radius;
- vertices[i + 1].y = Height / 2;
- vertices[i + 1].z = Mathf.Cos(tmpArc) * Radius;
- triangles[tmpCount++] = 0;
- triangles[tmpCount++] = i + 1;
- triangles[tmpCount++] = i != (SectorNumber - 1) ? i + 2 : 1;
- }
- for(int i = SectorNumber; i < SectorNumber*2; ++i)
- {
- tmpArc = 2 * Mathf.PI * i / SectorNumber;
- vertices[i + 1].x = Mathf.Sin(tmpArc) * Radius;
- vertices[i + 1].y = -Height / 2;
- vertices[i + 1].z = Mathf.Cos(tmpArc) * Radius;
- triangles[tmpCount++] = SectorNumber * 2 + 1;
- triangles[tmpCount++] = i != (SectorNumber * 2 - 1) ? i + 2 : SectorNumber + 1;
- triangles[tmpCount++] = i + 1;
- }
- vertices[SectorNumber * 2 + 1].x = 0;
- vertices[SectorNumber * 2 + 1].y = -Height / 2;
- vertices[SectorNumber * 2 + 1].z = 0;
- for(int i = 1; i < SectorNumber; ++i)
- {
- triangles[tmpCount++] = i;
- triangles[tmpCount++] = i + SectorNumber;
- triangles[tmpCount++] = i + 1;
- triangles[tmpCount++] = i + 1;
- triangles[tmpCount++] = i + SectorNumber;
- triangles[tmpCount++] = i + SectorNumber + 1;
- }
- triangles[tmpCount++] = SectorNumber;
- triangles[tmpCount++] = 2 * SectorNumber;
- triangles[tmpCount++] = 1;
- triangles[tmpCount++] = 1;
- triangles[tmpCount++] = 2 * SectorNumber;
- triangles[tmpCount++] = 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[i * 4].x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
- vertices[i * 4].z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
- vertices[i * 4].y = - Height / 2;
- vertices[i * 4 + 1].x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 1].z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 1].y = Height / 2;
- vertices[i * 4 + 2].x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 2].z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 2].y = Height / 2;
- vertices[i * 4 + 3].x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 3].z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
- vertices[i * 4 + 3].y = - Height / 2;
- }
- mesh.vertices = vertices;
- int[] triangles = mesh.triangles;
- for (int i = 0; i < pieces; ++i)
- {
- triangles[i * 24] = i * 4;
- triangles[i * 24 + 1] = (i + 1) * 4;
- triangles[i * 24 + 2] = (i + 1) * 4 + 1;
- triangles[i * 24 + 3] = i * 4;
- triangles[i * 24 + 4] = (i + 1) * 4 + 1;
- triangles[i * 24 + 5] = i * 4 + 1;
- triangles[i * 24 + 6] = i * 4 + 1;
- triangles[i * 24 + 7] = (i + 1) * 4 + 1;
- triangles[i * 24 + 8] = (i + 1) * 4 + 2;
- triangles[i * 24 + 9] = i * 4 + 1;
- triangles[i * 24 + 10] = (i + 1) * 4 + 2;
- triangles[i * 24 + 11] = i * 4 + 2;
- triangles[i * 24 + 12] = i * 4 + 2;
- triangles[i * 24 + 13] = (i + 1) * 4 + 2;
- triangles[i * 24 + 14] = (i + 1) * 4 + 3;
- triangles[i * 24 + 15] = i * 4 + 2;
- triangles[i * 24 + 16] = (i + 1) * 4 + 3;
- triangles[i * 24 + 17] = i * 4 + 3;
- triangles[i * 24 + 18] = i * 4 + 3;
- triangles[i * 24 + 19] = (i + 1) * 4 + 3;
- triangles[i * 24 + 20] = (i + 1) * 4;
- triangles[i * 24 + 21] = i * 4 + 3;
- triangles[i * 24 + 22] = (i + 1) * 4;
- triangles[i * 24 + 23] = i * 4;
- }
- triangles[pieces * 24] = 0;
- triangles[pieces * 24 + 1] = 1;
- triangles[pieces * 24 + 2] = 3;
- triangles[pieces * 24 + 3] = 1;
- triangles[pieces * 24 + 4] = 2;
- triangles[pieces * 24 + 5] = 3;
- triangles[pieces * 24 + 6] = 4 * pieces;
- triangles[pieces * 24 + 7] = 4 * pieces + 2;
- triangles[pieces * 24 + 8] = 4 * pieces + 1;
- triangles[pieces * 24 + 9] = 4 * pieces;
- triangles[pieces * 24 + 10] = 4 * pieces + 3;
- triangles[pieces * 24 + 11] = 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[size1 + size2 * 3];
- int[] triangles2 = new int[size2 * 12];
- for (int i = 0; i < size1; ++i)
- {
- vectices2[i] = vectices[i];
- }
- for (int i = 0; i < size2; ++i)
- {
- Vector3 center1 = Mid(vectices[triangles[i * 3 + 1]], vectices[triangles[i * 3 + 2]]).normalized;
- Vector3 center2 = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 2]]).normalized;
- Vector3 center3 = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 1]]).normalized;
- vectices2[size1 + i * 3] = center1;
- vectices2[size1 + i * 3 + 1] = center2;
- vectices2[size1 + i * 3 + 2] = center3;
- triangles2[i * 12] = triangles[i * 3];
- triangles2[i * 12 + 1] = size1 + i * 3 + 2;
- triangles2[i * 12 + 2] = size1 + i * 3 + 1;
- triangles2[i * 12 + 3] = size1 + i * 3 + 2;
- triangles2[i * 12 + 4] = triangles[i * 3 + 1];
- triangles2[i * 12 + 5] = size1 + i * 3;
- triangles2[i * 12 + 6] = size1 + i * 3 + 1;
- triangles2[i * 12 + 7] = size1 + i * 3;
- triangles2[i * 12 + 8] = triangles[i * 3 + 2];
- triangles2[i * 12 + 9] = size1 + i * 3;
- triangles2[i * 12 + 10] = size1 + i * 3 + 1;
- triangles2[i * 12 + 11] = 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[size1 + size2];
- int[] triangles2 = new int[size2 * 9];
- for (int i = 0; i < size1; ++i)
- {
- vectices2[i] = vectices[i];
- }
- for (int i = 0; i < size2; ++i)
- {
- Vector3 center = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 1]], vectices[triangles[i * 3 + 2]]).normalized;
- //vectices2[size1 + i] = center * (Random.Range(0f,1f)<0.6? 1: outterRadius);
- vectices2[size1 + i] = center * outterRadius;
- triangles2[i * 9] = triangles[i * 3];
- triangles2[i * 9 + 1] = triangles[i * 3 + 1];
- triangles2[i * 9 + 2] = size1 + i;
- triangles2[i * 9 + 3] = triangles[i * 3];
- triangles2[i * 9 + 4] = size1 + i;
- triangles2[i * 9 + 5] = triangles[i * 3 + 2];
- triangles2[i * 9 + 6] = size1 + i;
- triangles2[i * 9 + 7] = triangles[i * 3 + 1];
- triangles2[i * 9 + 8] = triangles[i * 3 + 2];
- }
- mesh.Clear();
- mesh.vertices = vectices2;
- mesh.triangles = triangles2;
复制代码 效果如下:
最后,可能有人想问,为什么要画这些?
怎么说呢,因为好玩,就是这么任性,有机会再跟大家分享一些其他的好玩的东西。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|