索引缓冲对象

有一点3D基础应该是知道这么个事情,我们会在*.obj文件,即3D对象中放至少两个东西——顶点坐标和顶点索引,索引表明了坐标之间的顺序,这样可以省略一些重复的坐标。什么意思呢?比如我们有四个顶点:

索引缓冲对象_第1张图片

四个点分别对应到0,1,2,3(因为索引是0开始的,当然也有1开始的,这里我们用0开始),在实际绘图的时候,一般会用很多的小三角形去近似一个图形,比如我们会使用B -> A -> C,A -> C -> D的方式去创造两个小三角形来拼出一个四边形:

索引缓冲对象_第2张图片

注意我们的连线顺序,A和C是相交的位置,它们所关联的两个小三角形的边,方向是相同的。在之后会创造一些彩色的图形,这个顺序会保证两个小三角形相交的部分颜色相同。

好处很明显,我们使用了很少的坐标,外加一个索引,表达出了一个四边形。所以,OpenGL中自然也有这个玩意,它被叫做索引缓冲对象IBO(Index Buffer Object)

创建IBO的方法和VBO类似,使用glGenBuffers(IBO_ID, &hIBO)创建。接着,我们需要使用glBindBufferglBufferData对它进行绑定,这一切都是在初始化内容里面完成的。在绘制的时候,我们使用glBindBuffer来表明所使用的索引缓冲,然后OpenGL就会按照这个缓冲绘制。索引缓冲的下标是从0开始的。

需要注意的是,VAO会储存IBO绑定,因此我们的绑定依然可以在初始化过程完成。这样一来,绘制流程就变成了:

// 绑定VAO
glBindVertexArray
// 载入VBO
glBindBuffer
glBufferData
// 载入IBO
glBindBuffer
glBufferData
// 设置顶点属性
glVertexAttribPointer
glEnableVertexAttribArray
// ......

// 渲染
glUseProgram
glBindVertexArray(Handle of VAO)
glDrawElements
glBindVertexArray

具体而言,参照我的实现。里面的一些函数被移入了utils.h中——它们没有修改,也大概不会再修改,它们的实现和上一篇文章是一样的。同样的,我只修改了渲染循环和初始化渲染代码部分。可以看到新增的内容。

#include "pch.h"
#include "utils.h"
#include "resource.h"
#define  VBO_ID        1
#define  IBO_ID        1
#define  VAO_ID        1
void  framebufferResize(GLFWwindow *, int, int);
void  keyInput(GLFWwindow *win, int key, int scancode, int action, int mods);
bool  initRender();
void  renderLoop();
const float vetPos[] =
{/*  X      Y     Z   */
     0.5f,  0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
    -0.5f,  0.5f, 0.0f
};
const GLuint index[] =
{
    0, 1, 3,
    1, 2, 3,
};
const char vetShader[] =
{
    "#version 400 core                                \n\
     layout (location = 0) in vec3 pos;               \n\
     void main()                                      \n\
     {                                                \n\
        gl_Position = vec4(pos.x, pos.y, pos.z, 1.0); \n\
     }"
};
const char pixelShader[] =
{
    "#version 400 core                                \n\
     out vec4 outColor;                               \n\
     void main()                                      \n\
     {                                                \n\
         outColor = vec4(0.5f, 0.2f, 0.5f, 1.0f);     \n\
     }"
};
GLuint  hVBO, hVAO, hIBO;
GLuint  hShaderProg, hVetShader, hPixelShader;
int APIENTRY wWinMain(
    _In_        HINSTANCE   hInstance,
    _In_opt_    HINSTANCE   hPrevInstance,
    _In_        LPWSTR      lpCmdLine,
    _In_        int         nCmdShow
)
{
    GLFWwindow  *win;
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#if _DEBUG
    FILE *ous;
    AllocConsole();
    setlocale(LC_CTYPE, "chs");
    freopen_s(&ous, "CONOUT$", "w", stdout);
#endif
    if ((win = glfwCreateWindow(420, 320, "Graph3D", NULL, NULL)) == NULL)
    {
        popMessageBox(_T("无法创建窗口"));
        goto err;
    }
    glfwMakeContextCurrent(win);
    glfwSetKeyCallback(win, keyInput);
    glfwSetFramebufferSizeCallback(win, framebufferResize);
    if (gladLoadGLLoader((GLADloadproc)glfwGetProcAddress) == 0)
    {
        popMessageBox(_T("无法初始化GLad, 请检查您的设备是否支持OpenGL 4.0"));
        goto err;
    }
    if (initRender() == false)
    {
        popMessageBox(_T("无法初始化渲染"));
        goto err;
    }
    while (glfwWindowShouldClose(win) == 0)
    {
        renderLoop();
        glfwPollEvents();                      // 检查, 触发事件
        glfwSwapBuffers(win);
    }
    goto succ;
err:
#if _DEBUG
    system("pause");
#endif
succ:
    glfwTerminate();
#if _DEBUG
    FreeConsole();
#endif
    return 0;
}
// 窗口尺寸被改变
void framebufferResize(GLFWwindow* win, int w, int h)
{
    glViewport(0, 0, w, h);
}
// 键盘输入
void keyInput(GLFWwindow *win, int key, int scancode, int action, int mods)
{
    if (action == GLFW_RELEASE)                // 松开按键
        if (key == GLFW_KEY_ESCAPE)
        {
            glfwSetWindowShouldClose(win, true);
        }
}
// 初始化
bool initRender()
{
    glGenBuffers(VBO_ID, &hVBO);               // 创建并绑定顶点缓冲
    glGenBuffers(IBO_ID, &hIBO);               // 索引缓冲
    glGenVertexArrays(VAO_ID, &hVAO);

    hVetShader = glCreateShader(GL_VERTEX_SHADER);
    if (compileShader(vetShader, hVetShader) == false)
    {
        return false;
    }
    hPixelShader = glCreateShader(GL_FRAGMENT_SHADER);
    if (compileShader(pixelShader, hPixelShader) == false)
    {
        return false;
    }

    hShaderProg = glCreateProgram();
    if (linkShaderProg(hShaderProg, hVetShader, hPixelShader) == false)
    {
        return false;
    }
    glDeleteShader(hVetShader);
    glDeleteShader(hPixelShader);

    glBindVertexArray(hVAO);                   // 绑定VAO

    glBindBuffer(GL_ARRAY_BUFFER, hVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vetPos), vetPos, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, hIBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index), index, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    return true;
}
// 渲染循环
void renderLoop()
{
    glClearColor(0.2f, 0.3f, 0.5f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(hShaderProg);
    glBindVertexArray(hVAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

如果你运行成功了,你会看到一个暗紫红色的矩形。它是由两个三角形组成的。这是因为我们一般把三角形作为基本图元,OpenGL绝大部分时候都是处理三角形的glDrawElements的第二个参数表示了我们使用6个索引。

索引缓冲对象_第3张图片

如果你没有看到矩形,那么说明你有某一步做错了,请仔细检查。下面我们切换绘图模式——使用glPolygonMode切换,在初始化代码段末尾(即initRender()函数的最后)添加下面的代码:

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

编译运行,你会看到三角形线框:

索引缓冲对象_第4张图片

我们可以用GL_LINE表示线框模式,稍后可以改成GL_FILL,切换成我们原本的绘图模式。

自此,可以长叹一口气了————OpenGL绘制出这些图案需要了解非常多的知识,目前来说,我们成功的踏出了第一步。

你可能感兴趣的:(OpenGL)