找回密码
 立即注册
查看: 582|回复: 0

Unity的立体几何问题

[复制链接]
发表于 2020-12-31 12:23 | 显示全部楼层 |阅读模式
今天我们来讲一点有趣的立体几何问题。


在可视化与可视化分析中,基础的几何图形绘制是一切的基础,比如常见的饼状图由扇形组成,柱状图由长方形组成,玫瑰图由弧形组成。在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个等腰直角三角形。那么描述一个正方体可以表示为:
  1. Vector3 p0 = new Vector3(0.5f, 0.5f, 0.5f).normalized;
  2. Vector3 p1 = new Vector3(-0.5f, 0.5f, 0.5f).normalized;
  3. Vector3 p2 = new Vector3(-0.5f, -0.5f, 0.5f).normalized;
  4. Vector3 p3 = new Vector3(0.5f, -0.5f, 0.5f).normalized;
  5. Vector3 p4 = new Vector3(0.5f, 0.5f, -0.5f).normalized;
  6. Vector3 p5 = new Vector3(-0.5f, 0.5f, -0.5f).normalized;
  7. Vector3 p6 = new Vector3(-0.5f, -0.5f, -0.5f).normalized;
  8. Vector3 p7 = new Vector3(0.5f, -0.5f, -0.5f).normalized;
  9. mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5 };
  10. mesh.triangles = new int[]
  11. {
  12.    0,1,2,
  13.    0,2,3,
  14.    1,5,6
  15.    1,6,2,
  16.    5,4,7,
  17.    5,7,6,
  18.    4,0,3
  19.    4,3,7,
  20.    0,4,1,
  21.    4,5,1,
  22.    3,2,6,
  23.    3,6,7
  24. };
复制代码
绘制效果:


最基础的3D物体是正4面体,也就是每个面都是正三角形的三棱锥。由4个顶点,4个三角形组成。那么描述一个正4面体可以表示为:
  1. Vector3 p0 = new Vector3(0, 0, 0);
  2. Vector3 p1 = new Vector3(1, 0, 0);
  3. Vector3 p2 = new Vector3(0.5f, 0, Mathf.Sqrt(0.75f));
  4. Vector3 p3 = new Vector3(0.5f, Mathf.Sqrt(0.75f), Mathf.Sqrt(0.75f) / 3);
  5. Vector3 center = (p0 + p1 + p2 + p3) / 4;
  6. p0 = (p0 - center).normalized;
  7. p1 = (p1 - center).normalized;
  8. p2 = (p2 - center).normalized;
  9. p3 = (p3 - center).normalized;
  10. mesh.vertices = new Vector3[] { p0, p1, p2, p3 };
  11. mesh.triangles = new int[]{
  12.    0,1,2,
  13.    0,2,3,
  14.    2,1,3,
  15.    0,3,1
  16. };
复制代码
绘制效果:


正8面体,由8个正三角形组成,其中有6个顶点。那么一个正8面体可以描述为:
  1. Vector3 p0 = new Vector3(1, 0, 0);
  2. Vector3 p1 = new Vector3(0, 1, 0);
  3. Vector3 p2 = new Vector3(0, 0, 1);
  4. Vector3 p3 = new Vector3(-1, 0, 0);
  5. Vector3 p4 = new Vector3(0, -1, 0);
  6. Vector3 p5 = new Vector3(0, 0, -1);
  7. mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5 };
  8. mesh.triangles = new int[]
  9. {
  10.    2,0,1,
  11.    3,2,1,
  12.    5,3,1,
  13.    0,5,1,
  14.    3,5,4,
  15.    5,0,4,
  16.    0,2,4,
  17.    2,3,4
  18. };
复制代码
绘制效果:


前面都还比较简单,正20面体,由20个正三角形组成,其中有12个顶点。对于一般人而言,这个时候想在头脑中想象出来正20面体的样子还是有点难的,这里我教大家怎么绘制正20面体。
首先,准备3块一样大小的长方形纸板,其长宽比为(√5 + 1):2,按照如图的方式进行组合:




其顶点连接组成的形状就是一个正20面体,单独对正20面体的任意一个角进行观察,这个角周围贴合了5个正三角形。就像这个样子:


根据上面的三个纸板参考的方式,我们可以容易的描述一个正20面体:
  1. Vector3 p0 = new Vector3(Mathf.Sqrt(5f) + 1f, 2f, 0).normalized;
  2. Vector3 p1 = new Vector3(Mathf.Sqrt(5f) + 1f, -2f, 0).normalized;
  3. Vector3 p2 = new Vector3(-Mathf.Sqrt(5f) - 1f, -2f, 0).normalized;
  4. Vector3 p3 = new Vector3(-Mathf.Sqrt(5f) - 1f, 2f, 0).normalized;
  5. Vector3 p4 = new Vector3(2f, 0, Mathf.Sqrt(5f) + 1f).normalized;
  6. Vector3 p5 = new Vector3(-2f, 0, Mathf.Sqrt(5f) + 1f).normalized;
  7. Vector3 p6 = new Vector3(-2f, 0, -Mathf.Sqrt(5f) - 1f).normalized;
  8. Vector3 p7 = new Vector3(2f, 0, -Mathf.Sqrt(5f) - 1f).normalized;
  9. Vector3 p8 = new Vector3(0, Mathf.Sqrt(5f) + 1f, 2f).normalized;
  10. Vector3 p9 = new Vector3(0, Mathf.Sqrt(5f) + 1f, -2f).normalized;
  11. Vector3 p10 = new Vector3(0, -Mathf.Sqrt(5f) - 1f, -2f).normalized;
  12. Vector3 p11 = new Vector3(0, -Mathf.Sqrt(5f) - 1f, 2f).normalized;
  13. mesh.vertices = new Vector3[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11 };
  14. mesh.triangles = new int[]
  15. {
  16.    0,8,4,
  17.    0,4,1,
  18.    0,1,7,
  19.    0,7,9,
  20.    0,9,8,
  21.    2,6,10,
  22.    2,3,6,
  23.    2,5,3,
  24.    2,11,5,
  25.    2,10,11,
  26.    1,11,10,
  27.    1,4,11,
  28.    11,4,5,
  29.    5,4,8,
  30.    3,5,8,
  31.    3,8,9,
  32.    3,9,6,
  33.    6,9,7,
  34.    6,7,10,
  35.    10,7,1
  36. };
复制代码
绘制出来效果如下:




好了,除了正12面体以外,基础的正X面体的绘制方法已经讲完了。


柱体
基础的3D图形还有哪些呢?


长方体,长方体可以通过正方体的Scale变换而轻易得到,这里不多说。
圆柱,怎么通过点和线来描述一个圆柱体呢?


首先我们知道一个圆可以拆分成很多个同心小扇形,在扇形角度很小的时候,可以用三角形来代替扇形。通过将圆形拉长就可以得到一个圆柱。描述代码如下:
  1. mesh.vertices = new Vector3[(SectorNumber + 1) * 2];
  2. mesh.triangles = new int[SectorNumber * 12];
  3. Vector3[] vertices = mesh.vertices;
  4. int[] triangles = mesh.triangles;
  5. vertices[0].x = 0;
  6. vertices[0].y = Height / 2;
  7. vertices[0].z = 0;
  8. float tmpArc;
  9. int tmpCount = 0;
  10. for (int i = 0; i < SectorNumber; ++i)
  11. {
  12.    tmpArc = 2 * Mathf.PI * i / SectorNumber;
  13.    vertices[i + 1].x = Mathf.Sin(tmpArc) * Radius;
  14.    vertices[i + 1].y = Height / 2;
  15.    vertices[i + 1].z = Mathf.Cos(tmpArc) * Radius;
  16.    triangles[tmpCount++] = 0;
  17.    triangles[tmpCount++] = i + 1;
  18.    triangles[tmpCount++] = i != (SectorNumber - 1) ? i + 2 : 1;
  19. }
  20. for(int i = SectorNumber; i < SectorNumber*2; ++i)
  21. {
  22.    tmpArc = 2 * Mathf.PI * i / SectorNumber;
  23.    vertices[i + 1].x = Mathf.Sin(tmpArc) * Radius;
  24.    vertices[i + 1].y = -Height / 2;
  25.    vertices[i + 1].z = Mathf.Cos(tmpArc) * Radius;
  26.    triangles[tmpCount++] = SectorNumber * 2 + 1;
  27.    triangles[tmpCount++] = i != (SectorNumber * 2 - 1) ? i + 2 : SectorNumber + 1;
  28.    triangles[tmpCount++] = i + 1;
  29. }
  30. vertices[SectorNumber * 2 + 1].x = 0;
  31. vertices[SectorNumber * 2 + 1].y = -Height / 2;
  32. vertices[SectorNumber * 2 + 1].z = 0;
  33. for(int i = 1; i < SectorNumber; ++i)
  34. {
  35.    triangles[tmpCount++] = i;
  36.    triangles[tmpCount++] = i + SectorNumber;
  37.    triangles[tmpCount++] = i + 1;
  38.    triangles[tmpCount++] = i + 1;
  39.    triangles[tmpCount++] = i + SectorNumber;
  40.    triangles[tmpCount++] = i + SectorNumber + 1;
  41. }
  42. triangles[tmpCount++] = SectorNumber;
  43. triangles[tmpCount++] = 2 * SectorNumber;
  44. triangles[tmpCount++] = 1;
  45. triangles[tmpCount++] = 1;
  46. triangles[tmpCount++] = 2 * SectorNumber;
  47. triangles[tmpCount++] = SectorNumber + 1;
  48. mesh.vertices = vertices;
  49. mesh.triangles = triangles;
复制代码
绘制效果如下(可以通过控制SectorNumber参数来控制圆柱的精细程度,左边24个扇区,右边12个扇区):




同样,弧形柱的绘制方式也和圆柱相似,代码:
  1. mesh.vertices = new Vector3[(pieces + 1) * 4];
  2. Vector3[] vertices = mesh.vertices;
  3. mesh.triangles = new int[(pieces*8 + 4)*3];
  4. float CurrentArc = 0;
  5. for (int i = 0; i <= pieces; ++i)
  6. {
  7.     CurrentArc = i * Arc / pieces + StartArc;
  8.     vertices[i * 4].x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
  9.     vertices[i * 4].z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
  10.     vertices[i * 4].y = - Height / 2;
  11.     vertices[i * 4 + 1].x = outterRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
  12.     vertices[i * 4 + 1].z = outterRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
  13.     vertices[i * 4 + 1].y = Height / 2;
  14.     vertices[i * 4 + 2].x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
  15.     vertices[i * 4 + 2].z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
  16.     vertices[i * 4 + 2].y = Height / 2;
  17.     vertices[i * 4 + 3].x = innerRadius * Mathf.Sin(CurrentArc * Mathf.PI / 180);
  18.     vertices[i * 4 + 3].z = innerRadius * Mathf.Cos(CurrentArc * Mathf.PI / 180);
  19.     vertices[i * 4 + 3].y = - Height / 2;
  20. }
  21. mesh.vertices = vertices;
  22. int[] triangles = mesh.triangles;
  23. for (int i = 0; i < pieces; ++i)
  24. {
  25.     triangles[i * 24] = i * 4;
  26.     triangles[i * 24 + 1] = (i + 1) * 4;
  27.     triangles[i * 24 + 2] = (i + 1) * 4 + 1;
  28.     triangles[i * 24 + 3] = i * 4;
  29.     triangles[i * 24 + 4] = (i + 1) * 4 + 1;
  30.     triangles[i * 24 + 5] = i * 4 + 1;
  31.     triangles[i * 24 + 6] = i * 4 + 1;
  32.     triangles[i * 24 + 7] = (i + 1) * 4 + 1;
  33.     triangles[i * 24 + 8] = (i + 1) * 4 + 2;
  34.     triangles[i * 24 + 9] = i * 4 + 1;
  35.     triangles[i * 24 + 10] = (i + 1) * 4 + 2;
  36.     triangles[i * 24 + 11] = i * 4 + 2;
  37.     triangles[i * 24 + 12] = i * 4 + 2;
  38.     triangles[i * 24 + 13] = (i + 1) * 4 + 2;
  39.     triangles[i * 24 + 14] = (i + 1) * 4 + 3;
  40.     triangles[i * 24 + 15] = i * 4 + 2;
  41.     triangles[i * 24 + 16] = (i + 1) * 4 + 3;
  42.     triangles[i * 24 + 17] = i * 4 + 3;
  43.     triangles[i * 24 + 18] = i * 4 + 3;
  44.     triangles[i * 24 + 19] = (i + 1) * 4 + 3;
  45.     triangles[i * 24 + 20] = (i + 1) * 4;
  46.     triangles[i * 24 + 21] = i * 4 + 3;
  47.     triangles[i * 24 + 22] = (i + 1) * 4;
  48.     triangles[i * 24 + 23] = i * 4;
  49. }
  50. triangles[pieces * 24] = 0;
  51. triangles[pieces * 24 + 1] = 1;
  52. triangles[pieces * 24 + 2] = 3;
  53. triangles[pieces * 24 + 3] = 1;
  54. triangles[pieces * 24 + 4] = 2;
  55. triangles[pieces * 24 + 5] = 3;
  56. triangles[pieces * 24 + 6] = 4 * pieces;
  57. triangles[pieces * 24 + 7] = 4 * pieces + 2;
  58. triangles[pieces * 24 + 8] = 4 * pieces + 1;
  59. triangles[pieces * 24 + 9] = 4 * pieces;
  60. triangles[pieces * 24 + 10] = 4 * pieces + 3;
  61. triangles[pieces * 24 + 11] = 4 * pieces + 2;
  62. mesh.triangles = triangles;
复制代码
绘制效果:




梯形柱的绘制逻辑就是只有一个扇面的弧形柱,因此在此就不再多加赘述。


球体
在Unity3D世界中,球体也是通过一个一个小三角形来表示的。怎么去生成一个由小三角形组成的球呢,常见的对于地球的划分是经纬度方法,如图:




这种分割球形表面的方法有个弊端,球靠近赤道两边的节点会很稀疏,靠近两极的节点会很密集,因此在这里不多做介绍。


比较好的做法是通过将正X面体每个面进行细分来拟近圆,对于正X面体的每个小三角面,按照每条边的中点将其拆分成四个三角形,拆分方式如图:






将拆分的新的四个三角形,贴合到球面上。然后依次迭代,就可以得到一个拟合不错的“球”。
其拆分逻辑如下:
  1. Vector3[] vectices = mesh.vertices;
  2. int[] triangles = mesh.triangles;
  3. int size1 = vectices.Length;
  4. int size2 = triangles.Length / 3;
  5. Vector3[] vectices2 = new Vector3[size1 + size2 * 3];
  6. int[] triangles2 = new int[size2 * 12];
  7. for (int i = 0; i < size1; ++i)
  8. {
  9.     vectices2[i] = vectices[i];
  10. }
  11. for (int i = 0; i < size2; ++i)
  12. {
  13.     Vector3 center1 = Mid(vectices[triangles[i * 3 + 1]], vectices[triangles[i * 3 + 2]]).normalized;
  14.     Vector3 center2 = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 2]]).normalized;
  15.     Vector3 center3 = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 1]]).normalized;
  16.     vectices2[size1 + i * 3] = center1;
  17.     vectices2[size1 + i * 3 + 1] = center2;
  18.     vectices2[size1 + i * 3 + 2] = center3;
  19.     triangles2[i * 12] = triangles[i * 3];
  20.     triangles2[i * 12 + 1] = size1 + i * 3 + 2;
  21.     triangles2[i * 12 + 2] = size1 + i * 3 + 1;
  22.     triangles2[i * 12 + 3] = size1 + i * 3 + 2;
  23.     triangles2[i * 12 + 4] = triangles[i * 3 + 1];
  24.     triangles2[i * 12 + 5] = size1 + i * 3;
  25.     triangles2[i * 12 + 6] = size1 + i * 3 + 1;
  26.     triangles2[i * 12 + 7] = size1 + i * 3;
  27.     triangles2[i * 12 + 8] = triangles[i * 3 + 2];
  28.     triangles2[i * 12 + 9] = size1 + i * 3;
  29.     triangles2[i * 12 + 10] = size1 + i * 3 + 1;
  30.     triangles2[i * 12 + 11] = size1 + i * 3 + 2;
  31. }
  32. mesh.Clear();
  33. mesh.vertices = vectices2;
  34. mesh.triangles = triangles2;
复制代码

各种类型的正面体拟合效果:
正4面体(4个基础三角面):
正8面体(8个基础三角面):
正6面体(正方体)(12个基础三角面):
正20面体:
看到这里有人可能就会问了,为什么没有讲正12面体,也就下面这位:




但是正12面体每个基本面是正五边形,再拆分成三角形的话至少会有36个基础三角形,因此不拿它来拟合球。


看到这,可能有人会问,小编讲了这么多到底要干嘛?
其实我就是突然有一天,想画一个海胆,就是下面这位:




但是Unity自带的几何结构很少,没法画。因此,自己动手写代码画。
要画这么一个海胆,首先要能画一个球,再将用于模拟球面的每个三角面通过下图中的变化来突出刺来。






代码逻辑如下:
  1. Vector3[] vectices = mesh.vertices;
  2. int[] triangles = mesh.triangles;
  3. int size1 = vectices.Length;
  4. int size2 = triangles.Length / 3;
  5. Vector3[] vectices2 = new Vector3[size1 + size2];
  6. int[] triangles2 = new int[size2 * 9];
  7. for (int i = 0; i < size1; ++i)
  8. {
  9.     vectices2[i] = vectices[i];
  10. }
  11. for (int i = 0; i < size2; ++i)
  12. {
  13.     Vector3 center = Mid(vectices[triangles[i * 3]], vectices[triangles[i * 3 + 1]], vectices[triangles[i * 3 + 2]]).normalized;
  14.     //vectices2[size1 + i] = center * (Random.Range(0f,1f)<0.6? 1: outterRadius);
  15.     vectices2[size1 + i] = center * outterRadius;
  16.     triangles2[i * 9] = triangles[i * 3];
  17.     triangles2[i * 9 + 1] = triangles[i * 3 + 1];
  18.     triangles2[i * 9 + 2] = size1 + i;
  19.     triangles2[i * 9 + 3] = triangles[i * 3];
  20.     triangles2[i * 9 + 4] = size1 + i;
  21.     triangles2[i * 9 + 5] = triangles[i * 3 + 2];
  22.     triangles2[i * 9 + 6] = size1 + i;
  23.     triangles2[i * 9 + 7] = triangles[i * 3 + 1];
  24.     triangles2[i * 9 + 8] = triangles[i * 3 + 2];
  25. }
  26. mesh.Clear();
  27. mesh.vertices = vectices2;
  28. mesh.triangles = triangles2;
复制代码
效果如下:
最后,可能有人想问,为什么要画这些?
怎么说呢,因为好玩,就是这么任性,有机会再跟大家分享一些其他的好玩的东西。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-20 09:07 , Processed in 0.089947 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表