缓冲区和 glVertexAttribPointer 之间的关系

Posted

技术标签:

【中文标题】缓冲区和 glVertexAttribPointer 之间的关系【英文标题】:Relation between buffers and glVertexAttribPointer 【发布时间】:2018-12-11 10:18:47 【问题描述】:

我试图了解 [glGenBuffers, glBindData, glBufferData, glBufferSubData] 和 glVertexAttribPointer 之间的关系。

我看到您实际上不必使用 buffers 方法(至少在 android 中不是),而是直接调用 glVertexAttribPointer,将指向缓冲区对象的指针作为最后一个参数传递。 (在这种情况下,缓冲区是 FloatBuffer

例如

GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer);
GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT, false, 16, colorBuffer);
GLES20.glVertexAttribPointer(normalHandle, 3, GLES20.GL_FLOAT, false, 12, normalsBuffer);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer);

1) 为什么要使用缓冲方法?仅当我的数据碰巧堆叠在一个数组中?

2) OpenGL 如何在幕后处理这些直接(非缓冲)函数?我看到它调用了glVertexAttribPointerBounds——它实际上是把它堆在一起形成一个字节数组吗?

3) 如果我将数组绑定到GL_ARRAY_BUFFER - 这意味着我必须使用堆叠数组版本,并且我将无法再传递直接对象,对吗?即我不能这样做:

int[] bufferVertices = new int[1];
GLES20.glGenBuffers(1, bufferVertices, 0);
vertexBufferId = bufferVertices[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeCoords.length * 4 , vertexBuffer, GLES20.GL_STATIC_DRAW);

GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride,0);
GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT, false, 16, colorBuffer);
GLES20.glVertexAttribPointer(normalHandle, 3, GLES20.GL_FLOAT, false,12, normalsBuffer);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false,8, textureBuffer);

【问题讨论】:

【参考方案1】:

您可以有多个缓冲区对象。如果绑定了顶点数组缓冲区,当调用glVertexAttribPointer 时,glVertexAttribPointer 的最后一个参数被视为该缓冲区的偏移量,但如果没有绑定顶点缓冲区对象,则最后一个参数被视为指向该缓冲区的指针数据:

见OpenGL ES 2.0 specification; 2.8. VERTEX ARRAYS; 21:

void VertexAttribPointer( uint index, int size, enum type, 
                          boolean normalized,  sizei stride, 
                          const void *pointer );

... 对于每个命令,pointer 指定指定数组的第一个元素的第一个值在内存中的位置。

见OpenGL ES 2.0 specification; 2.9. BUFFER OBJECTS; 25:

... 当一个数组来源于 一个缓冲区对象,该数组的 pointer 值用于以基本机器单位计算偏移量到缓冲区对象的数据存储中。 ...

这意味着可以这样做:

int[] bufferVertices = new int[4];
GLES20.glGenBuffers(4, bufferVertices, 0);

vertexBufferId = bufferVertices[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeCoords.length * 4, vertexBuffer, GLES20.GL_STATIC_DRAW);

colorBufferId = bufferVertices[1];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, colorBufferId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeCoords.length * 4, colorBuffer, GLES20.GL_STATIC_DRAW);

normlBufferId = bufferVertices[2];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, normlBufferId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeCoords.length * 4, normalsBuffer, GLES20.GL_STATIC_DRAW);

textureBufferId = bufferVertices[3];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, textureBufferId);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, cubeCoords.length * 4, textureBuffer, GLES20.GL_STATIC_DRAW);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId);
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, colorBufferId);
GLES20.glVertexAttribPointer(colorHandle, 4, GLES20.GL_FLOAT, false, 0, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, normlBufferId);
GLES20.glVertexAttribPointer(normalHandle, 3, GLES20.GL_FLOAT, false, 0, 0);

GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, textureBufferId);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 0, 0);

【讨论】:

好的,很好!我认为您忘记增加缓冲区的索引。你也知道它在幕后是如何处理的吗?如果我用指向缓冲区的指针调用 glVertexAttribPointer(就像我在问题中显示的那样),它是否首先在该缓冲区上调用(在幕后)BindBuffer? @DavidRefaeli 没有明确说明。但在这种情况下,OpenGL 对缓冲区大小一无所知。顶点数组数据在绘制调用时立即加载到 GPU。【参考方案2】:

1) 首选方法是尽可能使用缓冲区对象。这样可以确保提前分配必要的 GPU 资源。然而,对于动态几何,调用无缓冲方法有时更方便。根据我的经验,与使用 GLES20.GL_DYNAMIC_DRAW 的缓冲区相比,一些驱动程序使用无缓冲区方法表现出更好的性能。但对于静态几何,最好的方法是使用 GL 缓冲区。这样,驱动程序就可以避免在每次绘制调用之前复制顶点数据。

2) 这里的常识是,当你将一个 CPU 端数组传递给 glVertexAttribPointer 时,它必须在随后的 glDraw* 调用期间由驱动程序复制。然后您可以修改该数组,它将在下一次 glDraw* 调用期间被复制。至于内存中顶点数据的布局,我敢打赌它会保持原样,因为 AFAIK GPU 可以原生处理非交错流。因此,在 GL 绘制调用之前重新排列它们将需要无意义的聚集操作。

3) GL 缓冲区和 CPU 数组流的混合应该可以正常工作。这是一种常见的场景,其中一部分顶点数据是静态的(例如 texcoords),而另一部分是动态的(例如位置),例如,后者由 CPU 转换。

【讨论】:

以上是关于缓冲区和 glVertexAttribPointer 之间的关系的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL glVertexAttribPointer 正常

glVertexAttribPointer 问题(OpenGL 3.x 前向兼容上下文)

多个缓冲器和 VAO 性能

C++学习(三一二)glVertexAttribPointer和glVertexAttrib*的关系

GLES2.0 上的 VBO glDrawElements 和 glVertexAttribPointer 不显示任何内容

glVertexAttribPointer和stride参数要求