转:子龙山人opengl教程(下)
(续上篇) 为这个简单的长方形创建 vertex data！ 在这里,我们打算在屏幕上渲染一个正方形,如下图: 在你用opengl渲染图形的时候,时刻要记住一点,你只能直接渲染三角形,而不是其它诸如矩形的图形。所以,一个正方形需要分开成两个三角形来渲染。 图中分别是顶点(0,1,2)和顶点(0,2,3)构成的三角形。 opengl es2.0的一个好处是,你可以按你的风格来管理顶点。 打开openglview.m文件,创建一个纯粹的c结构以及一些array来跟踪我们的矩形信息,如下: typedef str ct {float position[3]；float color[4]； } vertex；const vertex vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {0, 1, 0, 1}}, {{-1, 1, 0}, {0, 0, 1, 1}}, {{-1, -1, 0}, {0, 0, 0, 1}} }；const gl byte indices[] = {0, 1, 2,2, 3, 0 }； 复制代码 这段代码的作用是: 1 一个用于跟踪所有顶点信息的结构vertex (目前只包含位置和颜色。) 2 定义了以上面这个vertex结构为类型的array。 3 一个用于表示三角形顶点的数组。 数据准备好了,我们来开始把数据传入opengl 创建vertex b ffer 对象 传数据到opengl的话,最好的方式就是用vertex b ffer对象。 基本上,它们就是用于缓存顶点数据的opengl对象。通过调用一些f nction来把数据发送到opengl-land。(是指opengl的画面？) 这里有两种顶点缓存类型- 一种是用于跟踪每个顶点信息的(正如我们的vertices array),另一种是用于跟踪组成每个三角形的索引信息(我们的indices array)。 下面我们在initwithframe中,加入一些代码: [self set pvbos]； 下面是定义这个set pvbos: - (void)set pvbos { gl int vertexb ffer； glgenb ffers(1, &vertexb ffer)； glbindb ffer(gl_array_buffer, vertexb ffer)； glb fferdata(gl_array_buffer, sizeof(vertices), vertices, gl_static_draw)； gl int indexb ffer； glgenb ffers(1, &indexb ffer)； glbindb ffer(gl_element_array_buffer, indexb ffer)； glb fferdata(gl_element_array_buffer, sizeof(indices), indices, gl_static_draw)； } 复制代码 如你所见,其实很简单的。这其实是一种之前也用过的模式(pattern)。 glgenb ffers - 创建一个vertex b ffer 对象 glbindb ffer - 告诉opengl我们的vertexb ffer 是指gl_array_buffer glb fferdata - 把数据传到opengl-land 想起哪里用过这个模式吗？要不再回去看看frame b ffer那一段？ 万事俱备,我们可以通过新的shader,用新的渲染方法来把顶点数据画到屏幕上。 用这段代码替换掉之前的render: - (void)render { glclearcolor(0, 104.0255.0, 55.0255.0, 1.0)； glclear(gl_color_buffer_bit)； 1 glviewport(0, 0, self.frame.size.width, self.frame.size.height)； 2 glvertexattribpointer(_positionslot, 3, gl_float, gl_false, sizeof(vertex), 0)； glvertexattribpointer(_colorslot, 4, gl_float, gl_false, sizeof(vertex), (glvoid*) (sizeof(float) *3))； 3 gldrawelements(gl_triangles, sizeof(indices)sizeof(indices[0]), gl_unsigned_byte, 0)； [_context presentrenderb ffer:gl_renderbuffer]； } 复制代码 1 调用glviewport 设置uiview中用于渲染的部分。这个例子中指定了整个屏幕。但如果你希望用更小的部分,你可以更变这些参数。 2 调用glvertexattribpointer来为vertex shader的两个输入参数配置两个合适的值。 第二段这里,是一个很重要的方法,让我们来认真地看看它是如何工作的: ·第一个参数,声明这个属性的名称,之前我们称之为glgetattriblocation ·第二个参数,定义这个属性由多少个值组成。譬如说position是由3个float(x,y,z)组成,而颜色是4个float(r,g,b,a) ·第三个,声明每一个值是什么类型。(这例子中无论是位置还是颜色,我们都用了gl_float) ·第四个,嗯...它总是false就好了。 ·第五个,指 stride 的大小。这是一个种描述每个 vertex数据大小的方式。所以我们可以简单地传入 sizeof(vertex),让编译器计算出来就好。 ·最好一个,是这个数据结构的偏移量。表示在这个结构中,从哪里开始获取我们的值。position的值在前面,所以传0进去就可以了。而颜色是紧接着位置的数据,而position的大小是3个float的大小,所以是从 3 * sizeof(float) 开始的。 回来继续说代码,第三点: 3 调用gldrawelements ,它最后会在每个vertex上调用我们的vertex shader,以及每个像素调用fragment shader,最终画出我们的矩形。 它也是一个重要的方法,我们来仔细研究一下: ·第一个参数,声明用哪种特性来渲染图形。有gl_line_strip 和 gl_triangle_fan。然而gl_triangle是最常用的,特别是与vbo 关联的时候。 ·第二个,告诉渲染器有多少个图形要渲染。我们用到c的代码来计算出有多少个。这里是通过个 array的byte大小除以一个indice类型的大小得到的。 ·第三个,指每个indices中的index类型 ·最后一个,在官方文档中说,它是一个指向index的指针。但在这里,我们用的是vbo,所以通过index的array就可以访问到了(在gl_element_array_buffer传过了),所以这里不需要. 编译运行的话,你就可以看到这个画面喇。 你可能会疑惑,为什么这个长方形刚好占满整个屏幕。在缺省状态下,opengl的"camera"位于(0,0,0)位置,朝z轴的正方向。 当然,后面我们会讲到projection(投影)以及如何控制camera。 增加一个投影 为了在2d屏幕上显示3d画面,我们需要在图形上做一些投影变换,所谓投影就是下图这个意思: 基本上,为了模仿人类的眼球原理。我们设置一个远平面和一个近平面,在两个平面之前,离近平面近的图像,会因为被缩小了而显得变小；而离远平面近的图像,也会因此而变大。 打开simplevertex.glsl,做一下修改:  add right before the main niform mat4 projection； modify gl_position line as followsgl_position = projection * position； 复制代码 这里我们增加了一个叫做projection的传入变量。 niform 关键字表示,这会是一个应用于所有顶点的常量,而不是会因为顶点不同而不同的值。 mat4 是 4x4矩阵的意思。然而,matrix math是一个很大的课题,我们不可能在这里解析。所以在这里,你只要认为它是用于放大缩小、旋转、变形就好了。 position位置乘以projection矩阵,我们就得到最终的位置数值。 无错,这就是一种被称之"线性代数"的东西。我在大学时期后,早就忘大部分了。 其实数学也只是一种工具,而这种工具已经由前面的才子解决了,我们知道怎么用就好。 bill hollings,cocos3d的作者。他编写了一个完整的3d特性框架,并整合到cocos2d中。(作者:可能有一天我也会弄一个3d的教程)无论任何,cocos3d包含了objective-c的向量和矩阵库,所以我们可以很好地应用到这个项目中。 这里, xjlafny7l.clo  有一个zip文件,(作者:我移除了一些不必要的依赖)下载并copy到你的项目中。记得选上:"copy items into destination gro p s folder (if needed)" 点击finish。 在openglview.h 中加入一个实例变量: gl int _projectionuniform； 然后到openglview.m文件中加上:  add to top of file#import "cc3glmatrix.h" add to bottom of compileshaders_projectionuniform = glgetuniformlocation(programhandle, "projection")； add to render, right before the call to glviewportcc3glmatrix *projection = [cc3glmatrix matrix]；float h =4.0f* self.frame.size.height  self.frame.size.width； [projection pop latefromfr st mleft:-2 andright:2 andbottom:-h2 andtop:h2 andnear:4 andfar:10]； gluniformmatrix4fv(_projectionuniform, 1, 0, projection.glmatrix)； modify vertices so they are within projection nearfar planesconst vertex vertices[] = { {{1, -1, -7}, {1, 0, 0, 1}}, {{1, 1, -7}, {0, 1, 0, 1}}, {{-1, 1, -7}, {0, 0, 1, 1}}, {{-1, -1, -7}, {0, 0, 0, 1}} }； 复制代码 ·通过调用 glgetuniformlocation 来获取在vertex shader中的projection输入变量 ·然后,使用math library来创建投影矩阵。通过这个让你指定坐标,以及远近屏位置的方式,来创建矩阵,会让事情比较简单。 ·你用来把数据传入到vertex shader的方式,叫做 gluniformmatrix4fv. 这个cc3glmatrix类有一个很方便的方法 glmatrix,来把矩阵转换成opengl的array格式。 ·最后,把之前的vertices数据修改一下,让z坐标为-7. 编译后运行,你应该可以看到一个稍稍有点距离的正方形了。 尝试移动和旋转吧 如果总是要修改那个vertex array才能改变图形,这就太烦人了。 而这正是变换矩阵该做的事(又来了,线性代数) 在前面,我们修改了应用到投影矩阵的vertex array来达到移动图形的目的。何不试一下,做一个变形、放大缩小、旋转的矩阵来应用？我们称之为"model-view"变换。 再回到 simplevertex.glsl  add right after the projection niform niform mat4 modelview； modify the gl_position linegl_position = projection * modelview * position； 复制代码 就是又加了一个 uniform的矩阵而已。顺便把它应用到gl_position当中。 然后到 openglview.h中加上一个变量: gl int _modelviewuniform； 到openglview.m中修改:  add to end of compileshaders_modelviewuniform = glgetuniformlocation(programhandle, "modelview")； add to render, right before call to glviewportcc3glmatrix *modelview = [cc3glmatrix matrix]； [modelview pop latefromtranslation:cc3vectormake(sin(cac rrentmediatime), 0, -7)]； gluniformmatrix4fv(_modelviewuniform, 1, 0, modelview.glmatrix)； revert vertices back to z-val e 0const vertex vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {0, 1, 0, 1}}, {{-1, 1, 0}, {0, 0, 1, 1}}, {{-1, -1, 0}, {0, 0, 0, 1}} }； 复制代码 ·获取那个model view niform的传入变量 ·使用cocos3d math库来创建一个新的矩阵,在变换中装入矩阵。 ·变换是在z轴上移动-7,而为什么sin(当前时间) 呢？ 哈哈,如果你还记得高中时候的三角函数。sin是一个从-1到1的函数。已pi(3.14)为一个周期。这样做的话,约每3.14秒,这个函数会从-1到1循环一次。 ·把vertex 结构改回去,把z坐标设回0. 编译运行,就算我们把z设回0,也可以看到这个位于中间的正方形了。 什么？一动不动的？ 当然了,我们只是调用了一次render方法。 接下来,我们在每一帧都调用一次看看。 渲染和 cadisplaylink 理想状态下,我们希望opengl的渲染频率跟屏幕的刷新频率一致。 幸运的是,apple为我们提供了一个cadisplaylink的类。这个很好用的,马上就用吧。 在openglview.m文件,修改如下:  add new method before init- (void)set pdisplaylink { cadisplaylink* displaylink = [cadisplaylink displaylinkwithtarget:self selector:@selector(render:)]； [displaylink addtor nloop:[nsr nloop c rrentr nloop] formode:nsdefa ltr nloopmode]； } modify render method to take a parameter- (void)render:(cadisplaylink*)displaylink { remove call to render in initwithframe and replace it with the following[self set pdisplaylink]； 复制代码 这就行了,有cadisplaylink在每一帧都调用你的render方法,我们的图形看起身就好似被sin周期地变型了。现在这个方块会前前后后地来回移动。 不费功夫地旋转 让图形旋转起来,才算得上有型。 再到openglview.h 中,添加成员变量。 float _c rrentrotation； 在openglview.m的render中,在pop latefromtranslation的调用后面加上: _c rrentrotation += displaylink.d ration *90； [modelview rotateby:cc3vectormake(_c rrentrotation, _c rrentrotation, 0)]； 复制代码 ·添加了一个叫_c rrentrotation的float,每秒会增加90度。 ·通过修改那个model view矩阵(这里相当于一个用于变型的矩阵),增加旋转。 ·旋转在x、y轴上作用,没有在z轴的。 编译运行,你会看到一个很有型的翻转的3d效果。 不费功夫地变成3d方块？ 之前的只能算是2.5d,因为它还只是一个会旋转的面而已。现在我们把它改造成3d的。 把之前的vertices、indices数组注释掉吧。 然后加上新的: const vertex vertices[] = { {{1, -1, 0}, {1, 0, 0, 1}}, {{1, 1, 0}, {1, 0, 0, 1}}, {{-1, 1, 0}, {0, 1, 0, 1}}, {{-1, -1, 0}, {0, 1, 0, 1}}, {{1, -1, -1}, {1, 0, 0, 1}}, {{1, 1, -1}, {1, 0, 0, 1}}, {{-1, 1, -1}, {0, 1, 0, 1}}, {{-1, -1, -1}, {0, 1, 0, 1}} }；const gl byte indices[] = { front0, 1, 2,2, 3, 0, back4, 6, 5,4, 7, 6, left2, 7, 3,7, 6, 2, right0, 4, 1,4, 1, 5, top6, 2, 1, 1, 6, 5, bottom0, 3, 7,0, 7, 4 }； 复制代码 编译运行,你会看到一个方块了。 但这个方块有时候让人觉得假,因为你可以看到方块里面。 这里还有一个叫做 depth testing(深度测试)的功能,启动它,opengl就可以跟踪在z轴上的像素。这样它只会在那个像素前方没有东西时,才会绘画这个像素。 到openglview.h中,添加成员变量。 gl int _depthrenderb ffer； 在openglview.m:  add new method right after set prenderb ffer- (void)set pdepthb ffer { glgenrenderb ffers(1, &_depthrenderb ffer)； glbindrenderb ffer(gl_renderbuffer, _depthrenderb ffer)； glrenderb fferstorage(gl_renderbuffer, gl_depth_component16, self.frame.size.width, self.frame.size.height)； } add to end of set pframeb fferglframeb fferrenderb ffer(gl_framebuffer, gl_depth_attachment, gl_renderbuffer, _depthrenderb ffer)； in the render method, replace the call to glclear with the followingglclear(gl_color_buffer_bit | gl_depth_buffer_bit)； glenable(gl_depth_test)； add to initwithframe, right before call to set prenderb ffer[self set pdepthb ffer]； 复制代码 ·set pdepthb ffer方法创建了一个depth b ffer。这个与前面的rendercolor b ffer类似,不再重复了。值得注意的是,这里使用了glrenderb fferstorage, 然不是context的renderb fferstorage(这个是在opengl的view中特别为color render b ffer而设的)。 ·接着,我们调用glframeb fferrenderb ffer,来关联depth b ffer和render b ffer。还记得,我说过frame b ffer中储存着很多种不同的b ffer？这正是一个新的b ffer。 ·在render方法中,我们在每次 pdate时都清除深度b ffer,并启用depth testing。 编译运行,看看这个教程最后的效果。 一个选择的立方块,用到了opengl es2.0。 何去何从？ 这里有本教程的完整源代码。 这只是opengl的一篇引导教程,希望能让你轻松地入门。 对了,我写这篇教程的原因是它在过去的数周中得票最高。谢谢大家的关注,并继续在今后为自己感兴趣的题目投上一票 - 我们每周都有一个新教程的。 著作权声明:本文由 e翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢！