std::vector 和 VBO 仅渲染最后一个形状 [重复]

Posted

技术标签:

【中文标题】std::vector 和 VBO 仅渲染最后一个形状 [重复]【英文标题】:std::vector and VBOs render only the last shape [duplicate] 【发布时间】:2016-11-29 03:39:26 【问题描述】:

我遇到了一个奇怪的问题。基本上我有 Mesh 类取决于一个标志,我可以画一个点、一条线或一个三角形。例如,如果我想画两条线,我可以这样做

    Vertex vertices1[] = 
        Vertex(glm::vec3(-.5, -.5, 0)),
        Vertex(glm::vec3(  0,  .5, 0))
    ;

    Vertex vertices2[] = 
        Vertex(glm::vec3(  .5, -.5, 0)),
        Vertex(glm::vec3( -.5,  .5, 0))
    ;

    Mesh mesh1(vertices1, sizeof(vertices1)/sizeof(vertices1[0]), 'L');
    Mesh mesh2(vertices2, sizeof(vertices2)/sizeof(vertices2[0]), 'L');

// Rendering Loop:
while( Window.isOpen() )
        ...
        //================( Rendering )=========================
        ourShader.Use();
        mesh1.draw();
        mesh2.draw();
        //======================================================
        ...
    

结果是

现在我想使用std::vector<Mesh> 并遍历网格。我的尝试如下

std::vector<Mesh> meshes;
meshes.push_back(mesh1);
meshes.push_back(mesh2);

while( Window.isOpen() )
    ...
    //================( Rendering )=========================
    ourShader.Use();
    for ( int i(0); i < meshes.size(); ++i )
        meshes[i].draw();
    //======================================================
    ...

用前面的方法,只画了最后一条线,这就是结果

此外,一旦我使用.push_back(),即使我不循环遍历向量,也会绘制最后一行。我不明白为什么使用std::vector 会降低渲染效果。我什至尝试过meshes[0].draw(),但没有运气。有什么建议吗?


编辑: 这是Mesh类的构造函数

#include <iostream>
#include <vector>
#include <glm/glm.hpp>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "display.h"
#include "keyboard.h"
#include "shader.h"

class Vertex

public:
    Vertex(const glm::vec3& p) : m_position(p)
    

private:
    glm::vec3 m_position;

;

class Mesh

public:
    Mesh(Vertex* vertices, unsigned int numVertices, const char& flag);
    ~Mesh();
    void draw();
private:
    enum
        POSITION_VB,
        NUM_BUFFERS
    ;

    GLuint m_vertexArrayObject;
    GLuint m_vertexArrayBuffers[NUM_BUFFERS];
    unsigned int m_drawCount;

    char m_flag;
;


Mesh::Mesh(Vertex* vertices, unsigned int numVertices, const char& flag) : m_flag(flag), m_drawCount(numVertices)


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

    glGenBuffers(NUM_BUFFERS, m_vertexArrayBuffers);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexArrayBuffers[POSITION_VB]);
    glBufferData(GL_ARRAY_BUFFER, numVertices*sizeof(vertices[0]), vertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindVertexArray(0);


Mesh::~Mesh()

    glDeleteVertexArrays(1, &m_vertexArrayObject);
    glDeleteBuffers(1, m_vertexArrayBuffers);


void Mesh::draw()

    switch(m_flag)
    
        case 'P':
            glBindVertexArray(m_vertexArrayObject);
            glDrawArrays(GL_POINTS, 0, m_drawCount);
            glBindVertexArray(0);
        break;
        case 'L':
            glBindVertexArray(m_vertexArrayObject);
            glDrawArrays(GL_LINES, 0, m_drawCount);
            glBindVertexArray(0);
        break;
        case 'T':
            glBindVertexArray(m_vertexArrayObject);
            glDrawArrays(GL_TRIANGLES, 0, m_drawCount);
            glBindVertexArray(0);
        break;
    





int main(void)

    Display Window(800, 600, "OpenGL Window");
    Keyboard myKeyboard( Window.getWindowPointer() );

    Vertex vertices1[] = 
        Vertex(glm::vec3(-.5, -.5, 0)),
        Vertex(glm::vec3(  0,  .5, 0))
    ;

    Vertex vertices2[] = 
        Vertex(glm::vec3(  .5, -.5, 0)),
        Vertex(glm::vec3( -.5,  .5, 0))
    ;

    Mesh mesh1(vertices1, sizeof(vertices1)/sizeof(vertices1[0]), 'L');
    Mesh mesh2(vertices2, sizeof(vertices2)/sizeof(vertices2[0]), 'L');

    std::vector<Mesh> meshes;
    meshes.emplace_back(mesh1);
    meshes.emplace_back(mesh2);
    std::cout << meshes.size() << std::endl;
    //*****************( SHADER )************************
    Shader ourShader("shader.vs", "shader.frag");

    glEnable(GL_PROGRAM_POINT_SIZE);


    while( Window.isOpen() )
        Window.PollEvents();
        Window.clear();

        //================( Rendering )=========================
        ourShader.Use();
        //mesh1.draw();
        //mesh2.draw();
        for ( int i(0); i < meshes.size(); ++i )
            meshes[i].draw();
        //meshes[0].draw();
        //meshes[1].draw();
        //======================================================

        Window.SwapBuffers();
    



    glfwTerminate();
    return 0;

着色器

#version 330 core                      

out vec4 color;                        
void main()                            
                                      
    color = vec4(1.0f,0.5f,0.2f,1.0f); 
   

#version 330 core                                                

layout (location = 0) in vec3 position; 

void main()                                                      
         
    gl_PointSize = 10.0;
    gl_Position = vec4(position, 1.0); 
   

【问题讨论】:

您的代码看起来不错。如果没有Mesh 代码,很难说出问题所在。它是否包含 VAO 描述符?也许问题出在它的赋值运算符或复制构造函数上?如果您不创建 mesh1mesh2 对象,而是使用带有适当参数的 std::vector::emplace 会发生什么? @Sergey,是的,它确实包含 VAO。请查看更新。我试过std::vector::emplace 有同样的问题。 如果你以相反的顺序遍历你的向量......是否只绘制了第一个? 'draw'是否有可能擦除场景? 这可能是复制构造函数的问题。没有看到MCVE,很难远程解决您的问题。 @ybungalobill,我会尽快准备一份。 【参考方案1】:

正如我所怀疑的,问题出在(缺少)复制构造函数上。默认的只是复制所有成员。结果,您的 VAO 和缓冲区被多次删除,甚至在您设法绘制任何东西之前(向量在重新分配期间移动,如果它们不能移动,它们就会复制)。根据经验:如果你有一个非默认析构函数,你还必须实现一个复制构造函数和一个赋值运算符,或者如果你的类不是可复制的,则显式删除它们。

对于您的具体情况,解决方案是:

    快速解决方案:将指向网格的指针存储在向量中:

    std::vector<Mesh*> meshes;
    meshes.emplace_back(&mesh1);
    meshes.emplace_back(&mesh2);
    

    正确的解决方案:使用适当的 RAII 进行资源管理。使用unique_ptr technique from here,您的 MCVE 代码变为:

    class Mesh
    
    public:
        Mesh(Vertex* vertices, unsigned int numVertices, const char& flag);
        void draw();
    private:
        //...
        GLvertexarray m_vertexArrayObject;
        GLbuffer m_vertexArrayBuffers[NUM_BUFFERS];
        unsigned int m_drawCount;
        char m_flag;
    ;
    
    
    Mesh::Mesh(Vertex* vertices, unsigned int numVertices, const char& flag) : m_flag(flag), m_drawCount(numVertices)
    
        GLuint id;
        glGenVertexArrays(1, &id);
        glBindVertexArray(id);
        m_vertexArrayObject.reset(id);
        for(int i = 0; i < NUM_BUFFERS; ++i)
        
            glGenBuffers(1, &id);
            glBindBuffer(GL_ARRAY_BUFFER, id);
            m_vertexArrayBuffers[i].reset(id);
            glBufferData(GL_ARRAY_BUFFER, numVertices*sizeof(vertices[0]), vertices, GL_STATIC_DRAW);
        
    
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glBindVertexArray(0);
    
    
    void Mesh::draw()
    
        switch(m_flag)
        
            case 'P':
                glBindVertexArray(m_vertexArrayObject.get());
                glDrawArrays(GL_POINTS, 0, m_drawCount);
                glBindVertexArray(0);
            break;
            case 'L':
                glBindVertexArray(m_vertexArrayObject.get());
                glDrawArrays(GL_LINES, 0, m_drawCount);
                glBindVertexArray(0);
            break;
            case 'T':
                glBindVertexArray(m_vertexArrayObject.get());
                glDrawArrays(GL_TRIANGLES, 0, m_drawCount);
                glBindVertexArray(0);
            break;
        
    
    
    
    int main()
    
        //...
    
        Mesh mesh1(vertices1, sizeof(vertices1)/sizeof(vertices1[0]), 'L');
        Mesh mesh2(vertices2, sizeof(vertices2)/sizeof(vertices2[0]), 'L');
    
        std::vector<Mesh> meshes;
        meshes.emplace_back(std::move(mesh1));
        meshes.emplace_back(std::move(mesh2));
    
        // ...
    
        return 0;
    
    

    请注意不再需要定义析构函数,并且您的类会自动变为可移动但不可复制。此外,如果您有 OpenGL 4.5 或 ARB_direct_state_access,那么事情会变得更加简单。

【讨论】:

【参考方案2】:

编辑

主要问题是,当您将 Mesh 对象添加到向量时会调用析构函数,因此会清理底层数据。 进一步阅读:Why does my class's destructor get called when I add instances to a vector? | What is The Rule of Three?

我会亲自为我的Mesh 类创建单独的init_buffersfree_buffers 方法并适当地使用它们。 (获取 OpenGL 上下文后初始化缓冲区,关闭窗口时释放缓冲区。) 这样您就可以在实际拥有 OpenGL 上下文之前开始构建网格(并将它们添加到场景中)。


我已经实现了缺少的代码部分,并使用 CLion 使用 GLFW 尝试了您的代码。

它有效。在此处查看代码/CLion 项目:OpenGLSandbox/main.cpp

我添加的唯一代码基本上就是这些,所以轮到你找出差异/错误了。

// Constants
const size_t NUM_BUFFERS = 1;
const size_t POSITION_VB = 0;

// Vertex class
class Vertex 
private:
    glm::vec3 mCoords;
public:
    Vertex(glm::vec3 coords) : mCoords(coords) ;
;

// Mesh class
class Mesh 
private:
    GLuint m_vertexArrayObject;
    char m_flag;
    unsigned int m_drawCount;
    GLuint m_vertexArrayBuffers[NUM_BUFFERS];
public:
    /* your ctor and draw method */

【讨论】:

感谢您的回答。我可能会在我的 Mac 上编译相同的代码,看看这是否有什么不同。

以上是关于std::vector 和 VBO 仅渲染最后一个形状 [重复]的主要内容,如果未能解决你的问题,请参考以下文章

C++ VBO 渲染问题

OpenGL:将 VBO 与 std::vector 一起使用

std::vector<MyClass*> 的 VBO 和正确的步幅

如何仅对 std::vector 中的子集进行排序?

Cylinder VBO OpenGL:我做错了啥?

尝试擦除最后一个std :: vector元素时程序崩溃