- BufferAttribute
- BufferGeometry
- InstancedBufferAttribute
- InstancedBufferGeometry
- InstancedInterleavedBuffer
- InterleavedBuffer
- InterleavedBufferAttribute
THREE.js中有一个重要的类,Mesh,即网格体对象。这个网格体对象在构造的时候需要传入两个变量,geometry和material,geometry指的是这个网格体的顶点属性信息,以及索引信息,而material中包含的是网格体的材质信息。
从webgl的底层来看,geometry可以视为绘制时的VAO/VBO/EBO等buffer的信息,material则可以视为存储uniform变量的对象。在geometry中,通常会有多个attribute属性信息,记录每个顶点的属性,以及index属性,记录EBO信息。每个attribute中存储了一个数组,包含对应顶点的属性信息,以及itemSize,记录每个属性包含的元素个数,以及normalized,元素是否需要归一化等。
THREE.js中有很多种类的geometry,比如常用的长方体几何体BoxGeometry,球几何体SphereGeometry,平面几何体PlaneGeometry,以及各种奇奇怪怪形状的几何体。但是这些几何体都有一个共同的基类,BufferGeometry。
BufferGeometry,故名思意,缓冲区几何体,内存存储的是要绘制的物体的缓冲区数据。使用webgl时我们知道,在定义好顶点数据后,需要一系列的gl.createBuffer()、gl.bindBuffer()、gl.getAttributeLocation()、gl.vertexAttribPointer()、gl.enableVertexAttribArray()等一系列操作来绑定和操作VAO/VBO/EBO。虽然BufferGeometry并没有这些步骤,而是在后续render的时候才会进行VAO/VBO/EBO的操作,但是BufferGeometry将所需要的数据进行了存储,后续能够很方便得处理数据。
在使用webgl绘制物体的时候,我们会在glsl中使用attribute变量来定义顶点属性。如,我们定义一个"attribute vec3 aPosition"来记录物体的顶点的坐标信息。在准备好数据后,我们使用gl.getAttributeLocation()来获取aPosition变量的地址,然后使用gl.vertexAttribPointer()将我们的数据送入变量,每次渲染GPU会从buffer中读取数据,组成顶点的位置,进行后续的处理。
BufferGeometry中类似的过程是使用一个BufferAttribute类型的变量来实现的。接口为BufferGeometry.prototype.setAttribute(name: string, attribute: BufferAttribute)
BufferAttribute变量内部包含了以下数据:
- array:buffer的结构化数组数据
- itemSize:每一个属性使用到的数据元素个数
- normalized:是否进行归一化
- count:数据数量,使用array.length / itemSize计算
在BufferGeometry内部以{ attributeName: BufferAttribute }键值对来保存顶点属性名称及其内容的。
在使用gl.vertexAttribPointer时,传入的参数有以下内容
- index:指定顶点属性的索引值,就是通过getAttributeLocation获取到的地址,这个可以通过对应的attributeName来获取
- size:每个顶点属性所使用的元素的个数,对应itemSize
- type:顶点属性的数据类型,three.js中全部默认为float类型
- normalized:是否归一化,对应normalized
- stride:步长,
- offset:偏移量
在BufferGeometry中,传入的结构化数组的值必须全部都是同一个顶点属性的数据,即不支持在一个结构化数组中传入两种及以上个顶点属性的值。渲染时,stride和itemSize*元素字节大小一样,offset为0。如果必须使用一个包含了多个顶点属性值的结构化数组,则需要使用InterleavedBuffer来构造带stride步长的buffer,stride即表示每一组数据包含多少个元素,如,我们在一个结构化数组中包含了3个position的XYZ,4个color的RGBA值,以及2个UV值,则stride为3+4+2=9。同时,还要使用lnterleavedBufferAttribute来构建attribute,需要传入一个包含顶点属性数据的InterleavedBuffer,顶点属性的itemSize,以及便宜量offset和normalized。如,上述的结构化数组中,每段步长内,前三个值为position的值,因此,使用方式为
new InterleavedBufferAttribute(interleavedBuffer, 3, 0, false);
而对于中间四个RGBA值,由于前面有三个position值,因此,offset为3
new InterleavedBufferAttribute(interleavedBuffer, 4, 3, false);
同理,对于UV,offset为3+4=7,itemSize为2。
一个完整的示例:
const vertices = new Float32Array([ // 定义顶点数据,三个顶点,每个顶点XYZ三个值
0.0, 0.5, 0.0,
-0.5, 0.0, 0.0,
0.5, 0.0, 0.0
])
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position", // attribute属性名,position,会在shader中找名为position的attribute变量
new THREE.BufferAttribute(
vertices, // 数据,一个结构化数组,保存所有的顶点值
3, // 每个顶点使用的元素个数,每个顶点包含XYZ三个值
false // 是否进行normalize
)
);
如果使用InterleavedBuffer
const buffer = new THREE.InterleavedBuffer(
new Float32Array([
// 顶点位置 // 顶点颜色
0.0, 0.5, 0.0, 1.0, 0.0, 0.0,
-0.5, 0.0, 0.0, 0.0, 1.0, 0.0,
0.5, 0.0, 0.0, 0.0, 0.0, 1.0
]),
6 // stride,每个顶点有position和color两个属性,各有XYZ和RGB三个值
);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute(
"position",
new THREE.InterleavedBufferAttribute(
buffer, // buffer数据
3, // position属性有三个值
0, // position的值在一个stride中的偏移值为0
false // 是否normalized
)
);
geometry.setAttribute(
"color",
new THREE.InterleavedBufferAttribute(
buffer,
3,
3,
false
)
);
理解了这一点后,有关BufferGeometry和BufferAttribute的其它方法并没有什么难的。但是值得注意的是,BufferGeometry中默认顶点坐标的attribute名为position,法向为normal,切线为tangent,同时,有computeVertexNormal和computeTangents方法可以自动根据顶点坐标计算法向和切线。同时会计算顶点包围球和轴向包围盒。
另一个比较好玩的东西是BufferGeometry中的groups属性和drawRange属性,groups可以将顶点进行分组,并对分组的部分设置一个新的材质。也就是说,mesh的material部分是可以是一个数组的,我们可以通过addGroup方法对BufferGeometry内的顶点进行分组,然后每一组对应一个material,这样就可以实现同一个mesh的不同部分有着不同的样式。而drawRange是用于控制绘制多少个顶点的。在webgl中,gl.drawElements方法有两个参数,count和offset,可以控制绘制图元的数量和数据的便宜量,而drawRange中的start和count正是对应了这两个属性。
BufferGeometry中其他的一些变换方法,如旋转,平移、缩放、使用矩阵等,其实也都是对应position\normal和tangent来进行变换的(如果有的话)。但是和mesh的变换不同的是,mesh的变换是作用在mesh自身的matrix上的,而BufferGeometry的变换是真的作用在buffer数据中的。
而对于BufferAttribute,它是buffer数据本身,所以并不会考虑自己是否是position之类的属性,所有的变换方法都是针对自身的buffer数据来进行的。因此,如果要对非position/normal/tangent数据进行矩阵变换,则可以获取到BufferAttribute后进行变换。
对于InterleavedBufferAttribute,原理是一样的,但是要注意的是,这种attribute使用到的数据只是buffer的一部分,所以每次获取原数据的时候要考虑一下buffer本身的stride和attibute的offset,获取attribute使用的那一部分数据。
THREE.js中还有另一类mesh,InstancedMesh,即实例对象mesh。实例对象和普通的对象的区别是,实例对象可以一次性绘制多个同样的物体,这些物体使用同样的geometry、material,也共享InstanceMesh对象的matrix和matrixWorld,但是每一个物体会有一个自身的instanceMatrix,用于调整自身的位置。WebGL2支持了实例化对象的绘制,使用gl.drawArraysInstanced和gl.drawElementsInstanced,webgl中,则要使用ANGLE_instanced_arrays扩展。
创建InstancedMesh时,除了geometry和material,还需要传入一个count,表示实例对象的个数,然后threejs会根据实例个数初始化每个对象的instanceMatrix。在shader中,对于非实例对象而言,其世界坐标为modelMatrix * worldPosition,而对于实例对象,则为modelMatrix * instanceMatrix * worldPosition。
同样的,使用InstancedMesh构建实例对象时,使用的geometry的类型为InstancedBufferGeometry,属性也是对应的InstancedBufferAttribute,对于InterleavedBufferAttribute,使用的buffer类型也为InstancedInterleavedBuffer。
InstancedBufferGeometry是BufferGeometry的子类,唯一的变动是,多了一个instanceCount属性,用于保存实例的个数。
InstancedBufferAttribute和InstancedInterleavedBuffer也是对应的BufferAttribute和InterleavedBuffer的子类,唯一的不同是,多了一个属性,meshPerAttribute,即每一个属性对应多少个实例对象,默认为1,表示一个实例属性值被一个实例所用,如果是2,则表示一个实例属性被两个连续的实例所用,以此类推。
有关geometry和attribute的重点内容就这么多.
标签:BufferGeometry,Geometry,Attribute,geometry,buffer,源码,0.0,顶点,属性 From: https://www.cnblogs.com/masakulayou/p/18137822