OpenGL 3/4中glVertexAttribPointer的步长和偏移计算错误问题

18

我遇到了一个问题,无法正确指向我的顶点数组:

const float vertices[] = {
/* position */ 0.75f, 0.75f, 0.0f, 1.0f, /* color */ 1.0f, 0.0f, 0.0f, 1.0f,
/* position */ 0.75f, -0.75f, 0.0f, 1.0f, /* color */ 0.0f, 1.0f, 0.0f, 1.0f,
/* position */ -0.75f, -0.75f, 0.0f, 1.0f, /* color */ 0.0f, 0.0f, 1.0f, 1.0f, };

...

glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)16);

glDrawArrays(GL_TRIANGLES, 0, 3);

glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
我不理解步幅和偏移量是如何工作的。 在我的情况下,使用glVertexAttribPointer()的正确方法是什么?
2个回答

69

由于glVertexAttribPointer经常会出问题,我在这里尝试进一步解释它。

计算属性数组中第i个属性的起始位置的公式为:

startPos(i) = offset + i * stride(来自derhass的另一个答案

并在下面的图表中进行了说明: tightly packed arrtibute array


如果您需要代码示例,请继续阅读。

格式化VBO数据,我们知道可以用三种格式来管理顶点数据。以三角形为例,混合了顶点颜色和纹理颜色,这里是准备顶点属性数据的方法:


#way1 每个属性一个VBO。

此格式类似于:(xyzxyz...)(rgbrgb...)(stst....),我们可以让sride = 0和offset = 0。

void prepareVertData_moreVBO(GLuint& VAOId, std::vector<GLuint>& VBOIdVec)
{
    // positon
    GLfloat vertPos[] = {
        -0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
    };

    // color
    GLfloat vertColor[] = {
      1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
    };

    // texture coordinate
    GLfloat vertTextCoord[] = {
      0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f
    };
    
    GLuint VBOId[3];
    
    glGenVertexArrays(1, &VAOId);
    glBindVertexArray(VAOId);
    glGenBuffers(3, VBOId);

    // specify position attribute
    glBindBuffer(GL_ARRAY_BUFFER, VBOId[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertPos), vertPos, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(0);

    // specify color attribute
    glBindBuffer(GL_ARRAY_BUFFER, VBOId[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertColor), vertColor, GL_STATIC_DRAW);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(1);

    // specify texture coordinate attribute
    glBindBuffer(GL_ARRAY_BUFFER, VBOId[2]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertTextCoord), vertTextCoord, GL_STATIC_DRAW);
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, NULL);
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    VBOIdVec.push_back(VBOId[0]);
    VBOIdVec.push_back(VBOId[1]);
    VBOIdVec.push_back(VBOId[2]);
}

#way2: 每个属性是顺序存储的,批量存储在单个VBO中.

这种格式类似于:(xyzxyzxyz... rgbrgb... ststst...),我们可以让stride=0,但需要指定offset。

void prepareVertData_seqBatchVBO(GLuint& VAOId, std::vector<GLuint>& VBOIdVec)
{
    GLfloat vertices[] = {
      -0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,  // position
      1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,   // color
      0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f     // texture coordinate
    };
    
    GLuint VBOId;
    glGenVertexArrays(1, &VAOId);
    glBindVertexArray(VAOId);
    
    glGenBuffers(1, &VBOId);
    glBindBuffer(GL_ARRAY_BUFFER, VBOId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // specifiy position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)0); // stride can aslo be 3 * sizeof(GLfloat)
    glEnableVertexAttribArray(0);

    // specify color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(9 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    // specify texture coordinate
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, (GLvoid*)(18 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    VBOIdVec.push_back(VBOId);
}

#第三种方式:在单个VBO中交错属性

这个格式类似于:(xyzrgbstxyzrgbst...),我们需要手动指定偏移量和步长。

void prepareVertData_interleavedBatchVBO(GLuint& VAOId, std::vector<GLuint>& VBOIdVec)
{
    // interleaved data
    GLfloat vertices[] = {
        -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f,  // 0
        0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,  // 1
        0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,  // 2
    };
    
    GLuint VBOId;
    glGenVertexArrays(1, &VAOId);
    glBindVertexArray(VAOId);
    glGenBuffers(1, &VBOId);
    glBindBuffer(GL_ARRAY_BUFFER, VBOId);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // specify position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,8 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    // specify color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
        8 * sizeof(GLfloat),(GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    // specify texture coordinate
    glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 
        8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
    glEnableVertexAttribArray(2);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

    VBOIdVec.push_back(VBOId);
}

感谢 Derhass 的答案。


哇,我试图在大部分时间里理解三种方法的差异(主要是交错与其他方法之间的区别,以及如何正确使用偏移量),然后我看到了这个并排的比较,一切都变得清晰了。谢谢。;) 有没有什么想法哪种方法通常更快? - James Wilkins
根据我所阅读的建议(例如苹果iOS的建议),交错数据版本更快。@JamesWilkins - Viktor Sehr
我猜这是有道理的,因为系统可以轻松地将这些值“blit”到一个结构体中,而不是从单独的数组中逐个复制每个值。 - James Wilkins
1
sizeof(GL_FLOAT) looks incorrect and works by accident. Should be sizeof(GLfloat) - Hertz
@ViktorSehrvim 最快的方法是使用第二种方式。这是最快的方式,因为您可以利用完整的缓存来上传顶点。 - Elvis Dukaj
@ViktorSehrvim,最快的方法是使用第二种方式。这是最快的方法,因为您可以使用完整的缓存来上传顶点。 - Elvis Dukaj

23

步长(stride)和偏移量(offset)是以字节为单位指定的。您正在使用交错的顶点数组,其中位置和颜色均为4个浮点数。要从特定属性数组中的第i个元素转到下一个元素,需要8个浮点数的距离,因此stride应该是8*sizeof(GLfloat)。偏移量是缓冲区中每个属性数组的第一个元素的字节位置,在您的示例中,位置的偏移量为0,颜色的偏移量为4*sizeof(GLfloat)。


我的两个glVertexAttribPointer的步幅和偏移值应该是多少? - user2350858
1
正如我在答案中所写的:stride为8sizeof(GLfloat)(通常为32),位置偏移量为0,颜色偏移量为4sizeof(GLfloat)(就像你已经做的那样)。 - derhass
偏移量的类型必须是const void*,你该如何实现呢?我考虑过使用nullptr + offset,但类型不对。我尝试了(void*)0 + offset,但还没有成功(不确定还有什么问题)。 - John P
@JohnP (void*)offset - undefined

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