转至:http://www.cocoachina.com/bbs/simple/?t19434.html
很感谢这位大牛。 未经过他的允许而转了他的文章,有问题请留言。
这是本资料的正式第一讲。
本讲将主要介绍一个最最简单的OpenGL应用,并且附有Mac OS X Snow Leopard系统以及iPhone OS3.1.3系统的OpenGL代码。通过本讲,各位能够了解到OpenGL的基本工作原理、其坐标系统、视口以及基本图元的绘制方法。
本讲将主要通过代码,对涉及到的与本讲主题相关的函数进行详细描述。
好,下面将分别贴出基于45nm Intel Core Architecture的Mac OS X Snow Leopard环境以及基于ARMv6 iPhone OS3.1.3的最基本OpenGL/OpenGL ES的工程代码。
在Mac代码中,你将会看到一个双色正方形;在iPhone代码中,你将会看到渐进色的正方形。
其中,iPhone代码会比较长。但是大家不用害怕,很多代码其实都是Apple自动生成出来的,我们在这一讲将会着重注意后面将贴出来的那部分代码。
新增了oglesBasic.zip资源。这个Demo展示了如何使用MSAA以及vertex array object绑定vertex buffer object。这些特性只有在iOS4.0以上才能使用。
这个Demo采用XCode4.5,并且适配了iPhone5的大屏。
对于冗长的OpenGL ES代码,我们主要看两部分,首先是OpenGL ES状态初始化代码:
复制代码
|
上面这段代码片段是定义在EAGLView.m中的createBuffer方法中。
还有就是绘制代码:
复制代码
|
对于Mac上的OpenGL而言,也同样如此。不过这部分代码就相对比较集中、精简一些,呵呵:
复制代码
|
从目前工程下载量来看,iPhone版的要略高于Mac版的,呵呵。不过这也在意料之内。
这里先引入两个概念:首先OpenGL是一个状态机。在不同状态下做同一个操作会产生不同结果。比如,我们在Mac的代码运行时可以看到两种颜色的正方形;而在iPhone上看到的却是一个渐进色的正方形。其实绘制代码都一样,不同的是在初始化时所采用的着色模型不同而已。Mac上用的是GL_FLAT;iPhone用的是默认的GL_SMOOTH。关于着色模式,本章后面会有更详细的介绍。
其次,OpenGL是一个客户机-服务器体系结构。因此在各种资料中提到的客户机(Client)其实指的就是主机(Host),也就是你的Mac、iPhone或PC的CPU;而服务器(Server)只的就是上述设备中的GPU。当然,最早时候OpenGL很多都是由CPU实现的,不过现在基本上都是由GPU承担大部分的绘制任务。主机端发送一系列的命令给GPU,GPU对命令进行解析,然后获取来自主机端的数据,再做相应的处理。
OpenGL的一个大致流水线为:Application->Command->Geometry->Rasterization->Texture->Fragment->Framebuffer。
中文:应用->命令->几何->光栅化->纹理->片断->帧缓存
好,有了这些基本概念后,下面将对代码中的一些与本次主题相关的函数进行介绍。
好,这里先谈一下OpenGL的坐标系统。
OpenGL的坐标系统是传统的右手坐标系。也就是我们以前中学时上解析几何时用的坐标系。对于一维水平横轴,从左到右数值依次由小到大;对于垂直纵轴,从下到上,数值依次由小到大;对于水平横轴与垂直纵轴的外积所得到的z轴,从后往前,数值依次由小到大。
然后,我们首先看初始化中的一个函数:
void glViewport( GLint x, |
这个函数用于设置视口。所谓视口就是OpenGL窗口所显示的区域。像Mac版本中,视口的宽高均为320;而在iPhone版本中,视口宽为320,高为480。它实现了从设备坐标到窗口坐标的一个转换。
第一、二个参数:x, y,指定视口矩形的左下角的位置,初始值为(0, 0)。
第三第四个参数:width、height,指定了视口的宽和高。
下面先简单地介绍一下glOrtho函数。在Mac下,这个函数只有double类型这一种;而在OpenGL ES下有float和fix两种,因此必须加上后缀。
这个函数有比较深刻的数学意义,呵呵。所以着重将放在视图变换的时候进行介绍,这部分将会有比较深动的例子以及会涉及到一些线性代数知识。
这里把它当作一个2D的来用,其作用可被认为是设置你的坐标轴。先贴出函数原型:
void glOrtho( GLdouble left, |
这里就先介绍前4个参数,后两个可以先分别设为-1和1。
left: 这里可以认为是OpenGL窗口最左边的横轴坐标值;
right:这里可以认为是OpenGL窗口最右边的横轴坐标值;
top: 这里可以认为是OpenGL窗口最上面的纵轴坐标值;
bottom:可以认为是OpenGL窗口最下面的纵轴坐标值。
比如:(-1, 1, -1, 1),我指明了窗口最左边的坐标值为-1;最右边的坐标值为1;最上面的坐标值为1;最下面的坐标值为-1;并且原点整个坐标轴的原点正好处于(0, 0)的位置。
如果是(0, 1, 0, 1),那么坐标轴的原点就在(0.5, 0.5)的位置,呵呵。
如果是(0, 1, 2, 3),那么原点位于(0.5, 2.5)的位置。
如果你所绘制的图形超出了坐标范围,那么超出部分将会被裁剪掉。各位可以通过上面下载的代码来改变这四个值,看看正方形的效果,呵呵。
那么像(-2, 2, -2, 2)与(-1, 1, -1, 1)有何差别呢?
除了前者边界扩大了以外,其分辨率也比后者扩大了1倍。比如,对于一个坐标为:(-0.5, -0.5),长宽分别为1的正方形,放到(-2, 2, -2, 2)的坐标系中就会比放到(-1, 1, -1, 1)看上去就要来得小。
另外,你也可以将X轴左边的坐标值设的比右边的大;Y轴下面的比上面的大。
大家可以试试(1, -1, 1, -1)的效果,呵呵。各位可以看到,画出来的正方形与原来相比正好上下左右颠倒了一下,呵呵。
因此,OpenGL中可以非常灵活地设置坐标。用户可以按照自己的习惯,或者根据当前上下文比较好着手的方式来定义自己的坐标系。
下面介绍一下glEnable/glDisable以及glEnableClientState/glDisableClientState这两对函数。
首先是glEnable/glDisable。这对函数用于打开/关闭服务器端(GPU端)GL能力。这部分能力有很多,比如光照、雾化、纹理等。
在上述代码的mac版本中使用了打开切除多余面的能力。如果打开这个功能,那么背对我们的(我们看不到的)面就会被切除,这样就能减少输出多边形的个数。关于这部分内容将放到3D视图部分进行讲述。
函数原型为:
void glEnable( GLenum cap); |
cap为一个枚举值,比如:GL_CULL_FACE、GL_FOG等。
glEnableClientState/glDisableClientState是打开/关闭客户端(CPU端)能力。
它提示给服务器端,客户端所给出的顶点、纹理的组织方式。像上述代码中:GL_COLOR_ARRAY以及GL_VERTEX_ARRAY表示顶点是以数组的形式存放,并且对应有颜色信息。我们可以尝试把iPhone代码中的glEnableClientState(GL_COLOR_ARRAY);这句代码注释掉,看看画出来的正方形是啥德性,呵呵。你将会看到一个全白的正方形。
下面将贴出这对函数的原型:
void glEnableClientState( GLenum cap); |
好。OpenGL初始化部分基本上讲好了,后面将详细介绍简单图元的绘制方法。
好。接下来我们开始正式讲图元绘制。
在drawView方法中的代码就是绘制代码。drawView会在视图需要刷新时被系统调用。
首先看glClear(GL_COLOR_BUFFER_BIT);
这个函数是用于清除指定的缓冲。比如这里就是清除颜色缓冲。它的效果就是将OpenGL整个窗口用指定的颜色填充。这个指定的颜色就是我们在初始化时调glClearColor来指定的。
先给出glClear的原型——
void glClear( GLbitfield mask); |
下面再回过头来介绍一下glClearColor。glClearColor用于指定清除缓冲的颜色值。如果是红色,那么调用glClear(GL_COLOR_BUFFER_BIT);就会使窗口充满红色,呵呵。下面贴出glClearColor原型——
void glClearColor( GLclampf red, |
这里参数很明显,分别是RGBA,即红色分量、绿色分量以及蓝色分量和透明度的值。像上述代码中,(R, G, B) = (0, 0, 0)表示黑色。而透明度值alpha,0表示完全透明,即选用背景色;而1则表示完全用当前对象的颜色。由于是作为背景色的设置,因此这里alpha值没有任何作用。
关于RGB颜色分量的详细介绍,各位可以在Google上找到很多相关资料,呵呵。
好下面就是正式的绘制方法了。
首先介绍OpenGL中最最基本、简单的绘图方法——glBegin/glEnd函数对。由于这两个函数在GPU实现时效率可能比较低下,因此不推荐各位使用。另外,这两个函数没有OpenGL ES1.1版本。
glBegin/glEnd对通过指定绘制模式,然后逐一指定顶点信息和颜色信息进行绘制。比如下面是绘制一个正方形的代码片段:
glBegin(GL_TRIANGLE_STRIP); |
将上述代码替换掉1楼Mac部分代码的第41到44行,可以看到效果是与原来完全一样的。
好,由于glBegin/glEnd用得不多,而且实用性也不大,因此一笔带过,下面将主要讲解用得更多的glDrawArrays以及glDrawElements。
下面介绍glDrawArrays函数,先给出函数原型:
void glDrawArrays( GLenum mode, |
该函数的第一个参数用于指定绘制模式。关于绘制模式我们稍后会有详细的介绍。
第二个参数用于指定所允许的顶点的起始顶点。比如说,我们之前定义了5个顶点,但是我们在画正方形的时候想跳过第一个顶点,从第二个顶点开始画,那么索引就是1(起始索引值为0)。
第三个参数要绘制的顶点的个数。
好。下面我们可以先尝试一下当不给first参数传0的情况:
static struct |
我们将上述代码片段替换1楼中iPhone部分代码的第5到22行。我们会发现出现的正方形与原来一样。我们可以在此基础上把glDrawArrays(GL_TRIANGLE_STRIP, 1, 4);改成glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);那么我们将会看到一个斜三角形。
下面将介绍glVertexPointer以及glColorPointer。
GPU的一个特点是拥有大规模的线程以及超大的带宽。因此,我们用glBegin/glEnd对去一点点绘制图形的话对GPU而言是非常低效的。GPU喜欢做的是一口气把所有命令读上来,然后分派到不同的流处理器去执行。每条流处理器又有很多个线程。GPU的流水线比较长,做的工作也是比较特定的。为了方便GPU来处理主机端的数据,我们通常会把顶点信息以及其相应的颜色信息组成连续存放的数组形式。这样既有利于存储器访问,而且也利于GPU快速加载(比如通过内部的DMA)。
glVertexPointer函数定义了一组顶点数据的一个数组。其原型如下:
void glVertexPointer( GLint size, |
第一个参数指定了每个顶点的坐标分量个数。比如,我们这里一个顶点的坐标有三个分量,分别是:x,y,z。如果只有两个分量的话,z分量在处理时会被置0;如果有4个分量,那么就会增加w分量。w分量用于线性变换,这将是下一讲的话题。这个参数只能是2、3或4。
第二个参数用于指定顶点每个坐标分量的数据类型。可以是:GL_SHORT、GL_INT、GL_FLOAT或GL_DOUBLE。在OpenGL ES中没有GL_DOUBLE。
这里稍微讲一下顶点坐标分量的数据类型。每个顶点的所有坐标分量必须的数据类型必须是一致的,不能定义x分量时用int,然后定义y 的时候就用float。并且每个顶点的坐标分量类型必须相同。
在GPU中,其专门有快速浮点计算,因此提高GPU处理数据的速度往往会使用float类型,而不是int。不过即使是当前主流桌面计算机用的GPU,其对double类型的支持比较弱,因为double需要消耗2倍于float的带宽。所以就目前而言,不管是OpenGL还是OpenGL ES,往往使用GLfloat类型来定义顶点坐标,以使得做线性变换计算时能充分利用GPU的计算单元的性能。
第三个参数指定相邻两个顶点的偏移字节数。像1楼iPhone部分代码中,用的是:glVertexPointer(3, GL_FLOAT, 16, vertexInfoList[0].vertices);
因为第一个顶点的起始地址与第二个顶点的起始地址之间相差16个字节——即sizeof(vertexInfoList[0])。
第四个参数用来指定顶点数组的起始位置。
glColorPointer与glVertexPointer定义一样。它用来指定一组顶点颜色信息的数组。其原型如下:
void glColorPointer( GLint size, |
第一个参数是每个颜色信息的分量个数,在OpenGL中可以是3或4;而OpenGL ES1.1中必须是4。如果是3个分量,则依次表示红(R)、绿(G)、蓝(B);如果是4个分量,那么前三个与前面的一样,第四个分量是alpha,表示透明度。
第二个参数用于指定颜色分量的数据类型,可以是:GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT、GL_DOUBLE;不过在OpenGL ES1.1中,常常使用GL_UNSIGNED_BYTE或GL_FLOAT。
第三个和第四个参数与glVertexPointer一样。
通过指定顶点的坐标信息和颜色信息,后面就可以利用glDrawArrays进行绘制了,呵呵。
好。我们下面再介绍一下如何用glDrawElements来绘制图形。
glDrawElements用于通过一个用户自定义的索引数组来绘制图形。下面看看其原型:
void glDrawElements( GLenum mode, |
第一个参数用于指定图元绘制模式;
第二个参数用于指定所要绘制顶点的个数;
第三个参数用于指定索引的数据类型;
第四个参数用于指定索引的起始地址。
这个函数可以很方便地用于绘制多个图形。首先,顶点信息往往比较大(由于包括了一个顶点的所有坐标分量信息以及颜色分量信息,甚至还有法线信息等),因此如果我们所要绘制的图形通过已指定的顶点就能绘制出,就无需重复地定义顶点,而只需要通过一个简短的索引数组就能解决问题。
其次,调用一条命令绘制图形往往比调多次效率要高,功耗要小。
下面有一个示例代码:
static GLubyte __attribute__((aligned(4))) indices[] = {0, 1, 3, 1, 2, 3}; |
将这代码替换1楼iPhone部分代码片段中第22行。我们会看到两个交错的三角形。
好,今天开始讲图元绘制模式。在OpenGL中图元绘制模式有: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, 以及GL_POLYGON。
在OpenGL ES1.1中,图元绘制模式有:GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN以及GL_TRIANGLES。
下面我们将逐一介绍。
首先是GL_POINTS,这个模式是仅绘制顶点。当我们使用glDrawArrays时,顶点通过由glVertexPointer和glColorPointer所指定的顺序将依次被绘制出。
我们可以尝试一下:
glDrawArrays(GL_POINTS, 0, 4); |
将上述代码替换掉原来的对glDrawArrays的调用。
GL_LINE_STRIP模式:
这个模式用于绘制线段带。比如现在有顶点v0, v1, v2, v3,那么绘制出的线段为带由三条线段构成,依次为:(v0, v1), (v1, v2), (v2, v3)。
glDrawArrays(GL_LINE_STRIP, 0, 4); |
将上述代码替换掉原来的对glDrawArrays的调用,然后查看结果。
GL_LINE_LOOP模式:
这个模式与GL_LINE_STRIP模式一样,除了最后一个顶点与第一个顶点仍然连成一条线段。比如,有4个顶点:v0, v1, v2, v3,那么构成的线段带为:(v0, v1), (v1, v2), (v2, v3), (v3, v0),共4条线段。
glDrawArrays(GL_LINE_LOOP, 0, 4); |
请将上述代码替换掉原来对glDrawArrays的调用,观察结果。
GL_LINES模式:
该模式是对两个顶点仅绘制一条线段,并且每个顶点在且在一条线段上,由该模式下所绘制出的线段组中,不会有相交的两条线段。比如有4个顶点:v0, v1, v2, v3,那么绘制出的线段为:(v0, v1), (v2, v3);如果只有三个顶点:(v0, v1, v2),那么将只有(v0, v1)一条线段。
glDrawArrays(GL_LINES, 0, 4); |
将上述代码替换掉原来代码中对glDrawArrays的调用,并观察结果。然后将4改为3再观察结果。
GL_TRIANGLE_STRIP模式:
这个模式就是示例代码中所采用的绘制模式。它是将前三个顶点构成一个三角形后,从第四个顶点开始由先前所构成的三角形的某一条边作为公共边然后构造后一个三角形。而构造后一个三角形的绘制顺序依赖于构造第一个三角形所采用的绘制顺序。
GL_TRIANGLE_FAN模式:
这个模式在构造三角形时始终以第一个顶点作为初始顶点,然后与后两个顶点构成三角形,因此构造顺序犹如打开一把折扇,呵呵。
static struct |
将上述代码替换掉1楼中Mac部分代码片段中的第26到44行,观察结果。
GL_TRIANGLES模式:
该模式与GL_LINES模式类似,取三个顶点构成一个独立的三角形,并且顶点数组中每个顶点只对应于一个三角形。
好,最后以用GL_TRIANGLE_FAN模式绘制一个圆形来结束本章。
这个圆是以(0, 0)为圆心,0.8为半径绘制成的。
用于Mac:
复制代码
|