五、OpenGL 简单开始 --- 索引缓冲区(Index Buffer) 与 错误处理 (Error Dealing)

上一篇:四、OpenGL 简单开始 --- 顶点着色器(shader)

1. 为什么要有 索引缓冲区 (Index Buffer)

  • 画一个长方形,由两个三角形组成

image

  • 修改数据代码
// !!!!!!!!!!! 数据  !!!!!!!!!!!
float posintions[] = {
    -0.5f, -0.5f,
     0.5f, -0.5f,
     0.5f, 0.5f,

     0.5f, 0.5f,
    -0.5f, 0.5f,
    -0.5f, -0.5f
};
// !!!!!!!!!!!数据 !!!!!!!!!!!

所以,这里发现问题点有重复,这样存取会浪费 GPU 空间。
复杂模型的三角形更多,这样的点也会更多,三角形的渲染需要三个完整的点,意味着每个三角形的点都要单独定义;这时如果我们有一个索引缓冲区来存放所有点(不包括重复的),然后需要哪个点只要通过索引取数据就可以很好的节省空间。

2. 代码参考

2.1 添加数据索引数组

// !!!!!!!!!!! 数据  !!!!!!!!!!!
float posintions[] = {
    -0.5f, -0.5f, // 0
     0.5f, -0.5f, // 1
     0.5f,  0.5f, // 2
    -0.5f,  0.5f  // 3
};
// 索引数组
unsigned int indices[] = {
    0, 1, 2,
    2, 3, 0
};
// !!!!!!!!!!!数据 !!!!!!!!!!!

2.2 生成和绑定索引缓冲区

GL 的 API 参考地址
主要学习 GL_ELEMENT_ARRAY_BUFFERGL_ARRAY_BUFFER 的区别,glDrawElementsglDrawArrays 的区别

// !!!!!!!!!!! 缓冲区  !!!!!!!!!!!
/* 数据缓冲区 */
unsigned int buffer;
// 生成缓冲区
GLCall(glGenBuffers(1, &buffer));  // 缓冲区个数 和 缓冲区编号
// 绑定缓冲区 给 缓冲区数组
GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));
// 存入数据
GLCall(glBufferData
(
    GL_ARRAY_BUFFER,  // 缓冲区对象
    6 * 2 * sizeof(float), // 设置大小
    posintions, // 数据
    GL_STATIC_DRAW // 使用方式
));

/* 索引缓冲区 */ 
unsigned int ibo;
// 生成缓冲区
GLCall(glGenBuffers(1, &ibo));  // 缓冲区个数 和 缓冲区编号
// 绑定元素缓冲区 给 缓冲区数组
GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));
// 存入数据
GLCall(glBufferData
(
    GL_ELEMENT_ARRAY_BUFFER,  // 缓冲区对象
    6 * sizeof(unsigned int), // 设置大小
    indices, // 数据
    GL_STATIC_DRAW // 使用方式
));
// !!!!!!!!!!! 缓冲区  !!!!!!!!!!!
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
    /* Render here */
    glClear(GL_COLOR_BUFFER_BIT);

    // 画一下
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);  // 数组绑定过了,所以传 nullptr 就可以了 

    /* Swap front and back buffers */
    glfwSwapBuffers(window);

    /* Poll for and process events */
    glfwPollEvents();
}

2.3 结果

image

3. 错误处理(Error Dealing)

GL 的 API 参考地址

3.1 glGetError

所有版本都兼容,返回错误状态标记。

3.1.1 错误重置与错误获取

/* ------------------------- 错误处理 ------------------------- */
/* glGetError 方式 */
// 使用前重置
static void GLClearError()
{
    // 获取所有错误,确保没有之前的遗留的错误(每次循环都需要重置,否则会延续到下次使用)
    while (glGetError() != GL_NO_ERROR);
}
// 状态码对应
static void GLCheckError()
{
    // 获取所有错误(每次循环都需要重置,否则会延续到下次使用)
    while (GLenum error = glGetError())
    {
        cout << "[OpenGL Error] (" << error << ")" << endl;
    }
}
/* ------------------------- 错误处理 ------------------------- */

3.1.2 制造错误,查看结果

main 内修改渲染 glDrawElementsGL_UNSIGNED_INTGL_INT

......
// 先清除
GLClearError();
// 再执行
glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr);
// 再检查错误
GLCheckError();
......

image

得到错误码 1280,转为 16 进制为 0x0500

image

查找 glew 头文件 0x0500 的含义是 GL_INVALID_ENUM。(可以写个函数,转化这些数字)

3.1.3 利用断言终止在错误处

/* 断言终止 */
// 如果是 false,则添加断点终止
#define ASSERT(x) if (!(x)) __debugbreak(); // MSBC 添加断点方式

/* ------------------------- 错误处理 ------------------------- */
/* glGetError 方式 */
// 使用前重置
static void GLClearError()
{
    // 获取所有错误,确保没有之前的遗留的错误(每次循环都需要重置,否则会延续到下次使用)
    while (glGetError() != GL_NO_ERROR);
}
// 状态码对应
static bool GLLogCall()
{
    // 获取所有错误(每次循环都需要重置,否则会延续到下次使用)
    while (GLenum error = glGetError())
    {
        cout << "[OpenGL Error] (" << error << ")" << endl;
        return false;
    }
    return true;
}
/* ------------------------- 错误处理 ------------------------- */
......
// 先清除
GLClearError();
// 再执行
glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr);
// 再检查错误(断言)
ASSERT(GLLogCall());
......

image

清晰了,知道执行到哪一行出问题了。
但可不可以更限定一点,具体到某个函数。

3.1.5 利用宏一次完成清除、执行、替换

// 错误检查宏:包含 清除、执行、检查
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogCall())
......
// 清除、执行、断言检查 一次全包
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr));
......

image

更清晰了,知道哪个函数体出了问题。
但可不可以把函数名字、哪个文件、哪一行显示在控制台。

3.1.7 利用宏对检查错误进行参数优化

// 错误检查宏:包含 清除、执行、检查
#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLLogCall(#x, __FILE__, __LINE__)) // #取函数名,file、line 依次取
......
// 清除、执行、断言检查 一次全包
GLCall(glDrawElements(GL_TRIANGLES, 6, GL_INT, nullptr));
......

image

可以了,从控制台就能知道 哪个函数体、哪个文件、哪一行 出了问题。
可以为之前的每个函数执行加上 GLCall() 了。

3.1.8 思考与总结

  • 每个函数执行都要单独用一次清除和查找,很麻烦;
  • 虽然得到了错误码,但还需要查找错误原因;
  • 除了断点调试和断言终止,否则不知道具体是哪一行代码导致错误。

3.2 glDebugMessageCallback

4.3 版本之后支持,返回错误原因(英语)。

posted @ 2024-05-11 14:54  bok_tech  阅读(111)  评论(0)    收藏  举报