1/索引与缓冲区对象

在上一章的实例中,引擎主循环每执行一次,就要上传顶点数据到GPU来实现渲染.对于简单的图形来说,这是可以接受的.但是实际项目中动辄几W的顶点数量不可能也使用这种方法来处理顶点,不然每一帧都要把所有顶点上传一次,那主循环不用干别的事了.

所以要优化这种情况,有两种方式:

  • 减少数据上传量
  • GPU上缓存数据

OpenGL对应的提供了两种方法来实现,分别是顶点索引缓冲区对象.

1.1/顶点数组对象

而且各位不要忘了,OpenGL是一个典型的C/S架构,也就是说CPU在OpenGL里面扮演的是客户端的角色,而GPU在OpenGL里面扮演的是服务端的角色.

这就会产生一个问题:CPU在通知GPU执行一个API之后,必须等待GPU返回一个结果之后才能继续执行下一个API.换句话说,要调用的OpenGL API越多,性能就越差(要等待很多次返回结果)

那么有没有一种方法,把数个命令的结果保存在GPU上面,CPU只需要调用一个API就可以完成执行数个命令的结果呢?

答案是顶点数组对象

1.1.1/何为顶点索引

回顾之前画矩形的时候,我们上传了6个顶点,但是实际上只有4个顶点是真正有用的,剩余两个顶点只是为了凑出两个三角形而上传的.这样不仅增加了顶点的上传数量,也增加了GPU顶点着色器的运行次数

以正方形的顶点坐标为例,顶点坐标是一个数组,那么顶点索引就是这个数组的index

static const glm::vec3 kPositions[6] =
{
    //第一个三角形
    { -1.0f, -1.0f, 0.0f},//左下
    {  1.0f, -1.0f, 0.0f},//右下
    {  1.0f,  1.0f, 0.0f},//右上
    //第二个三角形
    {  1.0f,  1.0f, 0.0f},//右上
    { -1.0f,  1.0f, 0.0f},//左上
    { -1.0f, -1.0f, 0.0f}//左下
};

去掉重复的定点之后,其实只有四个顶点

static const glm::vec3 kPositions[4] =
{
    { -1.0f, -1.0f, 0.0f},//左下
    {  1.0f, -1.0f, 0.0f},//右下
    {  1.0f,  1.0f, 0.0f},//右上
    { -1.0f,  1.0f, 0.0f},//左上
};

但是只有4个顶点的情况下,如何组成两个三角形呢?答案是去重之后的顶点坐标数据不重复,但是可以新建一个数组,存储重复的顶点索引,利用索引调用顶点坐标

static const glm::vec3 indices[6] =
{
    //第一个三角形
    0,1,2,
    //第二个三角形
    2,3,1
};

借助顶点索引数组,我们间接地形成了两个三角形.

1.1.2/何为顶点

在作者之前的文章中,使用了如下的代码来设置了顶点的属性

glVertexAttribPointer(vpos_location, 3, GL_FLOAT, false, sizeof(glm::vec3), kPositions);

具体绘制多少个顶点,是在如下代码中决定的

glDrawArrays(GL_TRIANGLES, 0, 6*6);//表示从第0个顶点开始画,总共画6个面,每个面6个顶点(两个三角形)。

之前我们提到过,顶点着色器是每个顶点都要执行一次.换句话说,glDrawArrays制定了多少个顶点,就执行多少次.

那么每次执行,就以当前顶点序号为下标,从glVertexAttribPointer设置的顶点属性去拿数据,设置到顶点shader的变量vpos_location中。

那么顶点属性的数据,这个数组,长度一定是要等于glDrawArrays指定的顶点数的,不然就会取不到数据。

按照这个逻辑,我将所有用glVertexAttribPointer设置的顶点属性,相同下标的,称之为一个顶点。

大概是这个意思

也就是说,一个顶点包含了

  • 坐标
  • 颜色
  • UV坐标

三个数据,只要这三个数据有一个不同,那就不能称之为两个相同的顶点.也就是说,使用顶点索引需要去重,本质上是去掉三者完全相同的顶点.

因此,对于项目中存放顶点的定义vertex_data.h需要修改为如下代码.

//顶点
struct Vertex
{
    glm::vec3 pos_;
    glm::vec4 color_;
    glm::vec2 uv_;
};
static const Vertex kVertexs[36] ={
    //Front
    glm::vec3(-1.0f, -1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(0.0f, 0.0f),
    glm::vec3( 1.0f, -1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(1.0f, 0.0f),
    glm::vec3( 1.0f,  1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(1.0f, 1.0f),
    glm::vec3(-1.0f, -1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(0.0f, 0.0f),
    glm::vec3( 1.0f,  1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(1.0f, 1.0f),
    glm::vec3(-1.0f,  1.0f, 1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(0.0f, 1.0f),
    //back
    glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec4(1.0f,1.0f,1.0f,1.0f),   glm::vec2(0.0f, 0.0f),
    // ......
};

至此,我们已经模糊掉了顶点属性的概念,我们面对的是整个顶点对象.

一个还在寻找自己的三流开发者