10

通过学习一些教程并编写自己的一些实验,我已经学习 OpenGL 几天了。但是有一件事我真的不明白是什么阻止了我继续。我已经在谷歌上搜索了几个小时,但还没有找到我的问题的答案。

我应该在哪里为每个单独的顶点指定每个单独的颜色值和纹理坐标?这些属性是否应该始终列在与顶点位置相同的数组(结构)中?像这样:

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}},

    ...

或者有没有办法将颜色值和纹理坐标放在单独的数组中?但随之而来的问题是:如何glDrawElements使用单独的数组调用?

如果您想知道我为什么要分离这些值:我目前正在 obj-c 中制作自己的 .obj 解析器,我想知道:如果您加载没有纹理的模型并且只想在目的?或者:如果你想加载一个只有纹理映射到它的模型,但每个顶点没有单独的颜色怎么办?并且:放置颜色值和纹理坐标不会使 Vertex 结构因过多数据而膨胀。

4

3 回答 3

22

实际上,这是使用多个数组/缓冲区将顶点数据分成位置、颜色等的常用方法。

上次我接触 ES 2.0 是在 WebGL 的上下文中(它的规范略有不同,但最终基于 ES 2.0)。

基本上完成的是使用将数据写入单独的缓冲区

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

在这种情况下,位置和颜色是包含顶点数据的浮点数组和包含索引为无符号短裤的索引。

要渲染此数据,您将使用指向着色器的缓冲区和属性指针:

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

最后绑定索引缓冲区:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

并渲染:

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, 0);

获取属性:

glUseProgram(shaderProgram);

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

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

...

如果您没有自定义着色器(使用固定功能),您也许可以使用

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

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

反而。不过,我建议不要这样做,因为它已经过时了(如果在 ES 2.0 中完全可用的话)。如果你还想使用它,你可以完全跳过整个缓冲业务并使用

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

glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_SHORT, indices);

我希望这不会太令人困惑并有所帮助。为了进一步阅读,虽然针对 OpenGL,但我建议使用Nehe 教程

于 2012-01-06T12:24:18.053 回答
2

您当然可以将数据放在不同的缓冲区中。请记住,glVertexAttribPointer决定属性来源的是调用。因此,要为属性使用不同的缓冲区,只需GL_ARRAY_BUFFER在调用glVertexAttribPointer. glDrawElements与它没有任何关系:

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

当不使用 VBO(我不确定在 ES 2.0 中是否可行)时,只需glVertexAttribPointer为每个属性调用不同的数组:

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

glDrawElements(..., indexArray);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);

但是,像在您的示例中那样将单个顶点的属性保持在一起通常会更高效,因为这对缓存更友好。如果几乎所有对象都使用所有数组,那么将它们放在一起并且不为少数不使用它的对象启用该属性可能是一个更好的主意。但是,如果使用的属性确实因对象而异,那么单独的缓冲解决方案可能是一个更好的主意。您还可以将所有单独的属性数组一个接一个地存储在单个 VBO 中,并在glVertexAttribPointer调用中使用相应的缓冲区偏移量,因此每个对象仍然只需要一个 VBO。

但当然,每个对象只能使用一个索引数组,而位置和颜色不能使用不同的索引数组。这可能需要您对从 OBJ 文件读取的数据进行一些后处理,因为 OBJ 文件确实可以对位置、法线和 texCoords 使用不同的索引。

于 2012-01-06T12:46:28.443 回答
1

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);
}    
于 2012-01-06T12:03:30.130 回答