C++/OpenGL:VAO 单独工作但不能一起工作

C++/OpenGL: VAOs work individually but not together

尝试使用 C++/OpenGL 实现 VAO 的基本示例时,我遇到了一个特殊的问题,在搜索了一段时间后我还没有找到解决方案。我的程序应该将纹理(CT 图像)渲染为正方形,然后在顶部绘制多边形(使用 GL_LINE_LOOP)以勾勒出特定器官的轮廓。首先,我在初始化时为 CT 图像创建一个 VAO:

// Generates textures for CT images
void TxPlanSys::InitSimCT(SimulationCT &S){
    GLuint VBO[2];
    GLfloat Vertices[] = {
        0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f
    };
    GLfloat UvData[] = {
        0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f
    };
    for(int i = 0; i < 6; i++){
        Vertices[3*i] *= (S.Ny*S.dy);
        Vertices[3*i+1] *= (S.Nz*S.dz);
    }

    // Vertex array object for image surface
    glGenVertexArrays(1,&SimCtVaoID);
    glBindVertexArray(SimCtVaoID);
    glGenBuffers(2,VBO);

    // Vertex buffer
    glBindBuffer(GL_ARRAY_BUFFER,VBO[0]);
    glBufferData(GL_ARRAY_BUFFER,sizeof(Vertices),Vertices,GL_STATIC_DRAW);

    GLuint VertexAttributeID = 0;
    glVertexAttribPointer(VertexAttributeID,3,GL_FLOAT,GL_FALSE,0,0);
    glEnableVertexAttribArray(VertexAttributeID);

    // UV buffer
    glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
    glBufferData(GL_ARRAY_BUFFER,sizeof(UvData),UvData,GL_STATIC_DRAW);

    GLuint UvAttributeID = 1;
    glVertexAttribPointer(UvAttributeID,2,GL_FLOAT,GL_FALSE,0,0);
    glEnableVertexAttribArray(UvAttributeID);

    // Unbind and clean up
    glBindVertexArray(0);
    glDeleteBuffers(2,VBO);

    // Initialize textures for CT images
    glGenTextures(S.Nx,&CtTextureID);
    for(int n = 0; n < S.Nx; n++){
        CurrentSlice = n;
        MakeImage(S);
        glBindTexture(GL_TEXTURE_2D,CtTextureID+n);
        glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,S.Ny,S.Nz,0,GL_RGB,GL_UNSIGNED_BYTE,MainImage);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    CurrentSlice = 0;
}

接下来,我在初始化时为特定轮廓集创建一个 VAO:

void TxPlanSys::InitContour(Structure * S){
    GLuint ContourVertexBuffer, ColorBuffer;
    float buffer[(const int)(S->PointIndex*3)];

    glGenVertexArrays(1,&ContourVaoID);
    glBindVertexArray(ContourVaoID);

    // Vertex buffer
    glGenBuffers(1,&ContourVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER,ContourVertexBuffer);
    for(int i = 0; i < S->PointIndex; i++){
        buffer[3*i+2] = 0.0;
        buffer[3*i] = S->SurfacePoints[3*i+1];
        buffer[3*i+1] = S->SurfacePoints[3*i+2];
    }
    glBufferData(GL_ARRAY_BUFFER,sizeof(buffer),buffer,GL_STATIC_DRAW);

    GLuint VertexAttributeID = 0;
    glVertexAttribPointer(VertexAttributeID,3,GL_FLOAT,GL_FALSE,0,0);
    glEnableVertexAttribArray(VertexAttributeID);

    // Unbind and clean up
    glBindVertexArray(0);
    glDeleteBuffers(1,&ContourVertexBuffer);
}

这就是事情变得不稳定的地方。我的渲染代码如下:

static void MainDisplay(){
    // Clear window to black
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Model-View-Projection matrix
    glm::mat4 Projection = glm::perspective((3.1415f*30.0f/180.0f), MyToolkit.AspectRatio, 0.1f, 120.0f);
    glm::mat4 View = glm::lookAt(
        glm::vec3(MyToolkit.CameraPos[0],MyToolkit.CameraPos[1],MyToolkit.CameraPos[2]),
        glm::vec3(MyToolkit.CameraPos[0],MyToolkit.CameraPos[1],0),
        glm::vec3(0,1,0)
    );
    glm::mat4 Model = glm::mat4(1.0f);
    glm::mat4 MVP = Projection * View * Model;

    // Set shader & send MVP matrix to shader
    glUseProgram(TPS.ImageShaderID);
    glUniformMatrix4fv(TPS.ImageMvpID, 1, GL_FALSE, &MVP[0][0]);

    // Draw square for image panel
    glBindVertexArray(TPS.SimCtVaoID);
    glBindTexture(GL_TEXTURE_2D,TPS.CtTextureID+TPS.CurrentSlice);
    glDrawArrays(GL_TRIANGLES,0,6);

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

    // Set shader & send MVP matrix to shader
    glUseProgram(TPS.ContourShaderID);
    glUniformMatrix4fv(TPS.ContourMvpID, 1, GL_FALSE, &MVP[0][0]);

    // Draw contours
    glBindVertexArray(TPS.ContourVaoID);
    glPointSize(3.0);
    glDrawArrays(GL_POINTS,0,9);
    glDrawArrays(GL_LINE_LOOP,0,9);

    // Swap buffers to render
    glutSwapBuffers();
}

当我仅注释掉轮廓的 glDraw 命令时,CT 图像绘制得很好。同样,当我仅注释掉 CT 图像的 glDraw 命令时,轮廓绘制得很好。但是,当我尝试在同一个函数调用中渲染它们时,轮廓 VAO 从 CT 图像 VAO 中获取顶点缓冲区并渲染它。

我希望我可以插入图片,但是我的代表。还不够高....

在查看了几个地方并阅读了有关 VAO 的内容后,我感到很困惑。任何帮助将不胜感激。这是我的比赛。信息:Ubuntu 14.04,英特尔 i5 CPU,ATI Radeon 显卡,运行 OpenGL 4.3。谢谢!

通过删除仍在使用的 VBO,您已经进入了 OpenGL 对象生命周期的美妙世界(是的,这是讽刺)。这是一个充满惊喜的世界,除非您喜欢花时间阅读规范文档。它还涉及重大的不一致。例如,程序和着色器对象的管理方式与其他类型的对象(如纹理和缓冲区)截然不同。

好消息是,如果您在使用完对象之前不删除它们,就可以避免所有这些陷阱。因为这通常很容易管理,所以我认为这通常是最好的策略。

不过既然你去了,那就试着了解一下具体情况吧。您的代码的关键部分是:

glGenVertexArrays(1, &ContourVaoID);
glBindVertexArray(ContourVaoID);

glGenBuffers(1, &ContourVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, ContourVertexBuffer);
...
glVertexAttribPointer(...);
...
glBindVertexArray(0);
glDeleteBuffers(1, &ContourVertexBuffer);

您正在删除一个被 VAO 引用的 VBO,而该 VAO 稍后仍在使用。这在某种程度上是可以的,但有一些注意事项。

对象名称与对象

充分理解这一点的一个重要方面是对象名称和对象之间的区别。当你打电话时:

glGenBuffers(1, &bufId);

您创建了一个缓冲区 name。此时,还没有创建缓冲区对象。实际缓冲区 object 仅在您第一次绑定名称时创建:

glBindBuffer(GL_ARRAY_BUFFER, bufId);

这表明缓冲区 name 和缓冲区 object 具有不同的生命周期。

规格说明

有了这个理解,我们就可以看到规范对删除行为的看法:

When a buffer, texture, sampler, renderbuffer, query, or sync object is deleted, its name immediately becomes invalid (e.g. is marked unused), but the underlying object will not be deleted until it is no longer in use.

其中 "in use" 包括被容器对象引用,如本例中的 VAO。因此,作为第一个近似值,VBO 在您的代码调用序列之后确实仍然存在。但是,名称 (id) 不再有效。有一长段描述后果:

Caution should be taken when deleting an object attached to a container object (such as a buffer object attached to a vertex array object, or a renderbuffer or texture attached to a framebuffer object), or a shared object bound in multiple contexts. Following its deletion, the object’s name may be returned by Gen* commands, even though the underlying object state and data may still be referred to by container objects, or in use by contexts other than the one in which the object was deleted. Such a container or other context may continue using the object, and may still contain state identifying its name as being currently bound, until such time as the container object is deleted, the attachment point of the container object is changed to refer to another object, or another attempt to bind or attach the name is made in that context. Since the name is marked unused, binding the name will create a new object with the same name, and attaching the name will generate an error.

坦率地说,关于生成错误的最后一部分我并不完全理解。但我认为可以肯定地说,这会导致混乱的局面。按照我的阅读方式,您基本上可以得到 2 个 had/have 同名的对象,并且事情变得很难看。

结论

在上面代码的末尾,您基本上可以安全地使用引用您删除的缓冲区的 VAO。但是当您为第二个对象分配一个新缓冲区时,事情会变得更加复杂和有问题。

除非你喜欢痛苦,否则最好的选择是此时不删除 VBO。保留名称,并将其与使用它的 VAO 同时删除。

顺便说一句:上面的一些内容仍然是简化的。例如,如果在绑定使用它的 VAO 时删除一个 VBO,则该 VBO 会自动与 VAO 解除绑定,并立即删除。正如我在开头所说:欢迎来到 OpenGL 对象生命周期的精彩世界。