🛎 本文为《和我一起学 Three.js 系列》高级篇的第三篇文章,文中的示例代码基于上一篇文章中的代码进行相应的扩大,请确保您曾经浏览上一篇文章,并和我一起实现了一个小程序:展现一个立方体。
1. 什么是几何体(Geometry)
在 Three.js 的世界中,几何体(Geometry)由顶点(vertices),线,面组成,被用来定义物体的「形态」和「大小」。
如果您想要在 3D 世界中「发明」某个物体,您须要首先确定这个物体「长什么样」?而后您就能够通过以下三种形式,发明出该物体:
- 应用 Three.js 提供的几何体对象;
- 应用 Three.js 提供的 API 创立自定义几何体(_例如创立粒子动画_);
- 通过 3D 软件导入模型;
🛎 在本篇文章中,咱们将只介绍前两种创立几何体的形式,在后续很多场景中,您会常常通过这两种形式实现您想要的成果。
2. 几何体与 3D 物体的关系
咱们在上一篇文章提到过,在 Three.js 中创立一个「3D 物体」必须通过实例化一个「网格对象(Mesh)」实现。几何体(Geometry),材质(Material)和网格对象(Mesh)三者的关系像是一个金字塔。
「几何体」形容物体的形态和大小,「材质」形容物体的外观和质地,「网格对象」则将两者合并在一起,并提供使物体挪动,旋转的能力。因而,要想当好 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 | <Array>);
其中,length
参数示意数组的长度,它和一般数组的区别次要有如下两点:
- 数组中的每个元素的类型固定为采纳 IEEE 754 规范示意的 32 位浮点数;
- 它在实现形式上是一个「真正的数组」,即元素占据间断的内存空间;
因而它是一种十分高效的数据结构,同时,因为其可能示意的数字范畴较大,且能够示意的精度为小数点后 6 到 7 位,因而也非常适合用来存储顶点地位等数据。
3.2.2 自定义几何体
创立一个自定义几何体须要以下三个步骤:
- 应用数组定义几何体的顶点(应用 Float32Array 数据类型);
- 将数组转换为一个 BufferAttribute 对象;
- 设置几何体的属性,并填充对应的值;
代码如下:
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 个顶点,别离指定了每个顶点在 x
,y
,z
轴上的坐标。
还记得咱们说过 BufferGeometry 对象的厉害之处在于可能利用 GPU 缓存晋升渲染性能吗?这实际上是 ② 处的 BufferAttribute
函数做到的,它用于将 JavaScript 数组中的数据转换为二进制数据,并存储至 GPU 缓存。该函数接管三个参数:
array
:一个类型数组(TypedArray),用于存储数据;itemSize
:元素大小,通常状况下指的是每个顶点须要占用的字节数,例如,每个顶点由三个浮点数(x,y,z)组成,则元素大小为 3,如果每个顶点还有两个浮点数(u,v)示意纹理坐标,则元素大小为 5;normalized
:示意是否归一化,如果值为true
示意数字会被始终限度在 0 到 1 范畴内(这次要是不便数据之间的比拟和计算,咱们目前不必关注它);
至此,咱们替换掉上一篇文章中的 geometry 变量,并开启材质的 wireframe: true
配置,能够失去如下的成果:
祝贺您 💐 !咱们刚刚实现了第一个自定义的 3D 图像!
🤔 3.2.3 思考题
- 不晓得您是否发现,咱们方才创立了 6 个顶点的坐标,然而失去的却是一个矩形图像,请您思考为什么是 6 个顶点而不是 4 个?
- 当您敞开
wireframe
配置时,您会发现矩形旋转至肯定角度后会忽然隐没,这是为什么?又该如何解决?
欢送在评论区留言和我探讨 👋!
3.3 Three.js 提供的几何体
在明确如何通过 BufferGeometry 对象绘制自定义图形后,咱们就容易了解 Three.js 所提供的几何体对象不过是基于 BufferGeometry 对象之上的一种封装,因而,咱们学习这些几何体的思路是:
- 理解 Three.js 提供了哪些现成的几何体:这有助于咱们在须要创立物体时,能疾速找到对应的几何体;
- 理解几何体的共性配置:这有利于咱们开发时有一个根本的思路,具体到某个几何体的使用,能够再查问文档寻找解答;
3.3.1 Three.js 提供的几何体
Three.js 目前一共提供了 21 种根底的几何体供开发者应用,我当然不会为您列举每种几何体的具体属性,您大能够通过官网查问您感兴趣的几何体,在本章节中,您只须要晓得在 Three.js 中「都有哪些」几何体能够应用即可。
🛎 Three.js 的官网十分棒,提供了交互式的文档,您能够通过调整参数立刻看到平面图形的变动,请您务必亲自返回尝试!
- BoxGeometry:创立立方体;
- CapsuleGeometry:创立一个「胶囊」体;
- CircleGeometry:创立一个圆形或扇形;
- ConeGeometry:创立一个椎体,或局部椎体;
- CylinderGeometry:创立一个柱体,或局部柱体;
- DodecahedronGeometry:创立一个十二面体;
- 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 用于阐明该对象的成果:
- ExtrudeGeometry:依据门路创立一个受挤压的多边体;
- IcosahedronGeometry:创立一个二十面体;
- LatheGeometry: 创立一个相似花瓶的形态;
- OctahedronGeometry:创立一个八面体;
- PlaneGeometry:创立一个立体;
- PolyhedronGeometry:和 BufferGeometry 相似,通过接管顶点数据与面数据自定义几何体;
- RingGeometry: 创立一个环形,或局部环形;
- ShapeGeometry:依据门路创立一个多边形;
- SphereGeometry:创立一个球体;
- TetrahedronGeometry:创立一个四面体;
- TorusGeometry:创立一个环体,或局部环体;
- TorusKnotGeometry:创立一个结体;
- TubeGeometry:依据门路创立管道;
- WireframeGeometry:相似 EdgesGeometry,不过生成的将是线框体;
3.3.2 几何体的共性配置
以 BoxGeometry 为例,几何体的公共属性能够分为两类:
- 定义几何体的尺寸(长度,宽度,深度);
- 定义不同方向上的分段数:WebGL 只能绘制三角形,分段数决定着一个面上的三角形数量(当分段数为 2 时,意味着一个面由 4 个三角形组成),一个面上的三角形数量越多,意味着这个面越平滑,成果会越好,但同时也意味着 GPU 要进行更多的计算,耗费更多的性能;
例如咱们创立一个长宽高为 5 的立方体,将宽度方向上的分段数设置为 3,会失去这样的成果:
4. 简单的几何体:平面文字
除了之前展现的简略的几何体外,Three.js 还提供了一些更加简单的几何形态,例如 「凸包几何体(DecalGeometry)」,「贴花几何体(ParametricGeometry)」,「参数化缓冲几何体(ConvexGeometry)」 和接下来将要介绍的一种比拟罕用的几何体「平面文字(TextGeometry)」。
不过在此之前,让咱们略微改良一下咱们的脚本援用形式:
4.1 应用 Vite 搭建开发环境
Three.js 并没有将平面文字相干的模块搁置在外围包内,这意味着咱们之前应用 <script>
标签导入 three.js 脚本,从全局对象 THREE
中获取对象的办法曾经行不通了。为此,咱们须要搭建一个古代 Web 开发环境!
🛎 我抉择应用 Vite 搭建开发环境,因为它简直不须要任何配置就能够搭建一个能满足咱们须要的开发环境,因为本篇文章的主题并不是 Vite,因而您若须要本人摸索 Vite 其余您感兴趣的中央!
首先,咱们执行如下命令创立一个 Vite 我的项目:
$ npm create vite@latest hello-three-js -- --template vanilla
我的项目创立胜利后,咱们执行上面的命令:
$ cd hello-three-js
$ npm install
$ npm install three # 引入 three.js 库
$ npm run dev
功败垂成!当您拜访 [http://localhost:5173](http://localhost:5173/)
地址时,您应该能够看到 Vite 的默认启动界面:
而后,您须要在 index.html
文件中创立 <canvas id="webgl"></canvas>
标签,并将之前咱们所有的代码粘贴至 main.js
文件中笼罩原有代码。
当初,咱们胜利领有了一个现代化的前端开发环境!
📌 在之后的文章中,咱们会始终应用该环境编写 Demo 帮忙咱们深刻了解 Three.js。
4.2 创立平面文字
TextGeometry 的应用办法与之前简略的几何体略有不同,因为咱们须要申明所需渲染文字的「字体」。这须要应用一个新的对象 fontLoader
,该对象并不能从 Three.js 中间接导入,而是要通过上面的形式:
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
应用 fontLoader
的办法如下:
const fontLoader = new FontLoader()
fontLoader.load(
'<facetype>',
(font) =>
{
console.log('loaded')
}
)
您能够通过 Facetype.js 将一个字体转换为一个 THREE.Font 对象的实例。Three.js 也在 /examples/fonts
目录下提供了一些可选的字体:
🛎 您须要将对应的字体文件拷贝到您的
fonts/
目录中。
在字体加载胜利后,咱们的配角就能够退场了:
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
TextGeometry
接管两个参数:
- 文本内容;
- 平面字配置;
具体代码如下:
fontLoader.load(
'/fonts/helvetiker_regular.typeface.json',
(font) =>
{
const textGeometry = new TextGeometry(
'Hello Three.js',
{
font: font,
size: 0.5, // 示意文本大小,即字体高度,默认为 100
height: 0.2, // 示意文本厚度,默认为 50
curveSegments: 12, // 示意圆角段数,默认为 12
bevelEnabled: true,// 示意是否启用斜角,默认为 false
bevelThickness: 0.01, // 示意斜角的深度,默认为 10
bevelSize: 0.01, // 示意斜角的高度,默认为 8
bevelOffset: 0, // 示意斜角绝对于文本的偏移量,默认为 0
bevelSegments: 1 // 示意斜角的段数,默认为 3
}
)
const textMaterial = new THREE.MeshBasicMaterial()
const text = new THREE.Mesh(textGeometry, textMaterial)
scene.add(text)
}
)
当初,咱们胜利将文字增加至咱们的场景中!一次真正的 Hello World!
🛎 对 GPU 而言,渲染文字意味着更大的性能开销,因而应该尽量避免对文字做过多的计算,一般来说,通过调小
curveSegments
和bevelSegements
的值是晋升渲染性能的好办法。
4.2.1 🤔 思考题
- 之前咱们创立 3D 物体时,咱们都会让其旋转起来,让场景变得更乏味。您晓得如何让咱们的字体旋转起来吗?
欢送在评论区留下您的答案 👋!
4.3 如何居中文字
目前为止,咱们胜利在场景中创立了平面文字,但很多时候,咱们须要将文字居中。要实现这一成果,其原理相似于咱们在 CSS 中的一种居中解决方案:
.div {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
咱们现将对象的左上点放在屏幕的核心,而后在让对象别离向上,左方向「回收」本身一半的宽高间隔。侥幸的是,在 Three.js 中,平面文字的左上角人造就位于画布的核心地位,所以咱们只须要取得文字的宽高,进行位移。
默认状况下,Three.js 会抉择应用球形边界包裹物体,咱们须要手动将其转换为盒型边界:
textGeometry.computeBoundingBox()
通过 textGeometry.boundingBox
属性,咱们能够取得物体在 x
,y
,z
轴上的长度,这里有点特地的是,这三个长度挂载在 min
和 max
属性上,这意味着咱们须要这样居中平面文字:
textGeometry.translate(
- textGeometry.boundingBox.max.x * 0.5,
- textGeometry.boundingBox.max.y * 0.5,
- textGeometry.boundingBox.max.z * 0.5
)
功败垂成!
5. 总结
在本篇文章中,我向您具体介绍了 Three.js 中一个要害概念:几何体(Geometry)。并说明了它和 3D 世界物品的关系。我还通过介绍自定义几何体的用法向您阐明了 GPU 绘制几何体的原理,并向您展现了所有 Three.js 反对的个别几何体,以及一个非凡的几何体:平面文字。心愿您当初对几何体这个概念曾经有了粗浅的了解,在下一篇文章中,我会为您介绍「摄影机(Camera)」这个十分重要的概念,它能够使咱们可交互的察看几何体。
心愿您能放弃学习的热度,下周见 👋。
6. 参考资料
- Three.js 官网;
- Vite 官网;
7. 应用到的工具
- 截屏:Xnip;
- 插图:OmniGraffle;
- 屏幕录制:QuickTime Player;
- 视频转 GIF 图片:iLoveIMG;
8. 💰 反对创作
您有很多形式能够表白您喜爱这篇文章,并违心反对我继续创作,例如:
- 点击各类平台「喜爱」按钮;
- 将文章转发在各类您喜爱的平台,并为它写一份简短的举荐语;
- 在评论区留言;
- 关注我的集体公众号「前端乱步」;
- …
无论您抉择哪一项,我都会因为您的观赏而感到愉悦。
发表回复