顶点数组,顾名思义,就是存放顶点的数组。
为什么使用顶点数组?
为什么要将顶点存放到数组中啊?方便管理,省事。之前是这样指定物体的顶点的:
glBegin(GL_POLYGON);
glVertex2f(0.0, 0.0);//顶点A
glVertex2f(0.0, 3.0);//B
glVertex2f(4.0, 3.0);//C
glVertex2f(4.0, 0.0);//D
glEnd();
看上面的glVertex2f这个函数,重复了4遍,如果以后顶点个数还多一些的话,还要继续重复,这多麻烦啊。所以就有了顶点数组,将这些顶点存放到数组中,然后指定OpenGL使用顶点数组中的顶点,那确实省事一些。
所以,使用顶点数组,就是为了减少OpenGL函数调用数量。 函数调用有开销,且写代码的人也要重复多次函数调用,麻烦。
另外一个问题,相邻多边形的共享顶点的冗余处理。
如上图所示,一个立方体,有6个面,8个顶点。如果分别渲染6个面,每个面的4个顶点都需要指定一遍,所以需要指定24个顶点,而实际只有8个顶点需要指定。
使用顶点数组,可以减少顶点的冗余数量。
…
完了,写了半天,在发表的时候,由于按错了backspace键,结果写的内容没了。
我已经将 步骤2写完了啊。哭。不重写了。下次接着写步骤3得了。
步骤3:解引用和渲染
前面两个步骤,启用了顶点数组,然后指定了数组中的数据,这一步,应该就是如何使用顶点数组了。正常我们使用数组A中的数据,是通过A[i]来访问数组中的第i个元素。
OpenGL中,不但能访问顶点数组中的单个元素,还能访问数组中的一个列表的元素,还能访问数组中的所有元素。
解引用单个数组元素
解引用单个数组元素,就相当于访问数组A中第i个元素,用A[i]一样。OpenGL中解引用单个数组元素的函数叫 glArrayElement()。
void glArrayElement(GLint ith);
如果之前的顶点数组,指定了n个顶点,那么这个glArrayElement函数,访问的是第 ith 个顶点的数据。比如之前启用了颜色顶点数组,和顶点坐标数组,且指定了顶点颜色数组 和 顶点坐标数组,那么glArrayElement(2),访问的是第二个顶点的颜色值 和 坐标值。启用多少个顶点数组,就访问各个顶点数组中,第i个顶点的数据。
比如:
static GLint vertices[] = { 25, 25,
100, 325,
175, 25,
175, 325,
250, 25,
325, 325 };
static GLfloat colors[] = { 1.0, 0.2, 0.2,
0.2, 0.2, 1.0,
0.8, 1.0, 0.2,
0.75, 0.75, 0.75,
0.35, 0.35, 0.35,
0.5, 0.5, 0.5 };
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_INT, 0, vertices);
glColorPointer(3, GL_FLOAT, 0, colors);
glBegin(GL_TRIANGLES);
glArrayElement(2);
glArrayElement(3);
glArrayElement(5);
glEnd();
顶点坐标数组,顶点颜色数组,各指定了6个顶点的信息,然后启用顶点坐标数组 和 颜色数组。然后指定了顶点坐标数组和颜色数组,最后使用第2个顶点,第3个顶点,第5个顶点的数据。这三个点的坐标值分别为(175, 25),(175, 325),(325, 325),颜色分别为 (0.8, 1.0, 0.2),(0.75, 0.75, 0.75),(0.5, 0.5, 0.5)。
注意,这个参数 ith,也是从下标0开始的。还有,这个glArrayElement,由于引用的是单个顶点数据,所以要放到glBegin和glEnd之间使用。
解引用数组元素的一个列表
上面访问了数组中的一个顶点,OpenGL的顶点数组还支持访问一个列表内的顶点,比如从顶点0访问到顶点2,这个函数叫glDrawElements。然后还有一个函数,支持访问多个列表内的顶点,比如访问两个列表的顶点,列表1— 顶点0到顶点2 ,列表2— 顶点5到顶点7,这个函数叫glMultiDrawElements。
先来看glDrawElements。
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
参数中mode表示绘制图元的类型,例如GL_POLYGON, GL_LINE_LOOP,GL_LINES和GL_POINTS。参数count表示引用的顶点个数, type 必须是 GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT,GL_UNSIGNED_INT,表示indices数组的数据类型。 indices则表示数组首地址,数组中存放的是顶点下标, 和前面的ith一样。和前面的glArrayElment不一样的是,如果启用了多个顶点数组,则每个数组中的下标在indices中的元素,都不会被访问,当前的RGB颜色、辅助颜色、颜色索引、法线坐标、雾坐标、纹理坐标和边界标志将处于不确定状态。
glDrawElements使用示例如下:
static GLubyte frontIndices[] = {4, 5, 6, 7};
static GLubyte rightIndices[] = {1, 2, 6, 5};
static GLubyte bottomIndices[] = {0, 1, 5, 4};
static GLubyte backIndices[] = {0, 3, 2, 1};
static GLubyte leftIndices[] = {0, 4, 7, 3};
static GLubyte topIndices[] = {2, 3, 7, 6};
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, frontIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, rightIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, bottomIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, backIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, leftIndices);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, topIndices);
注意,由于glDrawElements本身已经是绘制物体了,所以就不要放到glBegin和glEnd之间了。
还可以把几个索引的数组, 合并为一个数组,这样,就像下面这样的:
static GLubyte allIndices[] = {4, 5, 6, 7, 1, 2, 6, 5,
0, 1, 5, 4, 0, 3, 2, 1,
0, 4, 7, 3, 2, 3, 7, 6};
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, allIndices);
上面代码和前面的代码,功能是一样的。
解引用多个列表的顶点
完成解引用多个列表内的顶点的功能,这个函数叫glMultiDrawElements。
void glMultiDrawElements(GLenum mode, GLsizei *count, GLenum type, const GLvoid **indices, GLsizei primcount);
这个函数,其实就是调用一系列的glDrawElements函数。indices是一个二维数组,就是数组的数组,每个元素都是一个数组元素的列表。count是一个数组,包含了每个数组元素列表中的顶点数量。mode, type的含义和glDrawElements的含义相同。而primcount则表示这有几个列表。
调用glMultiDrawElements函数的效果相当于:
for(int i = 0; i < primcount; i++){
if(count[i] > 0)
glDrawElements(mode, count[i], type, indices[i]);
}
下面是glMultiDrawElements函数用法举例:
static GLubyte oneIndices[] = {0, 1, 2, 3, 4, 5, 6};
static GLubyte twoIndices[] = {7, 1, 8, 9, 10, 11};
static GLsizei count[] = {7, 6};
static GLvoid *indices[2] = {oneIndices, twoIndices};
glMultiDrawElements(GL_LINE_STRIP, count, GL_UNSIGNED_BYTE, indices, 2);
这个就是两个顶点列表,然后组合到一个二维数组indices中,然后通过glMultiDrawElements函数,访问两个列表的元素。
还有一个函数,和glDrawElements的功能一样,但是支持对顶点索引的范围限制。这个函数叫glDrawRangeElments。
void glDrawRangeElements(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid *indices);
可以看到,这个函数和glDrawElements相比,就多了两个参数start 和 end。引用indices数组中位于范围[start, end]之外的顶点是错误的做法。但是OpenGL实现并不一定会发现或报告这个错误。因此,非法的索引值可能会产生一个OpenGL错误,也可能不产生错误,这完全取决于具体的OpenGL实现。
解引用一个数组元素序列
最后一个解引用,解引用顶点数组中连续的顶点。这个函数叫glDrawArrays。
void glDrawArrays(GLenum mode, GLint first, GLsizei count);
这个已经不需要指定索引数组的首地址了,因为引用的是之前完全指定好的顶点数组。 只是告诉OpenGL,你给我引用哪个下标开始,连续多少个顶点,然后使用这些顶点来画什么东西就可以了。
同样,也可以访问多个范围内的顶点,这是通过glMultiDrawArrays来实现的。
void glMultiDrawArrays(GLenum mode, GLint *first, GLsizei *count, GLsizei primcount);
既然是多个范围,每个范围都有一个first,有一个count,然后还要告诉OpenGL,这有多少个范围primcount。
2.6.4 重启图元
重启图元,这个概念是指之前中断了图元的绘制操作,然后又重新开启图元绘制。
这是通过一个函数,叫glPrimitiveRestartIndex(GLuint index)来实现的。其中index就是中断图元的顶点索引。
这里有关于重启图元的讲解:
http://www.cnblogs.com/shane/archive/2013/04/23/3037210.html
Here is an example. Let’s say you have an index array as follows:
{ 0 1 2 3 65535 2 3 4 5 }
If you render this as a triangle strip normally, you get 7 triangles. If you render it with glPrimitiveRestartIndex(65535) and the primitive restart enabled, then you will get 4 triangles:
{0 1 2}, {1 2 3}, {2 3 4}, {3 4 5}
意思就是有一个顶点索引数组,然后我们指定数组中的一个元素a为glPrimitiveRestartIndex的参数,那么在进行图形绘制的时候,会在a这个元素索引处中断绘制,然后在a之后,又重启图元。
具体例子,就不看了。
2.6.5 实例化绘制
实例化绘制,是为提高渲染效率而生的。实例化绘制,是指在绘制图元的时候,给图元指定一个实例ID,这个实例ID只在顶点着色器中可用。
与实例化绘制相关的两个函数,叫glDrawArrayInstanced和glDrawElementsInstanced,分别用访问顶点数组中的线性顶点序列,或者访问顶点数组中的离散顶点序列。
void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
primcount次有效地调用glDrawArrays(),在每次调用前设置GLSL顶点着色器gl_InstanceID。mode指定了图元类型。first和count指定了传递给glDrawArrays()的数组元素的范围。
glDrawArraysInstanced()和如下的连续调用具有相同的效果(只不过我们的应用程序不必手动更新gl_InstanceID):
for(int i = 0; i < primcount; i++){
gl_InstanceID = i;
glDrawArrays(mode, first, count);
}
gl_InstanceID = 0;
glDawElmentsInstanced()执行同样的操作,但是允许随机访问顶点数组中的数据。
void glDrawElmentsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indicies, GLsizei primcount);
primcount 次有效地调用glDrawElements(),在每次调用前设置GLSL顶点着色器值gl_InstanceID,mode指定了图元类型。type指定了数组索引的数据类型,并且必须是如下之一:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT或GL_UNSIGNED_INT。indicies和count指定了传递给glDrawElements()的数组元素的范围。
glDrawElementsInstanced()的实现如下:
for(i = 0; i < primcount; i++){
gl_InstanceID = i;
glDrawElements(mode, count, type, indicies);
};
gl_InstanceID = 0;
2.6.6 混合数组
混合数组,我们之前接触过,在介绍stride,跨距这个参数的时候见过。
其实混合数组,就是将顶点坐标、颜色值等顶点相关的信息,全部放到一个数组中,然后通过跨距和 访问元素的首地址 来进行对顶点信息的区分。
这里介绍的混合数组相关的函数,叫glInterleavedArrays()。这个函数组合了“步骤1:启用数组”和“步骤2:指定数组的数据”。至于它的参数,使用起来很复杂,有很多要记忆的东西,不打算看了,PASS。
这又是好长的一节啊,真心不容易。我说作者,你就不能分成一小节一小节的啊,太TM长了。