和我一起学 Three.js【初级篇】:2. 掌握几何体

欢迎关注「前端乱步」公众号,我会在此分享 Web 开发技术,前沿科技与互联网资讯。

0. 系列文章合集

本系列第 6,7 章节支持微信公众号内付费观看,将在全系列文章点赞数+评论数 >= 500, 1000 时分别解锁发布。
  1. 《0. 总论》
  2. 《1. 搭建 3D 场景》
  3. 您当前在这里 《2. 掌握几何体》
  4. 《3. 掌握摄影机》
  5. 《4. 掌握纹理》
  6. 《5. 掌握材质》
  7. 《6. 掌握光照》
  8. 《7. 掌握阴影》
  9. 《8. 融会贯通,神功小成》(将于 2023.4.17 更新,敬请期待)

1. 什么是几何体(Geometry)

在 Three.js 的世界中,几何体(Geometry)由顶点(vertices),线,面组成,被用来定义物体的「形状」和「大小」

如果您想要在 3D 世界中「创造」某个物体,您需要首先确定这个物体「长什么样」?然后您就可以通过以下三种方式,创造出该物体:

  1. 使用 Three.js 提供的几何体对象
  2. 使用 Three.js 提供的 API 创建自定义几何体(_例如创建粒子动画_);
  3. 通过 3D 软件导入模型
在本篇文章中,我们将只介绍前两种创建几何体的方式,在后续很多场景中,您会经常通过这两种方式实现您想要的效果。

2. 几何体与 3D 物体的关系

我们在上一篇文章提到过,在 Three.js 中创建一个「3D 物体」必须通过实例化一个「网格对象(Mesh)」实现。几何体(Geometry),材质(Material)和网格对象(Mesh)三者的关系像是一个金字塔。

和我一起学 Three.js【初级篇】:2. 掌握几何体_第1张图片

「几何体」描述物体的形状和大小,「材质」描述物体的外观和质地,「网格对象」则将两者合并在一起,并提供使物体移动,旋转的能力。因此,要想当好 Web 3D 世界的造物主,您需要对这三个概念非常熟悉。

3. 学习几何体

您需要了解,Three.js 提供的所有现成的几何体,都继承自 BufferGeometry 对象,并且该对象也是 Three.js 为我们提供的创建自定义几何体的 API。因此,要学习几何体,我们要先从该对象说起。

3.1 BufferGeometry 对象

顾名思义,BufferGeometry 对象和「缓冲」相关,具体而言,该对象能够将几何体的相关数据(如顶点,UV,法线等)存入 GPU 的缓冲区(即显存),从而极大的提高 GPU 渲染性能与内存使用效率。

在计算机中,CPU 通常拥有更高的时钟速度和更大的缓存容量,但是并行计算的能力较差,难以处理大量的数据。而 GPU 的并行计算能力特别强,因此能够同时处理大量数据,但是缓存容量较小,时钟速度也较低。而针对 3D 渲染场景,大量的顶点数据需要在一次渲染中同时传递给 GPU 处理,因此对于这些数据的读写效率就成为了 GPU 渲染性能的瓶颈。因此,将顶点数据存储在缓冲区是现代 3D 渲染引擎中不可或缺的技术手段。

我们之前提到过,GPU 绘制几何体的过程,实际上是一个「读点」,「连线」和「结面」的过程。这里「读点」的「点」,指的就是几何体的「顶点」,它来源自开发者的输入,输入到哪里呢?在 Three.js 中,输入至 BufferGeometry 对象。

3.2 通过 BufferGeometry 对象绘制自定义几何体

为了绘制几何体,我们需要设定几何体的「顶点(vertices)」,每个顶点都至少由 3 个数字组成,表示其在空间直角坐标系内的位置。在一个颇具规模的 3D 世界中,我们有一定数量的几何体,意味着我们有大量的顶点数据需要 GPU 解析计算。因此,我们需要一种高效的数据结构存储顶点数据,在 JavaScript 世界中,我们通常使用 Float32Array 这一类型数组。

3.2.1 关于 Float32Array

Float32Array 是 JavaScript 提供的一种类型数组,用来存储 32 位浮点数(即表示 3.4028235 * 10 ^ 38 ~ 1.17549435 * 10 ^ -38 之间的任意数)。其函数签名为:

const array = new Float32Array(length | );

其中,length 参数表示数组的长度,它和普通数组的区别主要有如下两点:

  1. 数组中的每个元素的类型固定为采用 IEEE 754 标准表示的 32 位浮点数
  2. 它在实现方式上是一个「真正的数组」,即元素占据连续的内存空间

因此它是一种非常高效的数据结构,同时,由于其能够表示的数字范围较大,且可以表示的精度为小数点后 6 到 7 位,因此也非常适合用来存储顶点位置等数据。

3.2.2 自定义几何体

创建一个自定义几何体需要以下三个步骤:

  1. 使用数组定义几何体的顶点(使用 Float32Array 数据类型);
  2. 数组转换为一个 BufferAttribute 对象;
  3. 设置几何体的属性,并填充对应的值;

代码如下:

const geometry = new THREE.BufferGeometry()
const vertices = new Float32Array([
  -1.0, -1.0, 1.0,
   1.0, -1.0, 1.0,
   1.0,  1.0, 1.0,
   1.0,  1.0, 1.0,
  -1.0,  1.0, 1.0,
  -1.0, -1.0, 1.0,
]); // ①
const positionAttribute = new THREE.BufferAttribute(vertices, 3); // ②

geometry.setAttribute("position", positionAttribute) // ③

还记得我们上一章讲过的空间直角坐标系吗?在上面的示例代码中,我们定义了 6 个顶点,分别指定了每个顶点在 xyz 轴上的坐标。
还记得我们说过 BufferGeometry 对象的厉害之处在于能够利用 GPU 缓存提升渲染性能吗?这实际上是 ② 处的 BufferAttribute 函数做到的,它用于将 JavaScript 数组中的数据转换为二进制数据,并存储至 GPU 缓存。该函数接收三个参数:

  1. array:一个类型数组(TypedArray),用于存储数据;
  2. itemSize:元素大小,通常情况下指的是每个顶点需要占用的字节数,例如,每个顶点由三个浮点数(x,y,z)组成,则元素大小为 3,如果每个顶点还有两个浮点数(u,v)表示纹理坐标,则元素大小为 5;
  3. normalized:表示是否归一化,如果值为 true 表示数字会被始终限制在 0 到 1 范围内(这主要是方便数据之间的比较和计算,我们目前不用关注它);

至此,我们替换掉上一篇文章中的 geometry 变量,并开启材质的 wireframe: true 配置,可以得到如下的效果:

和我一起学 Three.js【初级篇】:2. 掌握几何体_第2张图片

恭喜您 !我们刚刚完成了第一个自定义的 3D 图像!

3.2.3 思考题

  1. 不知道您是否发现,我们刚才创建了 6 个顶点的坐标,但是得到的却是一个矩形图像,请您思考为什么是 6 个顶点而不是 4 个?
  2. 当您关闭 wireframe 配置时,您会发现矩形旋转至一定角度后会突然消失,这是为什么?又该如何解决?

欢迎在评论区留言和我讨论 !

3.3 Three.js 提供的几何体

在明白如何通过 BufferGeometry 对象绘制自定义图形后,我们就容易理解 Three.js 所提供的几何体对象不过是基于 BufferGeometry 对象之上的一种封装,因此,我们学习这些几何体的思路是:

  1. 了解 Three.js 提供了哪些现成的几何体:这有助于我们在需要创建物体时,能快速找到对应的几何体;
  2. 了解几何体的共性配置:这有利于我们开发时有一个基本的思路,具体到某个几何体的运用,可以再查询文档寻找解答;

3.3.1 Three.js 提供的几何体

Three.js 目前一共提供了 21 种基础的几何体供开发者使用,我当然不会为您罗列每种几何体的具体属性,您大可以通过官网查询您感兴趣的几何体,在本章节中,您只需要知道在 Three.js 中「都有哪些」几何体可以使用即可。

Three.js 的官网非常棒,提供了交互式的文档,您可以通过调整参数立即看到立体图形的变化,请您务必亲自前往尝试!
  1. BoxGeometry:创建立方体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第3张图片

  1. CapsuleGeometry:创建一个「胶囊」体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第4张图片

  1. CircleGeometry:创建一个圆形或扇形;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第5张图片

  1. ConeGeometry:创建一个椎体,或部分椎体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第6张图片

  1. CylinderGeometry:创建一个柱体,或部分柱体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第7张图片

  1. DodecahedronGeometry:创建一个十二面体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第8张图片

  1. EdgesGeometry:该对象用于创建一个只有「边」的几何体,与需要使用 Mesh 连接几何体和材质不同,它需要使用一个特殊的对象 LineSegemnts
const geometry = new THREE.BoxGeometry( 100, 100, 100 );
const edges = new THREE.EdgesGeometry( geometry );
const line = new THREE.LineSegments( edges, new THREE.LineBasicMaterial( { color: 0xffffff } ) );
scene.add( line );

官网提供了一个非常酷的 Demo 用于说明该对象的效果:

和我一起学 Three.js【初级篇】:2. 掌握几何体_第9张图片

  1. ExtrudeGeometry:根据路径创建一个受挤压的多边体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第10张图片

  1. IcosahedronGeometry:创建一个二十面体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第11张图片

  1. LatheGeometry: 创建一个类似花瓶的形状;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第12张图片

  1. OctahedronGeometry:创建一个八面体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第13张图片

  1. PlaneGeometry:创建一个平面;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第14张图片

  1. PolyhedronGeometry:和 BufferGeometry 类似,通过接收顶点数据与面数据自定义几何体;
  2. RingGeometry: 创建一个环形,或部分环形;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第15张图片

  1. ShapeGeometry:根据路径创建一个多边形;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第16张图片

  1. SphereGeometry:创建一个球体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第17张图片

  1. TetrahedronGeometry:创建一个四面体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第18张图片

  1. TorusGeometry:创建一个环体,或部分环体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第19张图片

  1. TorusKnotGeometry:创建一个结体;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第20张图片

  1. TubeGeometry:根据路径创建管道;

和我一起学 Three.js【初级篇】:2. 掌握几何体_第21张图片

  1. WireframeGeometry:类似 EdgesGeometry,不过生成的将是线框体;

3.3.2 几何体的共性配置

以 BoxGeometry 为例,几何体的公共属性可以分为两类:

  1. 定义几何体的尺寸(长度,宽度,深度);
  2. 定义不同方向上的分段数:WebGL 只能绘制三角形,分段数决定着一个面上的三角形数量(当分段数为 2 时,意味着一个面由 4 个三角形组成),一个面上的三角形数量越多,意味着这个面越平滑,效果会越好,但同时也意味着 GPU 要进行更多的计算,消耗更多的性能;

例如我们创建一个长宽高为 5 的立方体,将宽度方向上的分段数设置为 3,会得到这样的效果:

和我一起学 Three.js【初级篇】:2. 掌握几何体_第22张图片

4. 复杂的几何体:立体文字

除了之前展示的简单的几何体外,Three.js 还提供了一些更加复杂的几何形状,例如 「凸包几何体(DecalGeometry)」,「贴花几何体(ParametricGeometry)」,「参数化缓冲几何体(ConvexGeometry)」 和接下来将要介绍的一种比较常用的几何体「立体文字(TextGeometry」。

不过在此之前,让我们稍微改进一下我们的脚本引用方式:

4.1 使用 Vite 搭建开发环境

Three.js 并没有将立体文字相关的模块放置在核心包内,这意味着我们之前使用

你可能感兴趣的:(和我一起学 Three.js【初级篇】:2. 掌握几何体)