Object3D、Geometry和BufferAttribute的关系

373 阅读3分钟

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:画三角形

  • LineLineLoopLineSegments:画线

  • 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);

example-1.PNG

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)的三角形。

下面将它改成正确的Object3DLineSegment

const cylinderEdges = new LineSegments(cylinderEdgesGeometry, new LineBasicMaterial());

example-2.PNG

5. 总结

Geometry用来存储一系列顶点信息positionsnormals等;存储这些信息使用AttributeBuffer进行存储。

由于创建内置的Geometry时,不同的Geometry存储的数据可能是不同类型的图元,所以,在画对象时,Geometry要选择对应的Object3D,保证Geometry中的顶点信息可以被正确的解释