缓冲区和 glVertexAttribPointer 之间的关系

Relation between buffers and glVertexAttribPointer

我正在尝试了解 [glGenBuffersglBindDataglBufferDataglBufferSubData] 和 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);

您可以有多个缓冲区对象。如果绑定了顶点数组缓冲区,当调用 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 );

... For each command, pointer specifies the location in memory of the first value of the first element of the array being specified.

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

... When an array is sourced from a buffer object, the pointer value of that array is used to compute an offset, in basic machine units, into the data store of the buffer object. ...

这意味着可以做到:

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

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

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

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