Object3D、Geometry和BufferAttribute的关系
1. Geometries
从Three.js提供的内置Geometry来看,可以分为以下几类:
-
想要画一个3D几何体。例:
BoxGeometry -
想要画一个2D的平面。例:
PlaneGeometry -
想要画一组线。例:
EdgesGeometry -
想要画一组点。可以通过
BufferGeometry定义点的位置
在上面提到的,四类中,3D几何体和2D平面,在本质上都是通过平面三角形组合出来的图形。
在我们上面提到的这一系列东西,最主要的就是要提供顶点的位置信息。所以,Geometry就是一组顶点的属性。顶点的位置position,就是顶点的属性之一。常见的属性还包括顶点的法向量normal,用来建立光照的反射模型...
从面向对象的角度描述一个Geometry,我们很容易抽象它:
class AbstractGeoemetry {
private positions: Array<Vector3>;
private normals : Array<Vector3>;
// 其他属性...
}
但three.js在实际的视线中,并不是这样抽象的。对于一组position,可以通过[{x:1, y:1, z:1}, { x:2, y:2, z:2 }] 表示,但是考虑到,在向GPU发送数据时,数据就是通过一个纯数字的数组发送的,在存储时,就直接通过一个[1, 1, 1, 2, 2, 2]来存储,存储一组属性的类,three.js把他抽象成了BufferAtrribute。
2. BufferAttribute
深入BufferAttribute,它最重要的成员就是array: TypedArray,他就是用来给GPU发送数据的数字数组。
从直觉上来说,对数字数组中的顶点属性进行处理时,会有很多额外的计算,比如说,第二的顶点的位置,存在array[3]``array[4]``array[5],而不是array[1]。直接处理会很麻烦,所以,three.js抽象出BufferAttribute就是解决这个痛点的。
在创建BufferAttribute时,需要一个itemSize参数,这个就是用来指定array中,”几个元素“表示一个顶点。对于上面的例子,每个position需要x``y``z三个分量,所以itemSize就是3。
BufferAttribute提供了一系列方法,按”顶点“来处理数据,关注点是每一个顶点,而不是数组中某一个下标的某个值。
3. Object3D
有了上面的两部分内容,就可以定义一个被“画”的对象,在Geometry的一个实例中,包含了画一个对象的所有属性。但是问题来了:GPU怎么知道怎么解释positions中的值,一个一个的取顶点位置,把它画成一个一个的点;还是两个两个取,把它画成一条一条的线;亦或是三个三个取,画成一个一个三角形。
这时候,就要Object3D出场了,three.js也帮我们抽象好了很多内置的类,帮我们解决上面的问题:
-
Mesh:画三角形 -
Line、LineLoop、LineSegments:画线 -
Points:画点
也就是说,某一个Geometry,都需要一个对应的Object3D才能正确构建。比如说EdgesGeometry,就需要创建LineSegment对象才能够正确的画出来,否则画出来的图形就不是我们想要的。下面就举这个错误的例子:
4. 举例
查阅官方文档可以看到,在给出的例子中,想要画一个EdgesGeomtry,需要使用LineSegment,但是我们这里错误的使用了Mesh。
const scene = new THREE.Scene();
const cylinderGeometry = new THREE.CylinderGeometry( 0, 2, 6, 12 );
const cylinderEdgesGeometry = new EdgesGeometry(cylinderGeometry);
const cylinderEdges = new Mesh(cylinderEdgesGeometry, new MeshBasicMaterial({ color: 0xFFFFFF, side: THREE.DoubleSide }));
scene.add(cylinderEdges);
EdgesGeometry提供的是两个两个的顶点,LineSegment两个两个的取,是匹配的;而Mesh是三个三个取点。例如:[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]本来是要画两条线,一条是(1, 1, 1)-----(2, 2, 2),另一条(3, 3, 3)-----(4, 4, 4);但是用Mesh后,会画成(1, 1, 1)----(2, 2, 2)---(3, 3, 3)的三角形。
下面将它改成正确的Object3D:LineSegment。
const cylinderEdges = new LineSegments(cylinderEdgesGeometry, new LineBasicMaterial());
5. 总结
Geometry用来存储一系列顶点信息positions、normals等;存储这些信息使用AttributeBuffer进行存储。
由于创建内置的Geometry时,不同的Geometry存储的数据可能是不同类型的图元,所以,在画对象时,Geometry要选择对应的Object3D,保证Geometry中的顶点信息可以被正确的解释。