05.Three.js基础之图元

05 Three.js基础之图元

图元(Primitives)介绍

Primitive这个单词在百度翻译里的解释是:原始的、远古的

Primitive的复数即为Primitives

所谓 图元 就是Three.js内置的一些基础3D形状,例如 立方体、球体、圆锥体等。

有些文章或教程,包括Three.js官方文档,都是将 图元 称呼为 几何体。

但是在本文中,我们依然先使用 图元 这个称呼。

请注意,内置的图元并不一定都是3维体,也可以是2维的,例如 平面圆。

例如之前写的HelloThreejs示例中,就使用BoxGeometry来创建立方体。

虽然我们一直称呼为 立方体,但实际在Three.js中称呼其为 盒子(box)

在本文后面的一些文章中,也会将图元称呼为几何体。




以下内容更新于2021.07.20

之前在写这篇文章的时候,还没有学习过图形学,所以对于一些名词的概念解释都是想当然,甚至是胡说八道,胡言乱语。


顶点、图元、片元、图像 他们之间的递进关系

顶点:就是在3D世界中某一个具体的点,即点的位置(x,y,z)。除了位置信息,还可能包括 点的颜色或其他信息。

请注意,这些顶点位置都是相对的,依次是 局部位置、全局位置、镜头位置等等。

在管线渲染流程中,顶点处理模块的作用就是负责将顶点进行坐标转换。


图元:由若干个顶点构成的一组数据,用于构建或描述某种 二维或三维物体。

图元 中的 “元” 字可以理解为 “原始”的原,也就是说使用最少的点来描述一个物体的空间信息。

只有1个顶点依然可以是 图元,它只能表示某一个 点。例如 自动驾驶中扫描周围环境得到的3D点云数据就是由 一个一个小点 组成的。

如果是2个顶点,则可以表示出是一个 线段,同时2个点也可以表示出一个长方体。

2个顶点信息就可以表述出1个长方体? 没错的,你可以想象成 这2个点分别是长方体的 斜对角线上的2个点,例如在three.js中 包装盒Box3就只有2个点的信息:坐标最大的点、坐标最小的点

3个顶点,则可以表示出一个 三角形,同时3个点也可以表示出一个圆。

至于为什么3个顶点 可以表示出一个圆,你可以自己搜索或脑补。

请注意:图元依然为一堆顶点数据,而不是图像数据。

再次补充:假设一个物体有一部分不在显示范围之内,那么webgl会通过 裁切体(由镜头视椎体决定的)对物体进行裁切,只将需要渲染的部分进行渲染,而裁切得到的内容则会重新计算,得到一个新的图元。


关于图元的额外补充:

实际上在openglwebgl的概念中,图元分为2种:

  1. 几何图元:使用顶点、线段、三角形、曲线等等 用于描述物体 “几何轮廓” 。

    几何图元可以进行空间转换,例如平移,旋转,缩放等操作

  2. 图像图元:图像图元又被称为 光栅图元。使用像素阵列 用于直观储存 “图片信息”。

    通过描述就应该知道,实际上所谓的 图像图元就是材质中的纹理贴图。

    图像图元不可进行空间转换


几何图元 经过变换、投影、光栅化后,到达片元操作环节的。

图像图元(也就是纹理)是直接到达片元操作环节的。

最终在片元操作环节,几何图元+图像图元,最终合成得到物体图像。

当然还需要其他操作,例如光线反射等


而实际中,我们通常不会使用 “图像图元” 这个名词,而是使用 “纹理”。

所以在本文或者一些常见的教程中“图元” 往往都是指 “几何图元”。


片元:包含图像颜色、位置、深度的信息数据。你可以把片元简单理解为 “未完全加工完成的图像数据”。

3D图形管线渲染的流程中,经过裁切处理模块和图元组装模块之后,下一步经过光栅化处理模块,会将需要渲染的图元由一堆顶点数据转化为一堆图像数据。

请注意:片元已经不再是顶点数据,而是图像数据了,只不过这些图像数据是为完全加工完成,可以最终显示在屏幕上的图像数据。


图像:由 片元 经过片元处理模块,得到的最终图像数据。就是3D渲染输出到屏幕上的显示结果。

片元数据经过处理,用来更新缓存帧 上的像素,最终 缓存帧 上的结果就是最终渲染出的图像。

请注意:图像是由一个个像素构成。


以上内容为 图形学 中的相关知识,但是在本文中讲解的 “Three.js中内置的图元” 是Three.js为了帮助我们快速创建一些常见物体所提供的JS类。

所以一定要理解清楚,本文讲解的 图元 和实际图形学中的图元 是有差异。

再次重申一遍:本文讲解的 图元 实际上是JS的类,帮助我们快速创建某些形状的 顶点数据。

一组相关的顶点数据才是图形学中的图元。


以上内容更新于2021.07.20



3D模型的补充说明

内置的图元,都是一些基础的形状,相对简单,但也可以组合成相对复杂的3D场景。

但对于绝大多数3D应用来说,通常流程是:

  1. 在专业的3D软件 例如May、Blender、C4D中创建模型
  2. 将创建好的模型导出成模型文件,文件格式为.obj.gltf
  3. Three.js加载模型文件,然后开始后续操作

我们先不讨论如何导出或加载模型,那些会在后续操作中讲解。

此刻还是回归到默认的 图元 学习中。

图元的种类

图元汇总

图元种类(按英文首字母排序) 图元构造函数
盒子(Box) BoxBufferGeometry、BoxGeometry
平面圆(Circle) CircleBufferGeometry、CircleGeometry
锥形(Cone) ConeBufferGeometry、ConeGeometry
圆柱(Cylinder) CylinderBufferGeometry、CylinderGeometry
十二面体(Dodecahedron) DodecahedronBufferGeometry、DodecahedronGeometry
受挤压的2D形状(Extrude) ExtrudeBufferGeometry、ExtrudeGeometry
二十面体(Icosahedron) IcosahedronBufferGeometry、IcosahedronGeometry
由线旋转形成的形状(Lathe) LatheBufferGeometry、LatheGeometry
八面体(Octahedron) OctahedronBufferGeometry、OctahedronGeometry
由函数生成的形状(Parametric) ParametricBufferGeometry、ParametriceGeometry
2D平面矩形(Plane) PlaneBufferGeometry、PlaneGeometry
多面体(Polyhedron) PolyhedronBufferGeometry、PolyhedronGeometry
环形/孔形(Ring) RingBufferGeometry、RingGeometry
2D形状(Shape) ShapeBufferGeometry、ShapeGeometry
球体(Sphere) SphereBufferGeometry、SphereGeometry
四面体(Tetrahedron) TetrahedronBufferGeometry、TetrahedronGeometry
3D文字(Text) TextBufferGeometry、TextGeometry
环形体(Torus) TorusBufferGeometry、TorusGeometry
环形结(TorusKnot) TorusKnotBufferGeometry、TorusKnotGeometry
管道/管状(Tube) TubeBufferGeometry、TubeGeometry
几何体的所有边缘(Edges) EdgesGeometry
线框图(Wireframe) WireframeGeometry

一共有22种内置的图元。

上面表格中关于图元的中文名字,有些是我根据含义自己编的,我已经尽量靠近英文原意。
不同文章或教程可能对同一图元的称呼略微不同。

不要被上面那么多图元吓到,事实上他们并不复杂,并且多数情况下我们也用不到。

当需要用到了,只需要去查阅Three.js文档即可。


以下内容更新于2021.11.27

特别补充说明:内置的图元实际上也是变化多端的!

为什么这么说呢?

例如:圆柱(Cylinder),字面上它是用于创建圆柱体的,但是实际上认真阅读官方文档你会发现是这样描述它的构造函数的

CylinderGeometry官方文档:https://threejs.org/docs/index.html#api/zh/geometries/CylinderGeometry

CylinderGeometry(radiusTop : Float, radiusBottom : Float, height : Float, radialSegments : Integer, heightSegments : Integer, openEnded : Boolean, thetaStart : Float, thetaLength : Float)
radiusTop — 圆柱的顶部半径,默认值是1。
radiusBottom — 圆柱的底部半径,默认值是1。
height — 圆柱的高度,默认值是1。
radialSegments — 圆柱侧面周围的分段数,默认为8。
heightSegments — 圆柱侧面沿着其高度的分段数,默认值为1。
openEnded — 一个Boolean值,指明该圆锥的底面是开放的还是封顶的。默认值为false,即其底面默认是封顶的。
thetaStart — 第一个分段的起始角度,默认为0。(three o'clock position)
thetaLength — 圆柱底面圆扇区的中心角,通常被称为“θ”(西塔)。默认值是2*Pi,这使其成为一个完整的圆柱。

请注意最后的2个参数:

  1. thetaStart(默认值为0)
  2. thetaLength(默认值为2*Pi)

也就是说,你不修改这2个默认值,那么默认创建出的是一个完整的圆柱体,但是假设你修改了这2个值,比如 将thetaLength修改成0.3*Pi (54°),那么最终将创建出一个 夹角为54°扇形()

如果感兴趣,可以看一下我发布的这个项目,由数据生成3D饼图:https://github.com/puxiao/pie-3d

提醒:最好你在看完本系列教程后(不仅是本小节),再去看上面提到的pie-3d

通过上面对CylinderGeometry的描述,我们可以知道Three.js默认自带的图元实际上是可以产生很多变化的,得到的不一定仅仅是图元的 “字面” 物体。

以上内容更新于2021.11.27


BufferGeometryGeometry的区别

从上面的图元表格中不难发现,除了EdgesWireframeGeometry以外,其他图元的构造函数都是成对出现的。

虽然EdgesGeometryWireframeGeometry名字中并未出现 “Buffer”,但和其他所有包含 “Buffer” 字样的图元一样,他们都继承于BufferGeometry

差异之处 BufferGeometry Geometry
运算、渲染所消耗的性能
GPU渲染 支持 不支持,
需要Three.js内部转化为BufferGeometry后才支持
修改灵活度、可自定义程度 不高
添加新顶点 不支持 支持

简单来说就是:

  • BufferGeometry可自定义地方比较少,但性能高
  • Geometry可自定义地方比较多,但性能低一些

所有的Geometry对象最终都会被Three.js转化为BufferGeometry对象,然后再进行渲染。


以下内容更新于2021.11.27

上面关于BufferGeometryGeometry的区别这段话已经过时了,因为在较新的Three.js版本中已经将Geometry从核心类中移除。

目前你接触到的都应该只有BufferGeometry

一些心里话:

首先非常抱歉得说一句:一年前在写本系列文章时,我是一个对图形学一无所知,对Three.js好奇但又非常小白的人,我是一边学习一边写下本系列文章的。

所以本系列教程绝对不是好的教程——假设Three.js是一座大山的话,而我是站在山脚下向你讲述上山道路的那个人,但我自己也未曾上过这座山。

随着我对图形学、webgl、Three.js、Canvas的一些认知提升,我深深觉得想要写出好教程,一定要站在更高的维度,拥有更高的视野才可以更好向别人指明方向,写出好教程。

但是本教程对于那些完全小白,完全对Three.js一无所知的人,多少还是有些帮助的(尽管我指明的道路并不是最佳道路)很感谢那些Star本教程的人

即使看完全部的本教程,那么最多你也仅仅算是学会了个皮毛,简单入门而已,真正复杂难的是 图形学 中的一些知识点,例如 向量,矩阵,齐次坐标,点乘,叉乘,球极坐标,当然最复杂的莫过于 自定义渲染器(shader)

关于3D技术栈,虽然不够严谨,但是大体上可以这样表述:图形学(CG) > OpenGL > OpenGL ES 2.0 > WebGL > Three.js

所以Three.js仅仅是web 3D最基础,表层的知识技术栈,想要深入学习,你会发现这是一条几乎不到头的道路,学秃。

图形学就是那种 从入门到放弃 的知识体系。

但是别灰心,我们实际上并不会真正需要那么深入高深的,学会Three.js可以做一些基础的 网页3D还是会比一般前端要显得厉害很多。


BufferGeometry的重要知识点:position、normal、uv

先普及个基础知识:在3DVector3既可以表示一个 三维坐标,也可以表示一个三维方向。

一个完整的BufferGeometry是由若干个 点(Vector3)构成的:

上面提到的 点 准确说应该是3维 点坐标,对应的是Vector3 :https://threejs.org/docs/index.html#api/zh/math/Vector3

下面的知识实际上是针对 图形学 和OpenGL的。

  1. position:坐标(每个坐标就是一个vector3,由3个数字组成),所有的坐标就是组成该BufferGeometry的所有 点 的信息(对于底层的BufferGeometry而言3维点的(x,y,z)坐标是分开存储值的)

    这就是Three.js针对webgl进行的封装,实际上我们平时更多时候都使用的是Vector3,而不是具体的3个值。

  2. normal:法线(每个法线就是一个vector3,由3个数字组成),用于存储每个3D坐标点的朝向,用于计算 反光。

  3. uv:纹理映射坐标(每个uv就是一个vector2,由2个数字组成),用于存储每个3D坐标点对应渲染纹理时对应的 位置点信息,用于计算 贴图。

    对于纹理而言,它都是 二维的平面,因此uv的值对应的是Vector2,由x,y 2个坐标值组成,且每个值的取值范围都是0 - 1

    你可以简单把0 - 1理解成0% - 100%,对应的是一个百分比的值。

通过上面的讲述,我们大致可以作出以下结论,如果我们自定义一个BufferGeometry,那么:

对于初学者而言几乎不需要、也做不到 可以 自定义BufferGeometry这一步,我这里只是超前提一下。

  1. 假设这个BufferGeometry不需要考虑 反光 和 纹理贴图,那么它只需要拥有(设置) positon就可以了。

    this.setAttribute(‘position’, new BufferAttribute(this._vertices, 3))

  2. 假设这个BufferGeometry需要考虑反光,但不需要考虑纹理贴图,那么它需要设置postionnormal

    this.setAttribute(‘position’, new BufferAttribute(this._vertices, 3))

    this.setAttribute(’normal’, new BufferAttribute(this._normals, 3))

  3. 假设这个BufferGeometry需要考虑反光和纹理贴图,那么它的postion 、normal、uv都需要设置。

    this.setAttribute(‘position’, new BufferAttribute(this._vertices, 3))

    this.setAttribute(’normal’, new BufferAttribute(this._normals, 3))

    this.setAttribute(‘uv’, new BufferAttribute(this._uvs, 2))

  4. 特别强调,上面提到的position是一个BufferGeomerty所有点信息的集合,它并不是Mesh(网格,3D物体)的 位置信息。

如果你理解不了我说的这段话,完全没有关系,忽略这段我补充的知识点,我也是学习Three.js1年后才明白的。对于现在的你而言不理解是正常的。

忽略我上面的这段话,继续本教程后面的学习吧。

以上内容更新于2021.11.27

图元理论上的知识就先讲到这里,在下一节中,会编写一些图元示例。

上一页
下一页