OpenGLES 2.0 separate buffers for vertices, colors and texture coordinates

15,995

Solution 1

Actually it is the usual way to separate the vertex data into position, color, etc. using several arrays/buffers.

Last time I came in contact with ES 2.0 was in the context of WebGL (which is has a slightly different spec but is ultimately based on ES 2.0).

What is basically done is writing the data to seperate buffers using

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(float), positions, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glBufferData(GL_ARRAY_BUFFER, 16 * sizeof(float), colors, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

...

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(ushort), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

with positions and colors being float arrays containing the vertex data and indices containing the indices as unsigned shorts in this case.

To render this data, you'd use the buffers and attribute pointers to your shader:

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexAttribPointer(vertexPositionAttribute, 3,  GL_FLOAT, false, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glVertexAttribPointer(vertexColorAttribute, 4, GL_FLOAT, false, 0, 0);

Finally bind the index buffer:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

and render:

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0);

To get the attributes:

glUseProgram(shaderProgram);

vertexPositionAttribute= glGetAttribLocation(shaderProgram, "vertexPosition");
glEnableVertexAttribArray(vertexPositionAttribute);

vertexColorAttribute = glGetAttribLocation(shaderProgram, "vertexColor");
glEnableVertexAttribArray(vertexColorAttribute );

...

If you don't have custom shaders(using fixed function) you might be able to use

glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glVertexPointer(3,  GL_FLOAT, false, 0, 0);

glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glColorPointer(4, GL_FLOAT, false, 0, 0);

instead. I'd advise against it, though, as it is outdated (if at all available in ES 2.0). If you still want to use it, you can skip the whole buffer business altogether and use

glVertexPointer(3, GL_FLOAT, false, 0, positions);
glColorPointer(4, GL_FLOAT, false, 0, colors);

with

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);

I hope that wasn't too confusing and helps a bit. For further reading, although targeted at OpenGL, I'd suggest the Nehe tutorials.

Solution 2

You can of course have your data in different buffers. Keep in mind that it is the glVertexAttribPointer call that determines an attribute's source. So to use a different buffer for an attribute, just bind a different GL_ARRAY_BUFFER before calling glVertexAttribPointer. glDrawElements doesn't have anything to do with it:

glBindBuffer(GL_ARRAY_BUFFER, positionBuffer);
glVertexAttribPointer(0, ...);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glVertexAttribPointer(1, ...);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
glDrawElements(...);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);

When not using VBOs (which I'm not sure is possible in ES 2.0), just call glVertexAttribPointer with a different array for each attribute:

glVertexAttribPointer(0, ..., positionArray);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, ..., colorArray);
glEnableVertexAttribArray(1);

glDrawElements(..., indexArray);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);

But it is usually more performant to keep a single vertex's attributes together like in your example, as this is more cache friendly. If nearly all your objects use all arrays, then keeping them together and just not enabling the attribute for the few objects that don't use it, may be a better idea. But if the used attributes really differ from object to object, the separate buffer solution may be a better idea. You can also store all separate attribute arrays one after the other in a single VBO and use corresponding buffer offsets in the glVertexAttribPointer calls, so you still only need one VBO per object.

But of course you can only use one index array per object and not different index arrays for positions and colors. This may require you to postprocess the data read from an OBJ file a bit, as OBJ files can indeed use different indices for positions, normals and texCoords.

Solution 3

You can seperate them into sub-buffers but if you use them then you have to have them for all vertices and if you you use index-buffers then you have to use one index-buffer for all (position, color, texcoord, etc). Here are some excerpts from my code:

allocation with

glBindBuffer(GL_ARRAY_BUFFER, mId);
glBufferData(GL_ARRAY_BUFFER,
               mMaxNumberOfVertices * (mVertexBlockSize + mNormalBlockSize + mColorBlockSize + mTexCoordBlockSize),
               0,
               mDrawMode);

fill with

glBufferSubData(GL_ARRAY_BUFFER, mVertexOffset, numberOfVertsToStore * mVertexBlockSize, vertices);
glBufferSubData(GL_ARRAY_BUFFER, mNormalOffset, numberOfVertsToStore * mNormalBlockSize, normals);
glBufferSubData(GL_ARRAY_BUFFER, mColorOffset, numberOfVertsToStore * mColorBlockSize, colors);
glBufferSubData(GL_ARRAY_BUFFER, mTexCoordOffset, numberOfVertsToStore * mTexCoordBlockSize, texCoords);

and usage with this (I don't think constantly switching clientStates is best-practice though)

void Vbo::draw(GLenum primMode)
{
  glEnableClientState(GL_VERTEX_ARRAY);
  glVertexPointer(mVertexComponents, GL_FLOAT, 0, (void*)mVertexOffset);

  if(mNormalBlockSize){
    glEnableClientState(GL_NORMAL_ARRAY);
    glNormalPointer(GL_FLOAT, 0, (void*)mNormalOffset);
  }
  if(mColorBlockSize){
    glEnableClientState(GL_COLOR_ARRAY);
    glColorPointer(mColorComponents, GL_FLOAT, 0, (void*)mColorOffset);
  }
  if(mTexCoordBlockSize){
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer(mTexCoordComponents, GL_FLOAT, 0, (void*)mTexCoordOffset);
  }

  if (mAttachedIndexBuffer)
  {
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mAttachedIndexBuffer);
    glDrawElements(primMode,
                   mAttachedIndexBuffer->getNumberOfStoredIndices(),
                   mAttachedIndexBuffer->getDataType(),
                   0);
  }
  else
  {
    glDrawArrays(primMode, 0, mNumberOfStoredVertices);
  }

  if(mTexCoordBlockSize)
  {
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
  }
  if(mColorBlockSize)
  {
    glDisableClientState(GL_COLOR_ARRAY);
  }
  if(mNormalBlockSize)
  {
    glDisableClientState(GL_NORMAL_ARRAY);
  }
  glDisableClientState(GL_VERTEX_ARRAY);
}    
Share:
15,995
polyclick
Author by

polyclick

A multimedia developer and tech house dj from the city of Antwerp.

Updated on June 11, 2022

Comments

  • polyclick
    polyclick about 2 years

    I've been learning OpenGL for a few days now by following some tutorials and coding some experiments of my own. But there is one thing I really don't understand which blocks me from continuing. I've been googling for a few hours now and didn't find an answer yet to my question.

    Where should I specify every separate color value and texture coordinate for every individual vertex? Should those properties always be listed in the same array (struct) as the vertex positions? Like so:

    const Vertex Vertices[] = {
        // Front
        {{1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
        {{1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
        {{-1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
        {{-1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    
        ...
    

    Or is there a way to put color values and texture coordinates in separate arrays? But then the question arises: how do I call glDrawElements with separate arrays?

    In case you are wondering why I want to separate these values: I'm currently making my own .obj parser in obj-c and I was wondering: what if you load a model without a texture and only want to show a color on the object? Or: what if you want load a model with only a texture mapped to it but no separate color per vertex? And: Isn't putting color values and texture coordinate bloating the Vertex struct with too much data.