为什么使用 glDrawElements() 会导致分段错误?

Why does using glDrawElements() causes a Segmentation Fault?

我正在学习 OpenGL,我使用 this guide and this video 在 C++ 中编写了以下代码。 我还使用 GLFW 进行上下文创建,使用 GLEW 进行 GL 函数 大部分Shader class 是从链接的视频中复制的,

问题是在主循环内使用 glDrawElements() 进行渲染导致出现分段错误:

Segmentation fault


------------------
(program exited with code: 139)
Press return to continue

使用 glDrawArrays() 时我可以毫无问题地绘图。

有谁知道这可能是由什么引起的? 我认为错误可能取决于着色器 class 的实现,因为我在其他程序中使用了 glDrawArrays() 而没有使用它 class 并且关心主函数中的着色器。

program.cpp

//INCLUDE AND DECLARATIONS
#include <iostream>
#include <fstream> 
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
#include "Shader.h"

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
unsigned long getFileLength(std::ifstream& file);
int loadshader(char* filename, GLchar** ShaderSource, unsigned long* len);

const GLuint WIDTH = 800, HEIGHT = 600;


//VERTEX DATA
float data[] = {
//    X      Y     R     G     B   
    -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, // Top-left
     0.5f,  0.5f, 0.0f, 1.0f, 0.0f, // Top-right
     0.5f, -0.5f, 0.0f, 0.0f, 1.0f, // Bottom-right
    -0.5f, -0.5f, 1.0f, 1.0f, 1.0f  // Bottom-left
};

 GLuint elements[] = {
        0, 1, 2,
        2, 3, 0
    };

//main
int main()
{   //INIT GLFW AND WINDOW
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    glfwSetKeyCallback(window, key_callback);
    glewExperimental = GL_TRUE;
    glewInit();
    glViewport(0, 0, WIDTH, HEIGHT);



    //ALLOCATE BUFFERS
        //VERTEX ARRAY BUFFER
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
        //ELEMENT ARRAY BUFFER
    GLuint ebo;
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);
        //CREATE SHADER
    Shader shader("./shaders/basicShader");


    // main loop
    while (!glfwWindowShouldClose(window))
    {
        shader.Bind();
        glfwPollEvents();                       //window events
        glClearColor(1.0f, 0.0f, 0.5f, 0.5f);   //background
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

        glfwSwapBuffers(window);                //update window
    }

    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &ebo);
    glfwTerminate();
    return 0;
}


void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, GL_TRUE);
}

Shader.h

#include <iostream>
#include <fstream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>


class Shader
{
        public:
            Shader(const std::string& filepath);
            ~Shader();
            void Bind();
        private:
            static const GLuint NUM_SHADERS = 2;
            GLuint program;  
            GLuint shaders[NUM_SHADERS];

            std::string LoadShader(const std::string& fileName);
            void CheckShaderError(GLuint shader, GLuint flag, bool isProgram, const std::string& errorMessage);
            GLuint CreateShader(const std::string& text, unsigned int type);

};

Shader.cpp

#include "Shader.h"

Shader::Shader(const std::string& filepath)
{
    program = glCreateProgram();
    shaders[0] = CreateShader(LoadShader(filepath + ".vs"), GL_VERTEX_SHADER);
    shaders[1] = CreateShader(LoadShader(filepath + ".fs"), GL_FRAGMENT_SHADER);

    for(unsigned int i = 0; i < NUM_SHADERS; i++)
    {
        glAttachShader(program, shaders[i]);
    }
    glBindAttribLocation(program, 0, "position");
    glBindFragDataLocation(program, 0, "outColor");

    glLinkProgram(program);
    CheckShaderError(program, GL_LINK_STATUS, true, "Error linking shader program");

    glValidateProgram(program);
    CheckShaderError(program, GL_LINK_STATUS, true, "Invalid shader program");




    GLuint vao;
    glGenVertexArrays(1, &vao);     
    glBindVertexArray(vao);     

    GLint posAttrib = glGetAttribLocation(program, "position");
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), 0);
    glEnableVertexAttribArray(posAttrib);

    GLint AttribColor = glGetAttribLocation(program, "color");
    glVertexAttribPointer(AttribColor, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)(2*sizeof(float)));
    glEnableVertexAttribArray(AttribColor);


}

Shader::~Shader()
{
    for(unsigned int i = 0; i < NUM_SHADERS; i++)
    {
        glDetachShader(program, shaders[i]);
        glDeleteShader(shaders[i]);
    }
    glDeleteProgram(program);
}

void Shader::Bind()
{
        glUseProgram(program);
}


//loads shaders from files
std::string Shader::LoadShader(const std::string& fileName)
{
    std::ifstream file;
    file.open((fileName).c_str());

    std::string output;
    std::string line;

    if(file.is_open())
    {
        while(file.good())
        {
            getline(file, line);
            output.append(line + "\n");
        }
    }
    else
    {
        std::cerr << "Unable to load shader: " << fileName << std::endl;
    }

    return output;
}
//Checks for eventual errors in shaders
void Shader::CheckShaderError(GLuint shader, GLuint flag, bool isProgram, const std::string& errorMessage)
{
    GLint success = 0;
    GLchar error[1024] = { 0 };

    if(isProgram)
        glGetProgramiv(shader, flag, &success);
    else
        glGetShaderiv(shader, flag, &success);

    if(success == GL_FALSE)
    {
        if(isProgram)
            glGetProgramInfoLog(shader, sizeof(error), NULL, error);
        else
            glGetShaderInfoLog(shader, sizeof(error), NULL, error);

        std::cerr << errorMessage << ": '" << error << "'" << std::endl;
    }
}

GLuint Shader::CreateShader(const std::string& text, unsigned int type)
{
    GLuint shader = glCreateShader(type);
        if(shader == 0)
            std::cerr << "error allocating shader" << std:: endl;

    const GLchar* p[1];
    p[0] = text.c_str();
    GLint lengths[1];
    lengths[0] = text.length();

    glShaderSource(shader, 1, p, lengths);
    glCompileShader(shader);
    CheckShaderError(shader, GL_COMPILE_STATUS, false, "Error compiling shader!");

    return shader;
}

问题出在您的索引缓冲区绑定上。索引缓冲区 (GL_ELEMENT_ARRAY_BUFFER) 绑定是 VAO 状态的一部分。跳过不相关的调用,你有以下整体序列:

...
glGenBuffers(1, &ebo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);
...
GLuint vao;
glGenVertexArrays(1, &vao);     
glBindVertexArray(vao);     
...
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

由于 GL_ELEMENT_ARRAY_BUFFER 绑定是 VAO 状态的一部分,当您调用时,当前索引缓冲区绑定将替换为 VAO 状态中的索引缓冲区绑定:

glBindVertexArray(vao);     

由于在绑定 VAO 时您从不绑定索引缓冲区,这意味着 VAO 状态下的索引缓冲区绑定为 0。因此,在此调用后您没有绑定索引缓冲区。

接下来发生的是您使用最后一个参数 0 进行 glDrawElements() 调用。如果没有索引缓冲区绑定,最后一个参数将被解释为指向 CPU 内存的指针。因此 OpenGL 尝试读取地址 0 处的索引数据,这导致了崩溃。

要解决此问题,您只需在绑定 VAO 时绑定索引缓冲区即可。您可以通过更改调用顺序和 create/bind 设置索引缓冲区之前的 VAO 来做到这一点。或者你可以在设置顶点属性状态的时候再绑定一次。例如,在 Shader 构造函数的最后,在 glVertexAttribPointer() 和相关调用之后,添加此调用:

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);