OpenGL之渲染管线-VBO-VAO 三角形示例

在OpenGL中,一切都是3D的,但屏幕或窗口是一个2D像素阵列,因此OpenGL的大部分工作是将所有3D坐标转换为适合屏幕的2D像素。这个过程由OpenGL的渲染管线管理。

渲染管线可以分为两大部分:

  • 将3D坐标转换为2D坐标
  • 将2D坐标转换为实际的彩色像素

顶点着色器处理后,顶点值应该是NDC坐标;NDC坐标使用glViewport提供的数据,通过视口转换变为屏幕坐标。生成的屏幕空间坐标将转换为片段,作为片段着色器的输入。

标准化设备坐标 (Normalized Device Coordinates, NDC)

顶点着色器中处理过后,就应该是标准化设备坐标了,x、y 和 z 的值在-1.0到1.0的一小段空间(立方体)。落在范围外的坐标都会被裁剪。

 

  

顶点输入

在GPU上创建内存,储存的顶点数据

  • 通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理
  • 顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER

配置OpenGL如何解释这些内存

  • 通过顶点数组对象(Vertex Array Objects, VAO)管理

使用缓冲区对象的优点是,可以一次将大量数据发送到显卡,不必一次发送一个数据。

VAO并不保存实际数据,而是放顶点结构定义 数组里的每一个项都对应一个属性的解析

 

必须使用VAO,OpenGL才能知道如何处理顶点输入。如果没有绑定VAO,OpenGL会拒绝绘制任何东西。

 

OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。 (每一个缓冲类型类似于前面说的子集,每个VBO是一个小助理)

下图展示了VBO和VAO:

 

 

  下面是使用的关键代码:

//创建VBO和VAO对象,并赋予ID
unsigned int VBO, VAO; 
//创建1个VAO对象
glGenVertexArrays(1, &VAO); 
//创建1个VBO对象
glGenBuffers(1, &VBO); 
//绑定VBO和VAO对象
glBindVertexArray(VAO); 
//缓冲对象如果绑定的是顶点属性则用:GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO); 
//为当前绑定到target的缓冲区对象创建一个新的数据存储。
//如果data不是NULL,则使用来自此指针的数据初始化数据存储
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

//上面我们将数据存到了缓冲区对象中,下面就需要
//告知Shader(着色器)如何解析缓冲里的属性值
//第一个属性是从哪个位置开始解析
//第二个属性是每个顶点属性由几个组合而成
//第三个属性是数组中每个元素的类型
//第四个表示是否标准化,这里暂时不需要,需要注意只有整型值才会有效,如果浮点型的数据不会起作用
//第五个是步长,因为我们这个是数组中每3个元素组成一个顶点属性,而且是float类型
//第六个是偏移量,我们这里为0,就是从0开始读取数组中的数据的
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
//开启VAO管理的第一个属性值
 glEnableVertexAttribArray(0); 
//作为习惯,用完之后将VBO,VAO解绑,需要的时候可以重新绑定, 
glBindBuffer(GL_ARRAY_BUFFER, 0); 
glBindVertexArray(0); 

 然后创建一个三角形的示例: 

// GLAD的include文件包含所需的OpenGL头文件(如GL/GL.h),因此确保在其他需要OpenGL的头文件(如GLFW)之前包含GLAD。就是#include <glad/glad.h> 放在最前面
#include <glad/glad.h> 
#include <GLFW/glfw3.h>
#include <iostream>
float vertices[] = {
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f,
0.0f, 0.5f, 0.0f
};

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);

int main() {
    // 初始化GLFW,只有初始化完成之后才能够使用GLFW的函数
    glfwInit();
    // GLFW配置设置
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // 如果是苹果系统的话使用下面代码
#ifdef __APPLE__ 
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif  

    // 创建窗口 大小和名称
    GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL) {
        std::cout << "Failed to create GLFW window" << std::endl;
        // 此函数销毁所有剩余的窗口和光标
        glfwTerminate();
        return -1;
    }
    //GLFW将窗口的上下文设置为当前线程的上下文
    glfwMakeContextCurrent(window);
    // 告诉GLFW我们希望每当窗口调整大小的时候调用这个函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    //GLAD
    // glad: 加载所有OpenGL函数指针
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }


    //创建VBO和VAO对象,并赋予ID
    unsigned int VBO, VAO;
    //创建1个VAO对象
    glGenVertexArrays(1, &VAO);
    //创建1个VBO对象
    glGenBuffers(1, &VBO);
    //绑定VBO和VAO对象
    glBindVertexArray(VAO);
    //缓冲对象如果绑定的是顶点属性则用:GL_ARRAY_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    //为当前绑定到target的缓冲区对象创建一个新的数据存储。
    //如果data不是NULL,则使用来自此指针的数据初始化数据存储
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    //上面我们将数据存到了缓冲区对象中,下面就需要
    //告知Shader(着色器)如何解析缓冲里的属性值
    //第一个属性是从哪个位置开始解析
    //第二个属性是每个顶点属性由几个组合而成
    //第三个属性是数组中每个元素的类型
    //第四个表示是否标准化,这里暂时不需要,需要注意只有整型值才会有效,如果浮点型的数据不会起作用
    //第五个是步长,因为我们这个是数组中每3个元素组成一个顶点属性,而且是float类型
    //第六个是偏移量,我们这里为0,就是从0开始读取数组中的数据的
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    //开启VAO管理的第一个属性值
    glEnableVertexAttribArray(0);
    //作为习惯,用完之后将VBO,VAO解绑,需要的时候可以重新绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);


    // 渲染循环 一个循环就是一帧 只要是窗体不关闭,就会一直循环
    while (!glfwWindowShouldClose(window)) {
        processInput(window);

        // 在这里,我们将屏幕设置为了类似黑板的深蓝绿色
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f); //状态设置
        // 调用glClear函数,清除颜色缓冲之后,整个颜色缓冲都会被填充为glClearColor里所设置的颜色。
        glClear(GL_COLOR_BUFFER_BIT); //状态使用

         #pragma region 绘制三角形
        //要注意的是前面我们已经解绑了VAO,所以现在是无法解析数据的,所以我们需要重新绑定,
        // 至于数据我们已经存到缓冲区了
        glBindVertexArray(VAO);
        // 从数据数组中indexwei=0处开始读取,每三个做一个三角形的顶点(这是在VAO中定义的)。第三个参数是说一共绘制三个顶点数据(每个顶点由vertices数组中的3个元素组成)
        glDrawArrays(GL_TRIANGLES, 0, 3);
        #pragma endregion

        // glfw: 交换缓冲区 该函数在指定窗口的前后缓冲区交换
        // 前缓冲区:屏幕上显示的图像
        // 后缓冲区:正在渲染的图像
        // glfwSwapBuffers函数会交换颜色缓冲(它是一个储存着GLFW窗口每一个像素颜色值的大缓冲),
        // 它在这一迭代中被用来绘制,并且将会作为输出显示在屏幕上
        glfwSwapBuffers(window);
        // 轮询IO事件(按键按下 / 释放、鼠标移动等)通过下面方法就可以是得窗体对鼠标做出的动作做出反应,比如关闭,移动窗体等
        glfwPollEvents();
    }
    // glfw: 回收前面分配的GLFW先关资源. 一定要注意,只有关闭窗体之后才会跳出while循环走到这一步!!!
    glfwTerminate();
    return 0;
}
// glfwGetKey函数:需要一个窗口以及一个按键作为输入;函数将会返回这个按键是否正在被按下
void processInput(GLFWwindow* window)
{
    // 如果按下了ESC键,设置窗体的关闭标志为true,代表窗体可以退出
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// 当改变窗口的大小的时候,视口也应该被调整。我们可以对窗口注册一个回调函数(Callback Function),它会在每次窗口大小被调整的时候被调用
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // 设置窗口维度
    // glViewport(前两参数为窗口左下角位置,3.宽度,4.高度)
    glViewport(0, 0, width, height);
}

  结果:

 

 我们还没做着色器,所以这里的三角形颜色是根据自己电脑本身的默认颜色。 

posted @ 2022-08-29 22:52  安静点--  阅读(228)  评论(0编辑  收藏  举报