OpenGL ES 2.0:寻求针对移动顶点的VBO性能优化技巧

9
在我尝试从ES 1.x转换到OpenGL ES 2.0的过程中,我正在将一些代码转换为使用顶点缓冲区对象(“VBO”)而不是现有的未缓冲的glDrawArrays调用。
我已经设置了VBO并使其工作,但遇到了设计困境,希望得到更有经验的OpenGL ES 2.0专家的建议。
我想绘制一堆多边形精灵,它们经常移动。这是因为它们是动态Box2D物体,如果您熟悉Box2D的话。这些多边形体是通过使用GL_TRIANGLE_FAN生成的,这在ES 2.0上是相当关键的,因为GL_POLYGON不可用。
多边形还具有其他属性,例如颜色,可能会在应用程序生命周期的某个阶段进行更改,但是顶点位置几乎每个帧都会更改。
多边形在游戏中分组,因此我的意图是管理和绘制每组的交错顶点/颜色数组,而不是每个物体,以尝试最小化GPU通信。
成功的路线有很多,我一直在阅读《OpenGL ES 2.0编程指南》以寻求与VBO相关的尽可能多的信息和优化技巧,以下是它们的建议:
- 交错数据是有利的,因为“每个顶点的属性数据可以按顺序读取”。 - 该书建议“如果需要修改一组顶点属性数据,则可以将具有动态特性的顶点属性存储在单独的缓冲区中”。 - 建议“尽可能使用GL_HALF_FLOAT_OES”,特别是对于颜色,因为未投影的顶点位置可能超出此空间要求。 - 如果正在更新整个缓冲区,则应仅使用glMapBufferOES,并且即使在这种情况下,该操作“与glBufferData相比仍然可能很昂贵”。
以下是我的问题:
如果使用GL_TRIANGLE_FAN作为绘图模式,这是否会强制我针对每个物体存储一个VBO而不是每个组?或者一个公共的顶点位置来‘结束’扇形,并且当前物体将导致在组VBO中为下一个物体绘制新的扇形?
尽管顶点位置经常更新,我是否应该交错所有数据,还是将它们分开/只是将位置分开成它们自己的VBO?
根据上面提供的建议,我应该在每次更新渲染时完全glBufferData我的顶点位置,而不是使用glMapBufferOES或glBufferSubData来更新缓冲区中的位置。
在许多移动多边形的情况下,是否有任何未提及的功能/设计选择可以利用以增强性能?
我应该尝试使用GL_HALF_FLOAT_OES进行颜色存储,即在2个浮点数的空间中,我是否应该存储4个半浮点数?如果答案是‘是’,那么我只需使用GLfloat大小的任何GL类型来为每个颜色使用位OR,然后插入到适当的属性数组中即可。
一旦我创建了X个VBO,每次渲染时我需要做的唯一调用是glBindBuffer、glBufferData和glDrawElements/Arrays,还是每次使用glBufferData时还必须调用glEnableVertexAttribArray和glVertexAttribPointer?
我非常感谢您对此的进一步建议,谢谢。
1个回答

14

我没有使用ES的经验,但是我认为很多东西仍然适用。

  1. 部分地,它并不强制你使用一个VBO每个物体,但你必须为每个物体做一次glDrawArrays。这些可能仍然从同一个缓冲区获取它们的数据,但这仍然不可取。相反,我会避免使用像三角形扇形或带状物这样的复杂基元,并使用索引化的三角形列表,这样就可以在单个调用中绘制所有内容。我怀疑ES是否支持primitive_restart扩展。如果支持,那么可以指定一个特殊的顶点索引来重新启动基元。

  2. 如果您有许多其他静态属性,则将顶点位置分离到它们自己的缓冲区中会是一个好主意(当然,该缓冲区具有GL_DYNAMIC_DRAW甚至GL_STREAM_DRAW用途)。但是,如果您只有额外的4ub颜色或类似的东西,则额外的复制成本可能不会那么严重,并且您可能更好地受益于交错,需要进行测试。

  3. 如果您每帧都要更新它们,则完全的glBufferData可能比glBufferSubData更好。或者,您也可以调用glBufferData(...,NULL),然后调用glMapBuffer(...,GL_WRITE_ONLY),如果您不想为数据保留CPU数组。这告诉驱动程序您不再关心以前的数据。这样,驱动程序就可以为您分配全新的缓冲区,而以前的数据仍在用于渲染。因此,您可以在旧数据仍在使用时上传新数据(当不再使用旧缓冲区时,驱动程序会释放旧缓冲区)。

  4. 占位符

  5. 对于颜色,GL_UNSIGNED_BYTE可能会更好,因为这些通常不需要那么高的精度。当您例如具有3个浮点坐标和4字节颜色通道时,这可能也适用于对齐优化,从而生成一个16字节的顶点,这非常友好。在这种情况下,建议将顶点和颜色保留在同一个缓冲区中。

编辑:为了阐明第3点:如果您已经有一个CPU数组中的数据,则可以直接使用glBufferData处理该数据。如果您希望驱动程序为您分配该存储空间,则可以使用glMapBuffer,它会将指向缓冲区内存映射到CPU地址空间的指针(当然,您需要GL_WRITE_ONLY,因为您不关心以前的数据)。但在这种情况下,glBufferData与空指针一起使用将为缓冲区分配完全新的存储空间(而不复制任何数据),这告诉驱动程序,我们不关心之前的内容(即使它们当前仍用于渲染)。驱动程序可以优化此情况并在幕后分配新存储空间,但仍未释放以前的存储空间,直到以前的数据最终不再用于渲染为止才释放。但请

updateData(dataArray);
glBufferData(GL_ARRAY_BUFFER, size, dataArray, usage);

如果您已经在自己的CPU数组中获取了数据,或者

glBufferData(GL_ARRAY_BUFFER, size, NULL, usage);
dataArray = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
updateData(dataArray);
glUnmapBuffer(GL_ARRAY_BUFFER);

如果您不需要CPU的数据拷贝并希望驱动程序来处理。但是,如果在整个应用程序期间逐步更新数据,则第一种解决方案可能更好,因为只要缓冲区被映射,就不能将其用于渲染,并且您应该仅短时间映射缓冲区。


非常感谢你,Christian。像往常一样,你的帮助非常大。我对你在(3)中所说的内容很感兴趣——因此,使用最终的NULL参数调用glBufferData基本上会处理当前绑定的缓冲区,然后您会建议通过使用glMapBuffer提交新缓冲区而不是新的glBindBuffer然后glBufferData?如果您能提供几行代码,让我理解您的意思,我将不胜感激。如果我太迟钝,请原谅! - KomodoDave
你是说我可以在将NULL传递给glBufferData后使用glMapBuffer向当前绑定的缓冲区写入数据,然后即使我只调用了一次glBindBuffer,内部仍会创建一个与现有缓冲区分离的新缓冲区? - KomodoDave
非常感谢,Christian,我正在阅读 :) 编辑:您是指glBufferData(GL_ARRAY_BUFFER,size,NULL,usage)吗? - KomodoDave
@KomodoDava 是的,你说得对。驱动程序实际上并没有创建新的缓冲区,它只是为其分配新的存储空间,并稍后释放旧的存储空间(但请注意,这取决于具体实现,驱动程序不必进行此优化),但对于你来说,它仍然是同一个缓冲区。 - Christian Rau
2
非常感谢您的澄清,Christian。这对我来说非常有帮助 :) 您的时间非常宝贵,感激不尽。 - KomodoDave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接