2.6 顶点数组

顶点数组,顾名思义,就是存放顶点的数组。

为什么使用顶点数组?

为什么要将顶点存放到数组中啊?方便管理,省事。之前是这样指定物体的顶点的:

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长了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值