2

尝试使用 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,Intel i5 CPU,ATI Radeon 显卡,运行 OpenGL 4.3。谢谢!

4

1 回答 1

1

通过删除仍在使用的 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);

您创建一个缓冲区名称。此时,尚未创建任何缓冲区对象。实际的缓冲区对象仅在您第一次绑定名称时创建:

glBindBuffer(GL_ARRAY_BUFFER, bufId);

这表明缓冲区名称和缓冲区对象具有不同的生命周期。

规格说明了什么

有了这种理解,我们可以看到规范对删除行为的描述:

当缓冲区、纹理、采样器、渲染缓冲区、查询或同步对象被删除时,其名称立即变为无效(例如标记为未使用),但在不再使用之前不会删除底层对象。

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

删除附加到容器对象的对象(例如附加到顶点数组对象的缓冲区对象,或附加到帧缓冲区对象的渲染缓冲区或纹理)或绑定在多个上下文中的共享对象时应小心。在其删除之后,对象的名称可能由 Gen* 命令返回,即使底层对象状态和数据仍可能被容器对象引用,或者被删除对象所在的上下文之外的上下文使用。这样的容器或其他上下文可以继续使用该对象,并且可能仍然包含将其名称标识为当前绑定的状态,直到容器对象被删除,容器对象的附着点被更改为引用另一个对象,或在该上下文中尝试绑定或附加名称。

坦率地说,我并没有完全了解关于生成错误的最后一部分。但我认为可以肯定地说这会导致混乱的局面。按照我的阅读方式,您基本上可以得到两个具有/具有相同名称的对象,并且事情变得丑陋。

结论

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

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

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

于 2015-07-29T05:17:07.777 回答