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),
// ......
};
至此,我们已经模糊掉了顶点属性的概念,我们面对的是整个顶点对象.