通过顶点缓冲对象(Vertex Buffer Objects, VBO)管理GPU内存储顶点数据的内存,它会在GPU内存(通常被称为显存)中储存大量顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上,而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢,所以只要可能我们都要尝试尽量一次性发送尽可能多的数据。当数据发送至显卡的内存中后,顶点着色器几乎能立即访问顶点,这是个非常快的过程。
就像OpenGL中的其它对象一样,这个缓冲有一个独一无二的ID,所以我们可以使用glGenBuffers函数和一个缓冲ID生成一个VBO对象:
unsigned int VBO; // 顶点缓冲对象
glGenBuffers(1, &VBO); // 创建顶点缓冲对象 1: 缓冲数量 &VBO: 缓冲ID glGenBuffers: 创建缓冲
OpenGL有很多缓冲对象类型,顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER。OpenGL允许我们同时绑定多个缓冲,只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER目标上:
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定缓冲 GL_ARRAY_BUFFER: 缓冲类型 VBO: 缓冲ID glBindBuffer: 绑定缓冲
从这一刻起,我们使用的任何(在GL_ARRAY_BUFFER目标上的)缓冲调用都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 复制顶点数组到缓冲中供OpenGL使用 GL_ARRAY_BUFFER: 缓冲类型 sizeof(vertices): 数据大小 vertices: 数据 GL_STATIC_DRAW: 数据使用方式
// GL_STATIC_DRAW :数据不会或几乎不会改变。
// GL_DYNAMIC_DRAW:数据会被改变很多。
// GL_STREAM_DRAW :数据每次绘制时都会改变。
顶点数组对象(Vertex Array Object, VAO)可以像顶点缓冲对象那样被绑定,任何随后的顶点属性调用都会储存在这个VAO中。这样的好处就是,当配置顶点属性指针时,你只需要将那些调用执行一次,之后再绘制物体的时候只需要绑定相应的VAO就行了。这使在不同顶点数据和属性配置之间切换变得非常简单,只需要绑定不同的VAO就行了。刚刚设置的所有状态都将存储在VAO中
OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
一个顶点数组对象会储存以下这些内容:
glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
通过glVertexAttribPointer设置的顶点属性配置。
通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
unsigned int VAO; // 顶点数组对象
glGenVertexArrays(1, &VAO); // 创建顶点数组对象 1: 数组数量 &VAO: 数组ID glGenVertexArrays: 创建顶点数组对象
要想使用VAO,要做的只是使用glBindVertexArray绑定VAO。从绑定之后起,我们应该绑定和配置对应的VBO和属性指针,之后解绑VAO供之后使用。当我们打算绘制一个物体的时候,我们只要在绘制物体前简单地把VAO绑定到希望使用的设定上就行了。这段代码应该看起来像这样:
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO); // 绑定顶点数组对象 VAO: 顶点数组对象 glBindVertexArray: 绑定顶点数组对象
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定缓冲 GL_ARRAY_BUFFER: 缓冲类型 VBO: 缓冲ID glBindBuffer: 绑定缓冲
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 复制顶点数组到缓冲中供OpenGL使用 GL_ARRAY_BUFFER: 缓冲类型 sizeof(vertices): 数据大小 vertices: 数据 GL_STATIC_DRAW: 数据使用方式
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); // 设置顶点属性指针 0: 顶点属性位置值 3: 顶点属性大小 GL_FLOAT: 顶点属性类型 GL_FALSE: 是否标准化 3 * sizeof(float): 步长 (void*)0: 偏移量
glEnableVertexAttribArray(0); // 启用顶点属性 0: 顶点属性位置值 glEnableVertexAttribArray: 启用顶点属性
[...]
// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram); // 使用着色器程序 shaderProgram: 着色器程序 glUseProgram: 使用着色器程序
glBindVertexArray(VAO); // 绑定顶点数组对象 VAO: 顶点数组对象 glBindVertexArray: 绑定顶点数组对象
someOpenGLFunctionThatDrawsOurTriangle(); // 绘制物体 someOpenGLFunctionThatDrawsOurTriangle: 绘制物体
一般当你打算绘制多个物体时,你首先要生成/配置所有的VAO(和必须的VBO及属性指针),然后储存它们供后面使用。当我们打算绘制物体的时候就拿出相应的VAO,绑定它,绘制完物体后,再解绑VAO。