原创文章,转载请注明链接 http://blog.csdn.net/hoytgm/article/details/37743861
之前我们在发出画三角形的命令的时候,用的是glDrawArrays,其实,OpenGLES里面还有一个也很常用的draw命令,叫做glDrawElements。这次我们讲讲怎么使用glDrawElements吧。
我们在调用glDrawArrays的时候,要先将组成我们模型的所有点按照顺序传给OpenGLES。试想一下,如果我们画一个正方形,它是由两个三角形构成,那么我们需要准备6个点,如果是一个立方体呢?我们需要24个点。但是实际上,正方形由4个点组成,所以用glDrawArrays的话,有两个点是重复的。而立方体实际上只需要8个点就够了,剩下16个点都是重复的。有没有一个方法,让我们画一个正方形只需要准备4个点,画立方体只需要8个点呢?OpenGLES早就想到了,这就是glDrawElements。
但是glDrawElements比glDrawArrays多了一个参数需要指定,那就是index。什么是index呢,就是用一个数组去指定画的顶点顺序。比如,如果我们画一个正方形,我们有一个顶点数组[a, b, c, d],那么构成正方形的两个三角形的顶点是[a, b, c]和[b, c, d],那么这个index就是[0, 1, 2, 1, 2, 3]。我们用代码来演示一下吧。
当需要画一个正方形的时候,这是之前用glDrawArrays时需要使用的顶点数组
static float cube[] =
{
-0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.0f,
};
如果我们需要使用glDrawElements的话,顶点就可以缩减到4个啦
static float cube[] =
{
-0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.0f,
};
同时,还需要指定一个index数组
static unsigned short indices[] =
{
0, 1, 2, 1, 2, 3
};
然后,我们就可以用glDrawElements来代替glDrawArrays了
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indices);
我们讲解一下glDrawElements和glDrawArrays的参数区别吧。glDrawArrays的三个参数,第一个是三角形的类型,第二个是从顶点数组中起始的顶点,第三个是结束的顶点。这样的话,glDrawArrays就可以不用将顶点数组中所有的点都画。再看看上面的glDrawElements的参数,第一个是三角形的类型,第二个是要画多少个顶点,第三个是指明index数组的数据类型,第四个就是index数组的指针。所以从第二个参数这里可以看出,我们画的时候必须从index数组的第0个元素开始画,但是可以不一定画完整个index数组。还有一点必须注意,第三个参数,在OpenGLES 2.0版本中(截至目前,OpenGLES已经推出了3.1,但是游戏和应用还不多,所以我们目前还是主要用2.0),只支持GL_UNSIGNED_BYTE和GL_UNSIGNED_SHORT,也就是说,index数组的长度不限,但是每个元素不能超过2的16次方。
讲到这里,如果看了代码会发现,不管是顶点数组还是index数组,我们都是放在Render()函数里面,并且指定了是static变量。为什么?因为这个是放在用户端的数据,并没有将整个数据全部交给OpenGLES管理,所以,当OpenGLES用这些数据的时候,如果不指定是static,有可能数据就被销毁了,那么就会画不出来东西。因为如果是用户端的数据,OpenGLES用的时候,并不知道所给的指针是否合法,是否包含有效数据,如果我们不指定是static,那么局部变量在用的时候很可能被释放了。当然,我试过了,在IOS设备上面没这个问题,同样在ARM公司的Mali系列GPU上面也可以使用局部变量。
那么怎么把数据交给OpenGLES管理(其实就是放在显存),而不是放在用户端呢?放在用户端的数据有个弊端,就是显卡每次使用的时候,都要重新从用户端拷贝一次,这样会很影响性能,特别是数据量很大的时候。说个额外的话题,之前我也好奇为什么用局部变量作为顶点数据,Mali居然没有画错,我用大量数据来做了测试,发现Mali并没有影响性能,很有可能它们的驱动只对数据拷贝了一次,以后用的时候都没有拷贝动作,不过应该是对这段数据做了检查,以防被中途改变过。因为我每次画的时候随机改一个值,性能马上就降下来了。不过,我还没在IOS上面做过这些测试,以后再说吧。
扯远了,既然用户端的数据会影响性能,那么我们下面要讲的Vertex Buffer和Index Buffer就是把数据全部放在显存使用。其实不管Vertex Buffer还是Index Buffer,在OpenGLES 2.0中都是指的是Buffer,只不过传递数据的时候指定一下用途。先创建两个GLuint型的数据,相当于Buffer的句柄
#define VERTEX 0
#define INDEX 1
GLuint buffers[2] = {0};
然后创建两个buffer
glGenBuffers(2, buffers);
首先,我们先绑定一个buffer,指定buffer的用途为Vertex Buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[VERTEX]);
接下来创建4个顶点,也就是一个正方形,然后讲数据传递到刚才的Vertex Buffer里面
float cube[] =
{
-0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 1.0f,
0.5f, 0.5f, 0.0f, 1.0f,
};
glBufferData(GL_ARRAY_BUFFER, sizeof(cube), cube, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
最后一句的意思是我们绑定到一个句柄为0的buffer上面,其实相当于结束绑定。
对于Index Buffer,我们采用同样的方法,但是函数的第一个参数变为GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[INDEX]);
static unsigned short indices[] =
{
0, 1, 2, 1, 2, 3
};
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
这样,就把Vertex Buffer和Index Buffer初始化好了,在用的时候,我们首先要先再绑定两个buffer
glBindBuffer(GL_ARRAY_BUFFER, buffers[VERTEX]);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[INDEX]);
之前传递数据给Shader的时候,我们是传递了数据的指针,但是我们一旦绑定了Buffer,那么最后一个参数就不再是指针,而是数据的偏移,在这里我们无需偏移,所以就传递0就可以了
glVertexAttribPointer(aLocPos, 4, GL_FLOAT, 0, 0, 0);
再调用glDrawElements的时候,最后一个参数是index数组的指针,但是我们现在绑定了Index Buffer,最后一个参数也只是偏移了,同样,我们没有偏移,所以也传递0就可以了
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
看,我们要的正方形出来了
代码已上传,地址http://download.csdn.net/detail/hoytgm/7634443