使用 VAO 在 OpenGL 中更改绘制的实例化顺序

Posted

技术标签:

【中文标题】使用 VAO 在 OpenGL 中更改绘制的实例化顺序【英文标题】:Instantiation order changing draw in OpenGL using VAO 【发布时间】:2015-04-20 19:25:28 【问题描述】:

我尝试使用 VAO、VBO 和 IBO 在平面上绘制一堆球体。在使用这些之前,一切都按预期绘制。在我开始使用这些之后,事情变得很奇怪。我不能在这里发布我的整个代码,因为我有 5 个类(但如有必要,我可以提供指向我的代码的链接),所以我会尝试发布我认为有用的内容。

通过这个类我可以画一个球体:

SphereShaderProgram::SphereShaderProgram(std::string vertexShaderPath, std::string fragmentShaderPath) : ProgramManager(vertexShaderPath, fragmentShaderPath)

    _sphereH = 20;
    _sphereW = 20;
    _vbo = 0;
    _vao = 0;
    _ibo = 0;
    CreateProgram();
    BuildSphere();
    BuildVAO();



SphereShaderProgram::~SphereShaderProgram()

    glDeleteVertexArrays(1, &_vao);
    glDeleteBuffers(1, &_vbo);
    glDeleteBuffers(1, &_ibo);



void SphereShaderProgram::DrawSphere(const glm::mat4 &Projection, const glm::mat4 &ModelView)

    _ModelViewProjection = Projection * ModelView;
    _ModelView = ModelView;

    Bind(); //glUseProgram

    glBindVertexArray(_vao);
    LoadVariables();
    glDrawElements(GL_TRIANGLES, _sphereIndexes.size(), GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    UnBind();



int SphereShaderProgram::Get1DIndex(int line, int column)

    return line * (int) _sphereH + column;



void SphereShaderProgram::BuildSphere()

    for (int l = 0; l < _sphereH - 1; l++)
    
        for (int c = 0; c < _sphereW - 1; c++)
        
            int v1_1 = Get1DIndex(l, c);
            int v2_1 = Get1DIndex(l + 1, c + 1);
            int v3_1 = Get1DIndex(l + 1, c);

            int v1_2 = Get1DIndex(l, c);
            int v2_2 = Get1DIndex(l, c + 1);
            int v3_2 = Get1DIndex(l + 1, c + 1);

            _sphereIndexes.push_back(v1_1);
            _sphereIndexes.push_back(v2_1);
            _sphereIndexes.push_back(v3_1);

            _sphereIndexes.push_back(v1_2);
            _sphereIndexes.push_back(v2_2);
            _sphereIndexes.push_back(v3_2);
        
    

    for (int l = 0; l < _sphereH; l++)
    
        for (int c = 0; c < _sphereW; c++)
        
            float theta = ((float) l / (_sphereH - 1)) * (float) PI;
            float phi = ((float) c / (_sphereW - 1)) * 2 * (float) PI;
            float x = sin(theta) * cos(phi);
            float z = sin(theta) * sin(phi);
            float y = cos(theta);

            _sphereCoordinates.push_back(x);
            _sphereCoordinates.push_back(y);
            _sphereCoordinates.push_back(z);
        
    



void SphereShaderProgram::BuildVAO()

    // Generate and bind the vertex array object
    glGenVertexArrays(1, &_vao);
    glBindVertexArray(_vao);

    // Generate and bind the vertex buffer object
    glGenBuffers(1, &_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glBufferData(GL_ARRAY_BUFFER, _sphereCoordinates.size() * sizeof(float), &_sphereCoordinates[0], GL_STATIC_DRAW);

    // Generate and bind the index buffer object
    glGenBuffers(1, &_ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, _sphereIndexes.size() * sizeof(unsigned int), &_sphereIndexes[0], GL_STATIC_DRAW);

    glBindVertexArray(0);



void SphereShaderProgram::LoadUniformVariables()

    glm::mat4 MVP = _ModelViewProjection;
    glm::mat4 MV = _ModelView;
    glm::mat3 N = glm::transpose(glm::inverse(glm::mat3(MV)));
    glm::vec4 AC = glm::vec4(0.2, 0.2, 0.2, 1.0);
    glm::vec4 DC = glm::vec4(0.7, 0.0, 0.0, 1.0);
    glm::vec4 SC = glm::vec4(0.1, 0.1, 0.1, 1.0);
    glm::vec3 LP = glm::vec3(1.0, 6.0, 4.0);

    // OpenGL Matrices 
    GLuint ModelViewProjection_location = glGetUniformLocation(GetProgramID(), "mvpMatrix");
    glUniformMatrix4fv(ModelViewProjection_location, 1, GL_FALSE, glm::value_ptr(MVP));

    GLuint ModelView_location = glGetUniformLocation(GetProgramID(), "mvMatrix");
    glUniformMatrix4fv(ModelView_location, 1, GL_FALSE, glm::value_ptr(MV));

    GLuint Normal_location = glGetUniformLocation(GetProgramID(), "normalMatrix");
    glUniformMatrix3fv(Normal_location, 1, GL_FALSE, glm::value_ptr(N));

    // Lighting 
    GLuint AmbientColor_location = glGetUniformLocation(GetProgramID(), "ambientColor");
    glUniform4fv(AmbientColor_location, 1, glm::value_ptr(AC));

    GLuint DiffuseColor_location = glGetUniformLocation(GetProgramID(), "diffuseColor");
    glUniform4fv(DiffuseColor_location, 1, glm::value_ptr(DC));

    GLuint SpecularColor_location = glGetUniformLocation(GetProgramID(), "specularColor");
    glUniform4fv(SpecularColor_location, 1, glm::value_ptr(SC));

    GLuint LightPosition_location = glGetUniformLocation(GetProgramID(), "vLightPosition");
    glUniform3fv(LightPosition_location, 1, glm::value_ptr(LP));



void SphereShaderProgram::LoadAtributeVariables()

    // Vertex Attributes
    GLuint VertexPosition_location = glGetAttribLocation(GetProgramID(), "vPosition");
    glEnableVertexAttribArray(VertexPosition_location);

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



void SphereShaderProgram::LoadVariables()

    LoadUniformVariables();
    LoadAtributeVariables();

还有一架飞机:

PlaneShaderProgram::PlaneShaderProgram(std::string vertexShaderPath, std::string fragmentShaderPath) : ProgramManager(vertexShaderPath, fragmentShaderPath)

    CreateProgram();
    _vbo = 0;
    _vao = 0;
    _ibo = 0;
    BuildPlane();
    BuildVAO();



PlaneShaderProgram::~PlaneShaderProgram()

    glDeleteVertexArrays(1, &_vao);
    glDeleteBuffers(1, &_vbo);
    glDeleteBuffers(1, &_ibo);



void PlaneShaderProgram::DrawPlane(const glm::mat4 &Projection, const glm::mat4 &ModelView)

    _ModelViewProjection = Projection * ModelView;
    _ModelView = ModelView;

    Bind();

    glBindVertexArray(_vao);
    LoadVariables();
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);

    UnBind();



void PlaneShaderProgram::BuildPlane()

    _coordinates[0]  = -1.0f;
    _coordinates[1]  =  0.0f;
    _coordinates[2]  = -1.0f;
    _coordinates[3]  = -1.0f;
    _coordinates[4]  =  0.0f;
    _coordinates[5]  =  1.0f;
    _coordinates[6]  =  1.0f;
    _coordinates[7]  =  0.0f;
    _coordinates[8]  =  1.0f;
    _coordinates[9]  =  1.0f;
    _coordinates[10] =  0.0f; 
    _coordinates[11] = -1.0f;

    _indexes[0] = 0;
    _indexes[1] = 1;
    _indexes[2] = 2;
    _indexes[3] = 0;
    _indexes[4] = 2;
    _indexes[5] = 3;



void PlaneShaderProgram::BuildVAO()

    // Generate and bind the vertex array object
    glGenVertexArrays(1, &_vao);
    glBindVertexArray(_vao);

    // Generate and bind the vertex buffer object
    glGenBuffers(1, &_vbo);
    glBindBuffer(GL_ARRAY_BUFFER, _vbo);
    glBufferData(GL_ARRAY_BUFFER, 12 * sizeof(GLfloat), _coordinates, GL_STATIC_DRAW);

    // Generate and bind the index buffer object
    glGenBuffers(1, &_ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(GLuint), _indexes, GL_STATIC_DRAW);

    glBindVertexArray(0);



void PlaneShaderProgram::LoadUniformVariables()

    // OpenGL Matrices
    GLuint ModelViewProjection_location = glGetUniformLocation(GetProgramID(), "mvpMatrix");
    glUniformMatrix4fv(ModelViewProjection_location, 1, GL_FALSE, glm::value_ptr(_ModelViewProjection));



void PlaneShaderProgram::LoadAtributeVariables()

    // Vertex Attributes
    GLuint VertexPosition_location = glGetAttribLocation(GetProgramID(), "vPosition");
    glEnableVertexAttribArray(VertexPosition_location);

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



void PlaneShaderProgram::LoadVariables()

    LoadUniformVariables();
    LoadAtributeVariables();

另一方面,这是我的主要内容:

int main(void)

    // Set the error callback  
    glfwSetErrorCallback(ErrorCallback);

    // Initialize GLFW  
    if (!glfwInit())
    
        printf("Error initializing GLFW!\n");
        exit(EXIT_FAILURE);
    

    // Set the GLFW window creation hints - these are optional  
    glfwWindowHint(GLFW_SAMPLES, 4);

    // Create a window and create its OpenGL context    
    GLFWwindow* window = glfwCreateWindow(width, height, "OpenGL 4 Base", NULL, NULL);

    // If the window couldn't be created  
    if (!window)
    
        fprintf(stderr, "Failed to open GLFW window.\n");
        glfwTerminate();
        exit(EXIT_FAILURE);
    

    // Sets the context of the specified window on the calling thread  
    glfwMakeContextCurrent(window);

    // Initialize GLEW
    glewExperimental = true;
    GLenum glewError = glewInit();
    if (glewError != GLEW_OK)
    
        printf("Error initializing GLEW! %s\n", glewGetErrorString(glewError));
        glfwDestroyWindow(window);
        glfwTerminate();
        exit(EXIT_FAILURE);
    

    glfwSetKeyCallback(window, KeyCallback);
    glfwSetWindowSizeCallback(window, WindowSizeCallback);
    glfwSetScrollCallback(window, ScrollCallback);

    // Set the view matrix
    glm::mat4 ModelView = glm::lookAt(glm::vec3(0.0f, 7.0f, 15.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

    // Init matrix stack
    glm_ModelViewMatrix.push(ModelView);

    PlaneShaderProgram PlaneShaderProgram("FloorVertexShader.txt", "FloorFragShader.txt");
    SphereShaderProgram SphereShaderProgram("ADSPerVertexVertexShader.txt", "ADSPerVertexFragShader.txt");
    //SphereShaderProgram SphereShaderProgram = SphereShaderProgram("ADSPerPixelVertexShader.txt", "ADSPerPixelFragShader.txt");

    // Set a background color  
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    // 3D objects
    glEnable(GL_DEPTH_TEST);

    float d = 2.0f;
    float p0 = -10.0f + d / 2;
    // Main Loop  
    while (!glfwWindowShouldClose(window))
    
        // Clear color buffer  
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Clone current modelview matrix, which can now be modified 
        glm_ModelViewMatrix.push(glm_ModelViewMatrix.top());
        
            //------- ModelView Transformations
            // Zoom in/out
            glm_ModelViewMatrix.top() = glm::translate(glm_ModelViewMatrix.top(), glm::vec3(0.0, 0.0, zoom));
            // Rotation
            glm_ModelViewMatrix.top() = glm::rotate(glm_ModelViewMatrix.top(), beta, glm::vec3(1.0, 0.0, 0.0));
            glm_ModelViewMatrix.top() = glm::rotate(glm_ModelViewMatrix.top(), alpha, glm::vec3(0.0, 0.0, 1.0));

            //------- Draw the plane
            glm_ModelViewMatrix.push(glm_ModelViewMatrix.top());
            
                glm_ModelViewMatrix.top() = glm::scale(glm_ModelViewMatrix.top(), glm::vec3(7.0f, 1.0f, 7.0f));

                PlaneShaderProgram.DrawPlane(Projection, glm_ModelViewMatrix.top());
            
            glm_ModelViewMatrix.pop();

            //------- Draw spheres
            for (int i = 0; i < 10; i++)
            
                for (int j = 0; j < 10; j++)
                
                    glm_ModelViewMatrix.push(glm_ModelViewMatrix.top());
                    
                        glm_ModelViewMatrix.top() = glm::scale(glm_ModelViewMatrix.top(), glm::vec3(0.5f, 0.5f, 0.5f));
                        glm_ModelViewMatrix.top() = glm::translate(glm_ModelViewMatrix.top(), glm::vec3(p0 + i * d, 1.0f, p0 + j * d));

                        SphereShaderProgram.DrawSphere(Projection, glm_ModelViewMatrix.top());
                    
                    glm_ModelViewMatrix.pop();
                
            
        
        glm_ModelViewMatrix.pop();

        // Swap buffers  
        glfwSwapBuffers(window);

        // Get and organize events, like keyboard and mouse input, window resizing, etc...  
        glfwPollEvents();
    

    // Close OpenGL window and terminate GLFW  
    glfwDestroyWindow(window);
    // Finalize and clean up GLFW  
    glfwTerminate();

    exit(EXIT_SUCCESS);

实例化平面然后是球体程序,我得到以下结果(根本没有平面):

改变顺序,就是结果:

我正试图找出我所缺少的线索,因为我不知道哪里出了问题。在使用 VAO 之前(仅使用 glVertexAttribPointerglDrawElements),一切都已正确绘制。

提前谢谢你。

【问题讨论】:

绘制时不要在每一帧都调用 LoadAtributeVariables 函数,而应该在 BuildVAO 函数中调用一次,因为 VAO 存储属性绑定。 当我这样做时,什么都没有画出来,@user3256930。 那么您可能还缺少其他东西。 VAO 的基本思想是它们存储属性绑定,因此您不必每次绘制时都调用它。本教程展示了如何正确设置它们并使用它们进行绘制:swiftless.com/tutorials/opengl4/4-opengl-4-vao.html 【参考方案1】:

问题在于glVertexAttribPointer() 调用的位置。您在 LoadAtributeVariables() 方法中调用它,而该方法又从 Draw*() 方法中调用。

这应该是 VAO 设置的一部分,原因如下:

在每次重绘时调用是低效的。此调用设置作为 VAO 状态一部分的状态。这就是首先使用 VAO 的全部想法。您可以在设置过程中将所有这些状态设置一次,然后只需要在绘制调用之前再次绑定 VAO,即可通过一次调用再次设置所有状态。 在您的情况下,VBO 在您拨打电话时未绑定。 glVertexAttribPointer() 设置属性以从当前绑定的 VBO 中提取数据,即绑定为GL_ARRAY_BUFFER 的缓冲区。

第一个问题只是性能问题。第二个是您的代码不起作用的原因,因为在调用 glVertexAttribPointer() 时您没有正确的 VBO 绑定。

要解决此问题,您只需将LoadAtributeVariables() 调用移动到BuildVAO(),在此位置:

// Generate and bind the vertex buffer object
glGenBuffers(1, &_vbo);
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
glBufferData(GL_ARRAY_BUFFER, _sphereCoordinates.size() * sizeof(float), &_sphereCoordinates[0], GL_STATIC_DRAW);

LoadAtributeVariables();

并将其从当前位置移除,以便在每次绘制调用之前不再调用它。

【讨论】:

嗨,@Reto。我做了你说的修改,现在我的绘图调用只包含 VAO 绑定、LoadUniformVariables 和 drawElements。但是,这导致程序崩溃。 :S 我错过了你说的话吗? 为了让它工作,我必须在 draw call 中进行 VBO 和 IBO 绑定(我已经评论过它)。对吗? 不,如果VAO的初始设置正确完成,您只需在draw call中绑定VAO。

以上是关于使用 VAO 在 OpenGL 中更改绘制的实例化顺序的主要内容,如果未能解决你的问题,请参考以下文章

OpenGL实例化数组奇怪的顶点位置

无法使用 VAO 和 EBO (openGL) 绘制多个对象

opengl从一个vao中绘制多个对象

如何在 OpenGL 中绘制由 5 个三角形组成的 VAO?

在 OpenGL 4 中创建第二个 VAO 并绘制两个形状

OpenGL有没有办法复制现有的VAO