OpenGL 入门(二)— 顶点数组对象(VAO)和顶点缓冲对象(VBO)

前言

关键词介绍:

  • 顶点数组对象(Vertex Array Object,VAO): 存储缓冲区和顶点属性状态。
  • 顶点缓冲对象(Vertex Buffer Object,VBO): 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
  • 元素缓冲对象(Element Buffer Object,EBO):也叫索引缓冲对象(Index Buffer Object,IBO): 一个存储元素索引供索引化绘制使用的缓冲对象。
  • 标准化设备坐标(Normalized Device Coordinates, NDC):标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪,不会显示在你的屏幕上。

图形渲染管线(Graphics Pipeline)

定义:实际上指的是一堆原始图形数据途经一个输送管道,期间经过各种变化处理最终出现在屏幕的过程。

在OpenGL中,任何事物都在3D空间中,而屏幕和窗口却是2D像素数组,这导致OpenGL的大部分工作都是关于把3D坐标转变为适应你屏幕的2D像素。

图形渲染管线可以被划分为两个主要部分:第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。

图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

下面是图形渲染管线的每个阶段的抽象展示。官方链接
要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。请添加图片描述
这里主要涉及渲染流水线问题,就不做过多描述,因为涉及到知识点,后面会单独总结一篇。

顶点缓冲对象(Vertex Buffer Objects, VBO)

我们通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理这个内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。

从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。

顶点缓冲对象是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:

unsigned int VBO;
glGenBuffers(1, &VBO);

同事,我们的顶点缓冲数据会被解析为下面这样子:
请添加图片描述

  • 置数据被储存为32位(4字节)浮点值。
  • 每个位置包含3个这样的值。
  • 在这3个值之间没有空隙(或其他值)。这几个值在数组中紧密排列(Tightly Packed)。
  • 数据中第一个值在缓冲开始的位置。

顶点数组对象(Vertex Array Object,VAO)

顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。

这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。

这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中

OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。

一个顶点数组对象会储存以下这些内容:

  • glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
  • 通过glVertexAttribPointer设置的顶点属性配置。
  • 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
    请添加图片描述

创建一个VAO和创建一个VBO很类似:

unsigned int VAO;
glGenVertexArrays(1, &VAO);

顶点着色器(Vertex shader)

顶点着色器:它属于GPU流水线中的几何阶段,是完全可编程的,它通常用于实现顶点的空间变换,顶点着色等功能。

我们需要做的第一件事是用着色器语言GLSL(OpenGL Shading Language)编写顶点着色器,然后编译这个着色器,这样我们就可以在程序中使用它了。一个基础的GLSL顶点着色器的源代码:

#version 330 core  //版本
layout (location = 0) in vec3 aPos; //in关键字,在顶点着色器中声明所有的输入顶点属性(Input Vertex Attribute)

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);//gl_Position设置的值会成为该顶点着色器的输出
}

为了能够让OpenGL使用它,我们必须在运行时动态编译它的源代码。

	...
const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";

	...
int main()
{
	...

    // 1.顶点着色器(vertex shader) 用于处理顶点 
    unsigned int vertexShader=glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader,1,&vertexShaderSource,NULL);
    glCompileShader(vertexShader);
    //检查着色器编译错误
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader,GL_COMPILE_STATUS,&success);
    if(!success){
        glGetShaderInfoLog(vertexShader,512,NULL,infoLog);
        std::cout<<"ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"<<infoLog<<std::endl;
    }

	...
}


片段着色器(Fragment shader)

片段着色器:也叫片元着色器,它属于GPU流水线中的光栅化阶段,是完全可编程的,片段着色器所做的是计算像素最后的颜色输出。
一个基础的GLSL片元着色器的源代码:


	...
const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}\n\0";
	...
	
int main()
{
	...

    // 2.片段着色器(fragment shader) 用于处理片段/片元(一个片段是OpenGL渲染一个像素所需的所有数据)
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // 创建片段着色器
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);   // 将着色器源码附加到着色器对象上
    glCompileShader(fragmentShader);                                  // 编译着色器
    // 检查着色器编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); // 获取编译状态
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); // 获取错误信息
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
                  << infoLog << std::endl;
    }

	...
	
    return 0;
}

着色器程序 (Shader Program Object)

着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。如果要使用刚才编译的着色器我们必须把它们链接(Link)为一个着色器程序对象,然后在渲染对象的时候激活这个着色器程序。已激活着色器程序的着色器将在我们发送渲染调用的时候被使用。

创建一个着色器程序,用来管理着色器:

	...
	// 3.着色器程序(shader program) 用来管理着色器
    unsigned int shaderProgram = glCreateProgram(); // 创建着色器程序
    glAttachShader(shaderProgram, vertexShader);     // 将之前编译的着色器附加到程序对象上
    glAttachShader(shaderProgram, fragmentShader);   // 将之前编译的着色器附加到程序对象上
    glLinkProgram(shaderProgram);                    // 链接着色器
    // 检查着色器程序链接错误
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); // 获取链接状态
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); // 获取错误信息
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
                  << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);   // 删除着色器
    glDeleteShader(fragmentShader); // 删除着色器
    ...

元素缓冲对象(Element Buffer Object,EBO)

元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)。

简单来说,就是通过索引来解决数据重复利用。
在这里插入图片描述
请添加图片描述

创建一个三角形

有了上面的知识点介绍,我们可以创建一个三角形。

OpenGL常用模板

创建之前,我们先整理一下OpenGL模板脚本:

#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

void InitGLFW();
bool CreateWindow();
bool InitGLAD();
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);

// settings 窗口宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

GLFWwindow *window;

int main()
{
    InitGLFW();                      // 初始化GLFW
    bool isCreated = CreateWindow(); // 创建一个窗口对象
    if (!isCreated)
        return -1;
    bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数
    if (!isGLAD)
        return -1;

    //TODO:创建一个三角形,使用着色器语言GLSL编写顶点着色器和片段着色器,将它们编译链接到一个着色器程序中,并使用这个着色器程序绘制三角形。




    // 循环渲染
    while (!glfwWindowShouldClose(window))
    {

        // 输入
        processInput(window);

        // 渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // 检查并调用事件,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 释放/删除之前的分配的所有资源
    glfwTerminate();
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

void InitGLFW()
{
    // 初始化GLFW
    glfwInit();
    // 配置GLFW  第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;
    // 第二个参数接受一个整型,用来设置这个选项的值。
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
bool CreateWindow()
{
    // 创建一个窗口对象
    window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        // 创建失败,终止程序
        glfwTerminate();
        return false;
    }
    // 将我们窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    // 设置窗口大小改变时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    return true;
}
bool InitGLAD()
{
    // 初始化GLAD,传入加载系统相关opengl函数指针的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        // 初始化失败,终止程序
        return false;
    }
    return true;
}

// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    // 设置窗口的维度
    glViewport(0, 0, width, height);
}

// 输入
void processInput(GLFWwindow *window)
{
    // 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true
    // 关闭应用程序
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

在这里插入图片描述

链接着色器(link shaders)

链接顶点数组、顶点缓冲对象

 	//设置顶点数据(和缓冲区)并配置顶点属性
    //1.顶点输入
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // 左
        0.5f, -0.5f, 0.0f,  // 右
        0.0f, 0.5f, 0.0f    // 上
    };

    //2.创建一个顶点缓冲对象(Vertex Buffer Objects, VBO) 顶点数组对象(Vertex Array Object, VAO)
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO); // 生成一个VAO对象
    glGenBuffers(1, &VBO);      // 生成一个VBO对象
    
    //3.首先绑定顶点数组对象,然后绑定和设置顶点缓冲区,然后配置顶点属性。
    glBindVertexArray(VAO); // 绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区中
    
    //4.配置顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); // 解析顶点数据
    glEnableVertexAttribArray(0);                                                  // 启用顶点属性

    //注意这是允许的,调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象,因此之后我们可以安全地解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //你可以在之后解除对VAO的绑定,这样其他VAO调用就不会意外地修改这个VAO,但这种情况很少发生。修改其他
    // VAOs无论如何都需要调用glBindVertexArray,所以当不直接需要时,我们通常不会取消绑定VAOs(也不会取消绑定vbo)。
    glBindVertexArray(0);

绘制物体三角形

	...
    // 循环渲染
    while (!glfwWindowShouldClose(window))
    {
	...
        // 绘制三角形
        glUseProgram(shaderProgram); // 使用着色器程序
        glBindVertexArray(VAO);      // 绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
        // glBindVertexArray(0); // 不需要每次都绑定VAO

        // 检查并调用事件,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    ...

完整脚本

#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

void InitGLFW();
bool CreateWindow();
bool InitGLAD();
// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height);
void processInput(GLFWwindow *window);

// settings 窗口宽高
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char *vertexShaderSource = "#version 330 core\n"
                                 "layout (location = 0) in vec3 aPos;\n"
                                 "void main()\n"
                                 "{\n"
                                 "   gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
                                 "}\0";
const char *fragmentShaderSource = "#version 330 core\n"
                                   "out vec4 FragColor;\n"
                                   "void main()\n"
                                   "{\n"
                                   "   FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
                                   "}\n\0";

GLFWwindow *window;

int main()
{
    InitGLFW();                      // 初始化GLFW
    bool isCreated = CreateWindow(); // 创建一个窗口对象
    if (!isCreated)
        return -1;
    bool isGLAD = InitGLAD(); // 初始化GLAD,传入加载系统相关opengl函数指针的函数
    if (!isGLAD)
        return -1;

    // TODO:创建一个三角形,使用着色器语言GLSL编写顶点着色器和片段着色器,将它们编译链接到一个着色器程序中,并使用这个着色器程序绘制三角形。

    // 1.顶点着色器(vertex shader) 用于处理顶点
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // 检查着色器编译错误
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
                  << infoLog << std::endl;
    }

    // 2.片段着色器(fragment shader) 用于处理片段/片元(一个片段是OpenGL渲染一个像素所需的所有数据)
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); // 创建片段着色器
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);   // 将着色器源码附加到着色器对象上
    glCompileShader(fragmentShader);                                  // 编译着色器
    // 检查着色器编译错误
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success); // 获取编译状态
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); // 获取错误信息
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n"
                  << infoLog << std::endl;
    }

    // 3.着色器程序(shader program) 用来管理着色器
    unsigned int shaderProgram = glCreateProgram(); // 创建着色器程序
    glAttachShader(shaderProgram, vertexShader);     // 将之前编译的着色器附加到程序对象上
    glAttachShader(shaderProgram, fragmentShader);   // 将之前编译的着色器附加到程序对象上
    glLinkProgram(shaderProgram);                    // 链接着色器
    // 检查着色器程序链接错误
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success); // 获取链接状态
    if (!success)
    {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog); // 获取错误信息
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n"
                  << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);   // 删除着色器
    glDeleteShader(fragmentShader); // 删除着色器


    //设置顶点数据(和缓冲区)并配置顶点属性
    //1.顶点输入
    float vertices[] = {
        -0.5f, -0.5f, 0.0f, // 左
        0.5f, -0.5f, 0.0f,  // 右
        0.0f, 0.5f, 0.0f    // 上
    };

    //2.创建一个顶点缓冲对象(Vertex Buffer Objects, VBO) 顶点数组对象(Vertex Array Object, VAO)
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO); // 生成一个VAO对象
    glGenBuffers(1, &VBO);      // 生成一个VBO对象
    //3.首先绑定顶点数组对象,然后绑定和设置顶点缓冲区,然后配置顶点属性。
    glBindVertexArray(VAO); // 绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定VBO
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 将顶点数据复制到缓冲区中
    
    //4.配置顶点属性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); // 解析顶点数据
    glEnableVertexAttribArray(0);                                                  // 启用顶点属性

    //注意这是允许的,调用glVertexAttribPointer将VBO注册为顶点属性的绑定顶点缓冲对象,因此之后我们可以安全地解除绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    //你可以在之后解除对VAO的绑定,这样其他VAO调用就不会意外地修改这个VAO,但这种情况很少发生。修改其他
    // VAOs无论如何都需要调用glBindVertexArray,所以当不直接需要时,我们通常不会取消绑定VAOs(也不会取消绑定vbo)。
    glBindVertexArray(0);

    //取消此调用的注释以绘制线框多边形。
    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    // 循环渲染
    while (!glfwWindowShouldClose(window))
    {

        // 输入
        processInput(window);

        // 渲染指令
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);


        // 绘制三角形
        glUseProgram(shaderProgram); // 使用着色器程序
        glBindVertexArray(VAO);      // 绑定VAO
        glDrawArrays(GL_TRIANGLES, 0, 3); // 绘制三角形
        // glBindVertexArray(0); // 不需要每次都绑定VAO

        // 检查并调用事件,交换缓冲
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    //可选:一旦资源超出其用途,就取消分配所有资源:
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // 释放/删除之前的分配的所有资源
    glfwTerminate();
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

void InitGLFW()
{
    // 初始化GLFW
    glfwInit();
    // 配置GLFW  第一个参数代表选项的名称,我们可以从很多以GLFW_开头的枚举值中选择;
    // 第二个参数接受一个整型,用来设置这个选项的值。
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
bool CreateWindow()
{
    // 创建一个窗口对象
    window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        // 创建失败,终止程序
        glfwTerminate();
        return false;
    }
    // 将我们窗口的上下文设置为当前线程的主上下文
    glfwMakeContextCurrent(window);
    // 设置窗口大小改变时的回调函数
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    return true;
}
bool InitGLAD()
{
    // 初始化GLAD,传入加载系统相关opengl函数指针的函数
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        // 初始化失败,终止程序
        return false;
    }
    return true;
}

// 窗口大小改变时调用
void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    // 设置窗口的维度
    glViewport(0, 0, width, height);
}

// 输入
void processInput(GLFWwindow *window)
{
    // 当用户按下esc键,我们设置window窗口的windowShouldClose属性为true
    // 关闭应用程序
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

效果图:
在这里插入图片描述

重要的API

主要介绍比较重要的API。

glViewport 函数

说明
用于设置窗口的维度。
语法

void glViewport(
   GLint   x,
   GLint   y,
   GLsizei width,
   GLsizei height
);

参数

  • x,y 以像素为单位,指定了视口的左下角位置。
  • width,height 表示这个视口矩形的宽度和高度,根据窗口的实时变化重绘窗口。

glBufferData 函数

说明
一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
语法

void glGenBuffers(
	GLenum target, 
	GLsizeiptr size, 
	const void *data, 
	GLenum usage
);

参数

  • 第一个参数是目标缓冲的类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。。
  • 第二个参数指定传输数据的大小(以字节为单位);用一个简单的sizeof计算出顶点数据大小就行。
  • 第三个参数是我们希望发送的实际数据。
  • 第四个参数指定了我们希望显卡如何管理给定的数据。它有三种形式:
    • GL_STATIC_DRAW :数据不会或几乎不会改变。
    • GL_DYNAMIC_DRAW:数据会被改变很多。
    • GL_STREAM_DRAW :数据每次绘制时都会改变。
      +三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是GL_STATIC_DRAW。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,这样就能确保显卡把数据放在能够高速写入的内存部分。

glVertexAttribPointer 函数

说明
一个专门用来把用户定义的数据复制到当前绑定缓冲的函数。
语法

void glVertexAttribPointer(
	GLuint index, GLint size,
	GLenum type,
	GLboolean normalized, 
	GLsizei stride, 
	const void *pointer
);

参数

  • index:指定我们要配置的顶点属性。还记得我们在顶点着色器中使用layout(location = 0)定义了position顶点属性的位置值(Location)吗?它可以把顶点属性的位置值设置为0。因为我们希望把数据传递到这一个顶点属性中,所以这里我们传入0。
  • size:指定顶点属性的大小。顶点属性是一个vec3,它由3个值组成,所以大小是3。
  • type:指定数据的类型,这里是GL_FLOAT(GLSL中vec*都是由浮点数值组成的)。
  • normalized:是否希望数据被标准化(Normalize)。如果我们设置为GL_TRUE,所有数据都会被映射到0(对于有符号型signed数据是-1)到1之间。我们把它设置为GL_FALSE。
  • stride:它告诉我们在连续的顶点属性组之间的间隔。
  • pointer:类型是void*,所以需要我们进行这个奇怪的强制类型转换。它表示位置数据在缓冲中起始位置的偏移量(Offset)。
  • 21
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt OpenGL提供了一种实现高效渲染的机制,它使用了VAO(Vertex Array Object)和VBO(Vertex Buffer Object)技术。 VAO是一种对象,用于存储顶点数据、顶点属性以及它们之间的关联关系。它可以理解为一个顶点属性的容器。通过使用VAO,我们可以将顶点数据存储在显存中,而不是每次渲染时都从CPU传输顶点数据到显存,从而提高渲染的效率。 VBO是一个存储顶点数据的缓冲对象。通过将顶点数据存储在VBO中,我们可以将数据一次性地传输到显存中,并且可以高效地管理和使用这些数据。同时,VBO还可以提供顶点缓存和索引缓存功能,用于顶点的重用和图元的索引。 Qt OpenGL提供了许多方便的API来操作VAOVBO。我们可以使用QOpenGLVertexArrayObject类来创建和管理VAO,通过调用QOpenGLBuffer类的相关方法来创建和管理VBO。例如,我们可以使用QOpenGLVertexArrayObject::bind()和QOpenGLVertexArrayObject::release()方法来绑定和释放VAO,使用QOpenGLBuffer::bind()和QOpenGLBuffer::release()方法来绑定和释放VBO。 使用VAOVBO可以大大简化OpenGL代码的编写,并且能够有效提高渲染效率。我们可以将需要渲染的数据一次性地传输到显存中,并且设置好相应的渲染状态,然后每次渲染时只需要绑定VAO进行渲染即可,不需要重复的数据传输和状态设置操作。 综上所述,Qt OpenGLVAOVBO技术可以帮助我们实现高效渲染,提高应用程序的性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值