4改变顶点位置_OpenGL顶点规范的最佳实践

4b54cf4fa0cf92b0e32c15a966d67071.png

1 buffer对象的大小

你可以分配任意大小的buffer对象,但需要在潜意识里注意一些问题。

创建过多的buffer(大小差不多的小buffer)可能会引起一层驱动层的问题,有些驱动只允许分配一定数量的buffer,而不管分配的buffer大小。注意,这里的很多可能是数以千计的,所以好的做法是将小的buffer合并到一个大的buffer中。

对于buffer的尺寸两个互相矛盾的问题:

  1. 更大的buffer意味着将多个小对象放中了一个之中,这允许你在不切换buffer对象状态的情况下渲染更多对象,由此可以提升性能。
  2. 然而将小buffer放在一个大buffer中,在map一个buffer时,所有的buffer都不再可用。所以当你将很多的buffer合并成一个buffer的情况下,使用map的方式去访问其中一个buffer时,其它buffer不再可以被访问。

2 VBO的数据格式

VBO(vertex buffer object)在使用方式上相当灵活,例如有多种方式可以表示VBO中的顶点属性(attribute):

(VVVV) (NNNN) (CCCC)

一种可靠的方式是给每个draw call的每个顶点属性分配一个单独的VBO,如果你有vertex(位置), normal(法线)和color(颜色)作为顶点属性时,可以描述为:*(VVVV) (NNNN) (CCCC),其中共有3个独立的VBO。

(VVVVNNNNCCCC)

另一种方法是将所有的顶点属性块存储在一个batch中,相互之间紧紧相邻,但又都是在同一个VBO中。当通过glVertexAttribPointer来指定顶点属性时,需要传递其在VBO的位置偏移,可以表述为:(VVVVNNNNCCCC)。

(VNCVNCVNCVNC)

另一种方法是在一个batch中交错地存储顶点属性,在一个VBO中顺序地存储这种交错的顶点属性块。与之前一样,也需要通过glVertexAttribPointer来传递在VBO中的偏移,但也需要使用stride参数来保证每个顶点属性数据的正确性。这种方式可表述为:(VNCVNCVNCVNC)

现在这仅仅只是一个batch。你可以将多个batch存储在一个或多个VBO个。

最好的布局方式依赖于具体的GPU和驱动,但也有一些通用的好思想。

2.1 最小化vertex状态的改变

当绘制多个不同的mesh是,尽可能的将多个具有相同顶点格式的mesh存储在一个buffer中,也就是最小化glVertexAttribPointer的调用次数。

glDrawArrays以及其它数组风格的绘制命令可以很方便地使用buffer中的一个子区间进行绘制。

基于索引的绘制(如,glDrawElements)会有点麻烦,在将其数据放于buffer之前需要调用mesh的索引的偏移(bias),即可以在上传数据之前手动完成,也可以在绘制命令中使用BaseVertex(注意,ES中没有相关用法),例如glDrawElementsBaseVertex,其中BaseVertex是一个偏移,在绘制过程中会作用于所有的index数据。基于索引的绘制的一个好处是不多于65536个的顶点可以顺序存放在一个vertex buffer中,因此索引默认使用unsigned short类型,最多可以索引65536个顶点.

2.2 Attribute sizes

顶点属性数据越小越好,充分复用有short和byte数据的归一化(normalized)等特性。

2D纹理坐标

大部分情况可以在无损的情况下存储为归一化的GL_SHORT或GL_UNSIGNED_SHORT。

法线

法线的精度通常不是非常重要,因为归一化的向量范围是[-1, 1],因此最好使用归一化的整型格式。法线的3个元件(xyz)可以被使用GL_INT_2_10_10_10_REV类型存储成一个32位的整型,可以忽略存储了2个bit的那部分。

颜色

如果不需要HDR颜色,可以将其存储为GL_UNSIGNED_BYTE,因此一个颜色可以打包进4个byte中。如果需要更高的精度,可以使用GL_UNSIGNED_INT_2_10_10_10_REV,其中2个bit分配给alpha使用。如果确实需要HDR颜色,可以使用GL_R11F_G11F_B10F来保证一定的精度,如果精度还是不够,可以使用GL_HALF_FLOAT,但最好不要使用GL_FLOAT。

位置

很难不使用GL_FLOAT来达到所需要的精度,但这也和你的数据和工作方法有关。也可以使用GL_HALF_FLOAT,但它所表示的范围和精度是相对有限的。

一个久经考验的可选方法是使用归一化的GLshort,但需要让所有的模型位置都处于[-1,1]这一区间。找到所有位置中XYZ的最小值和最大值,然后从所有的位置中减去min/max立方体的中心;接着使用width/height/depth的一半来缩放新的位置。使用这种方法需要全局地保持中心位置和缩放因子。

当构造模型视图矩阵(或模型到其它任意的矩阵)时,你需要将中心点的偏移和缩放应用于该矩阵(在最后的绘制之前)。注意,偏移和缩放不要作用于法线,因此它们在不同的空间。

有一些东西需要注意的,任何顶点数据的对齐都不能小于4个字节。如果你一个GLushort类型的vec3,你不能使用第4个component(最后两个byte)来分配给一个新属性(如GLbyte类型的vec2)。如果你想填充一些数据而不是无用的填充(默认会填充两个byte),你需要使用GLushort类型的vec4结构。

2.3 交错(Interleaving)

交错布局的顶点属性究竟能否提升性能,是需要一定的profile数据的。但因为对齐的原因,交错布局可能会比非交错布局占用更多的空间。

2.4 流式属性(Streamed attributes)

流式属性(属性每帧都会改变或改变的很频繁)需要使用buffer对象流技术,该技术通常不适合静态属性,很多的流式技术需要丢弃整个buffer对象,因此没有办法将流式属性和非流式属性放在同一个buffer中。

3 顶点,法线,纹理坐标(Vertex, normals, texcoords)

  • 是否需要创建单独的VBO?这样是否会损失性能?

如果你的对象是静态的,尽可能的将它们放在一直VBO中来提升性能。

如果其中的一些属性是动态的,如(位置)经常改变,将其放在一个单独的VBO中,可以快速且简单地对其进行更新。例如,如果你用CPU来模拟水,每个顶点的位置(position)可能一直在改变,但是它的颜色会一直保持一个颜色。

示例:多个VBO的顶点属性:

// 绑定顶点位置glBindBuffer(GL_ARRAY_BUFFER, vertexVBOID);// 位置的开始地址glVertexPointer(3, GL_FLOAT, sizeof(float)*3, NULL);// 绑定法线和纹理坐标glBindBuffer(GL_ARRAY_BUFFER, otherVBOID);// 法线的开始地址glNormalPointer(GL_FLOAT, sizeof(float)*6, NULL); // 纹理坐标的开始地址glTexCoordPointer(2, GL_FLOAT, sizeof(float)*6, sizeof(float*3));

4 动态VBO

  • 如果VBO的所有数据都是动态的,是否应该调用glBufferData,或者glBufferSubData,或者glMapBuffer?

如果你需要更新其中一部分数据,使用glBufferSubData,如果要更新整个VBO,使用glBufferData(来自于nVidia的文档信息)。然而当更新整个VBO时,另一个方法被认为可以工作的更好,使用一个NULL指针来调用glBufferData,然后使用glBufferSubData来更新它的内容。传递NULL给glBufferData是用来告诉驱动不需要关心先前的内容,这样驱动可以使用一个新的内存空间来代替,这样可以让驱动管线更加高效的上传数据(不需要stall来等待之前命令的完成)。

另外也可以使用双VBO缓冲区,在第N帧时,你更新VBO2,但是使用VBO1的数据进行渲染,在每N+1帧,你更新VBO1,但是使用VBO2进行渲染,这在大部分GPU上可以带来明显的性能提升。

5 顶点布局规范(Vertex Layout Specification)

使用这种方式的代码:

glBindBuffer(GL_ARRAY_BUFFER, vboID);glEnableClientState(GL_VERTEX_ARRAY);glVertexPointer(3, GL_FLOAT, sizeof(TVertex_VNTWI), info->posOffset);glTexCoordPointer(2, GL_FLOAT, sizeof(TVertex_VNTWI), info->texOffset);glEnableClientState(GL_TEXTURE_COORD_ARRAY);glNormalPointer(GL_FLOAT, sizeof(TVertex_VNTWI), info->nmlOffset);glEnableClientState(GL_NORMAL_ARRAY);// --------------------int weightPosition = glGetAttribLocation(programID, "blendWeights");glVertexAttribPointer(weightPosition, 4, GL_FLOAT, GL_FALSE, sizeof(TVertex_VNTWI), info->weightOffset);glEnableVertexAttribArray(weightPosition);// --------------------int indexPosition = glGetAttribLocation(programID, "blendIndices");glVertexAttribPointer(indexPosition, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof(TVertex_VNTWI), info->indexOffset);glEnableVertexAttribArray(indexPosition);// --------------------glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboID);glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0);

原文链接 https://www.khronos.org/opengl/wiki/Vertex_Specification_Best_Practices

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值