http://jimmyzhouj.blog.51cto.com/2317513/883520

原创作品,允许转载,转载时请务必以超链接形式标明文章  原始出处 、作者信息和本声明。否则将追究法律责任。 http://jimmyzhouj.blog.51cto.com/2317513/883520

[jimmyzhouj 翻译]  Nehe iOS OpenGL ES 2.0教程

免责声明:本教程翻译原稿均来自互联网,仅供学习交流之用,请勿进行商业传播。同时,转载时不要移除本声明。如产生任何纠纷,均与本博客所有人和发表该翻译稿之人无任何关系。谢谢合作!
IOS Lesson 02 – 第一个三角形
原文链接地址: http://nehe.gamedev.net/tutorial/ios_lesson_02__first_triangle/50001/
上节课我们重点介绍了程序的基本结构,本次课程我们要真正开始绘画了。下图就是程序运行时显示内容的截屏:

使用OpenGL将几何图形(geometry)绘制到屏幕上需要完成几个步骤。首先我们需要告诉OpenGL绘制什么几何图形。通常是一系列的三角形,每个三角形用三个顶点来指定。OpenGL为每个顶点调用顶点着色器(vertex shader),它可以通过旋转和移动来执行几何变换,或者是简单的光照(lighting)。得到的平面被光栅化(rasterized),意思是OpenGL会计算出被各平面覆盖的所有像素点。对每一个被覆盖的像素点,OpenGL会调用片段着色器(fragment shader,如果我们使用多重抽样,每个像素会运行不止一次,所以总的来说这是片段而不是像素)。片段着色器的任务是根据颜色,光照来决定片段的颜色,或者是将图像映射到几何图形上。

因此本课程包括两块内容:怎么样绘制一个三角形,和一个简单的着色器是什么样的。
你最好先下载源代码,然后我们进入下一步。
上传几何图形

几何图形可以被指定为几种几何图元:GL_POINT, GL_LINES或者GL_TRIANGLES,其中直线和三角形都有一些变体,可以绘制chain的或者strip的。所有这些几何图元都是由一组顶点组成的。一个顶点是三维空间的一个点,参照坐标系是x轴指向正右方,y轴指向正上方,z轴垂直于屏幕指向读者。

当我们之后讲到移动,旋转和投影课程的时候我们会好好讲讲深度(depth)。现在你只要知道我们需要三个浮点数来表示x,y,z的位置,还需要额外的第四个元素w来实现线性变换(由于矩阵乘法的原因)。
在顶点着色器的最后,屏幕上的每一个顶点都必须在一个立方体内,立方体在x,y,z上的坐标范围都是从-1.0到1.0。我们不做复杂的变换,所以我们选择的顶点位置符合这个规则。
在Lesson02::init方法中,我们将顶点存为浮点数的连续数组
  
  1. //create a triangle
  2. std::vector<float> geometryData;
  3. //4 floats define one vertex (x, y, z and w), first one is lower left
  4. geometryData.push_back(-0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
  5. //we go counter clockwise, so lower right vertex next
  6. geometryData.push_back( 0.5); geometryData.push_back(-0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
  7. //top vertex is last
  8. geometryData.push_back( 0.0); geometryData.push_back( 0.5); geometryData.push_back(0.0); geometryData.push_back(1.0);
可能你已经注意到,我们是按照逆时针的次序指定顶点的。这有助于帮助OpenGL丢弃掉我们看不见的几何图形。根据定义,只有逆时针方向的三角形才是面向(facing towards)我们的(所以表面只有一侧)。假设你要翻转这个三角形,只要你翻转超过90度了,顶点的次序也翻转了。
我们现在已经有一个浮点数数组了,怎么把它传递给OpenGL呢?
我们使用了一个概念叫做Vertex Buffer Objects(VBO)。这就是在图像内存中分配一块缓冲区(buffer),然后将几何数据移到那里。当我们之后绘制几何图形时,数据已经在芯片上了,这样就减少了每一帧传递数据的带宽要求。对那些在程序运行时不发生变形(deform)的几何图形,这会工作的很好。一旦几何图形的拓扑发生了变化,必须将新的数据拷贝到缓冲区中。其他各种变换,比如移动,旋转,伸缩都能很容易在顶点着色器中实现,不需要改变缓冲区的内容。
  
  1. //generate an ID for our geometry buffer in the video memory and make it the active one
  2. glGenBuffers(1, &m_geometryBuffer);
  3. glBindBuffer(GL_ARRAY_BUFFER, m_geometryBuffer);
  4. //send the data to the video memory
  5. glBufferData(GL_ARRAY_BUFFER, geometryData.size() * sizeof(float), &geometryData[0], GL_STATIC_DRAW);
第一步是在图像内存中生成一个缓冲区。可以调用函数glGenBuffers,第一个参数是要分配的缓冲区的数目,第二个参数是指针,指向保存缓冲区的 id的变量。在头文件中,m_geometryBuffer定义为unsigned int。
接下来绑定缓冲区,之后的所有缓冲区操作都是在我们的geometry buffer上执行的。GL_ARRAY_BUFFER指我们在这里保存简单数据,我们一直使用这个缓冲区直到重新索引到别的缓冲区中去。
最后,我们调用函数glBufferData把几何数据送到VBO中。几个参数分别为:类型,要拷贝的字节长度,指向数据的指针,和是否允许显示芯片做内存优化。对于静态图形,最后一个参数用GL_STATIC_DRAW,如果我们想定期上传新数据,这里要用GL_DYNAMIC_DRAW。
添加颜色
到现在为止,我们传递了几何数据,已经可以用纯色画一个三角形了。不过我们还可以
用前面的方法来实现顶点的其他属性(attributes),比如我们可以给每个顶点指定颜色。
在大部分图形应用中,颜色被指定为红,绿,蓝通道的强度( intensities)。在大部分图片格式中强度取值范围为0到255,不过我们在图形芯片上使用的是浮点数,强度范围指定为0到1。
颜色数据的概念和之前几何数据一样:
  
  1. //create a color buffer, to make our triangle look pretty
  2. std::vector<float> colorData;
  3. //3 floats define one color value (red, green and blue) with 0 no intensity and 1 full intensity
  4. //each color triplet is assigned to the vertex at the same position in the buffer, so first color -> first vertex
  5. //first vertex is red
  6. colorData.push_back(1.0); colorData.push_back(0.0); colorData.push_back(0.0);
  7. //lower right vertex is green
  8. colorData.push_back(0.0); colorData.push_back(1.0); colorData.push_back(0.0);
  9. //top vertex is blue
  10. colorData.push_back(0.0); colorData.push_back(0.0); colorData.push_back(1.0);
  11. //generate an ID for the color buffer in the video memory and make it the active one
  12. glGenBuffers(1, &m_colorBuffer);
  13. glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
  14. //send the data to the video memory
  15. glBufferData(GL_ARRAY_BUFFER, colorData.size() * sizeof(float), &colorData[0], GL_STATIC_DRAW);
绘图(drawing)
现在所有的数据已经保存在图像内存中了,我们只需要把它显示出来。这通过告诉OpenGL我们要使用的着色器程序来实现。
我们不会详细解释着色器的加载工作。有兴趣了解细节的人可以去看Shader类已有的丰富文档。如果有人需要更细致的解释,请在论坛里告诉我。
  
  1. //load our shader
  2. m_shader = new Shader("shader.vert", "shader.frag");
  3. if(!m_shader->compileAndLink())
  4. {
  5. NSLog(@"Encountered problems when loading shader, application will crash...");
  6. }
  7. //tell OpenGL to use this shader for all coming rendering
  8. glUseProgram(m_shader->getProgram());
在代码里,我们创建了Shader类的一个对象,告诉它输入文件的名字。就像之前说过的,有顶点着色器和片段着色器两个阶段。它们分别加载,编译自己的输入文件,最后链接到着色器程序,它是一个vertex shader和fragment shader对。这在compileAndLink()方法中实现,如果出错的话,会返回false。
最后,我们调用glUseProgram来告诉OpenGL使用我们刚刚产生的着色器程序。
真简单。现在怎么样把几何数据和颜色数据送给着色器呢?
对每一个顶点,顶点着色器可以处理几个输入值,如之前添加颜色缓冲区时所说的。这些值被称为属性(attributes)。只要是全局定义了属性,就可以在着色器里访问它们。比如attribute vec4 position,表示有一个属性,名字是position,被指定为一个有4个浮点值的向量。
从程序的观点来看,我们需要把geometry buffer里面的数据分配给名字是position的属性,将color buffer的数据分配给名字是 color的属性。也可以用别的名字,但在这节课里还是用这个吧。
  
  1. //get the attachment points for the attributes position and color
  2. m_positionLocation = glGetAttribLocation(m_shader->getProgram(), "position");
  3. m_colorLocation = glGetAttribLocation(m_shader->getProgram(), "color");
  4. //check that the locations are valid, negative value means invalid
  5. if(m_positionLocation < 0 || m_colorLocation < 0)
  6. {
  7. NSLog(@"Could not query attribute locations");
  8. }
  9. //enable these attributes
  10. glEnableVertexAttribArray(m_positionLocation);
  11. glEnableVertexAttribArray(m_colorLocation);
OpenGL会索引着色器程序里的所有属性,通过调用函数glGetAttribLocation并且传给它着色器程序和程序源码中属性的名字,我们可以得到并存储索引值。m_positionLocation和m_colorLocation是int型的,所有无效的属性名都会得到-1的返回值。
最后我们调用glEnableVertexAttribArray来允许传送数据给这些属性。
到现在为止我们已经完成了初始化方法的所有工作了。但实际的绘制在每一帧都要发生。所以在Lesson02::draw 方法中,我们需要将缓存的数据映射到属性,然后开始绘制。
  
  1. //bind the geometry VBO
  2. glBindBuffer(GL_ARRAY_BUFFER, m_geometryBuffer);
  3. //point the position attribute to this buffer, being tuples of 4 floats for each vertex
  4. glVertexAttribPointer(m_positionLocation, 4, GL_FLOAT, GL_FALSE, 0, NULL);
为了映射数据,首先我们要将geometry buffer绑定以激活它,就像之前传送数据时做的那样。然后我们告诉OpenGL这就是position属性要获得数据的缓冲区。glVertexAttribPointer可以实现这个功能,参数分别是:属性的位置,多少个数组成一个元素(这里1个顶点有4个浮点数 ),数据类型(float),数据是否需要向量化(通常不用),如果交错数据( interleave data)的话需要一个跨距(stride)(这里没有,0),最后一个参数是指向一个数组的指针,每一帧该数组将被拷贝到图形芯片中。我们这里传递NULL,告诉OpenGL使用当前绑定的缓冲区内容。
  
  1. //bint the color VBO
  2. glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
  3. //this attribute is only 3 floats per vertex
  4. glVertexAttribPointer(m_colorLocation, 3, GL_FLOAT, GL_FALSE, 0, NULL);
绑定color buffer的过程和前面的一样。
现在已经万事俱备,可以画三角形了。
  
  1. //initiate the drawing process, we want a triangle, start at index 0 and draw 3 vertices
  2. glDrawArrays(GL_TRIANGLES, 0, 3);

函数glDrawArrays需要三个参数:我们要画的图元(可以是GL_POINTS,GL_LINE_STRIP,GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES之一),离缓冲区首个元素的偏移量,和需要画多少个顶点。

如果我们增加一个三角形,包括了3个顶点的geometry buffer和color buffer。当想要同时画这两个三角形时,我们只需要把dlDrawArrays中顶点的数目从3改为6就可以了。
Yay!我们确实把第一个三角形画到屏幕上了!

OpenGL Shading Language

我们还不知道在图形芯片里发生了什么。可编程图形管道(programmable graphics pipeline)的核心部分就是顶点着色器和片段着色器,它们是用OpenGL ES 着色语言(GLSL)指定的。它看起来和C语言很像,但有一些不同的数据类型,和包含2,3,后者4个该数据类型的元素的向量。对浮点数来说,这些向量就是vec2, vec3, vec4。
我们不准备详细的解释GLSL,而是看一个简单的例子。对于GLSL编程来说有一个又好又全的帮助文档,这就是快速参考卡的第三页和第四页,下载地址如下:
http://www.khronos.org/opengles/sdk/2.0/docs/reference_cards/OpenGL-ES-2_0-Reference-card.pdf
那我们的顶点着色器使用的shader.vert是什么样的呢,我们已经讨论了属性,它被用作输入:
  
  1. //the incoming vertex' position
  2. attribute vec4 position;
  3. //and its color
  4. attribute vec3 color;
这里我们看到了两个向量,position对应的向量有4个元素,color对应的有3个元素。其实color是在片段着色器而不是在顶点着色器中才有用。为了把数值从顶点着色器传递到片段着色器去,需要全局定义为varying限定符。
  
  1. //the varying statement tells the shader pipeline that this variable
  2. //has to be passed on to the next stage (so the fragment shader)
  3. varying lowp vec3 colorVarying;
colorVarying这个名字看起来很奇怪,这里只是给大家做一个示范。记住,顶点着色器只是在每一个顶点才调用一次,这样一个三角形会调用3次。而片段着色器会在每一个覆盖的像素点都调用一次。要注意的是,在顶点之间的每一个像素点,一个varying变量的数值会由线性插值计算得到。这就是为什么显示出来的三角形有这么光滑的颜色过渡。
关键字lowp代表低精度。在GLSL中我们需要给变量指明是高精度,中等精度还是低精度(highp, mediump, lowp)。对颜色值而言,精度不是那么重要,所以用低精度就可以了。如果是顶点的位置,为了防止边缘部分出现奇怪的人工痕迹,我们就需要用高精度。
  
  1. //the shader entry point is the main method
  2. void main()
  3. {
  4. colorVarying = color; //save the color for the fragment shader
  5. gl_Position = position; //copy the position
  6. }
运行着色器程序的时候,首先调用的是main()方法。我们先传递color值,然后将position属性拷贝给预先定义的输出变量gl_Position,指明将position用于光栅化。
最后在片段着色器用到的shader.frag中,将传来的color值存到指定的输出变量gl_FragColor中。因为每一个像素点都要执行一次main方法,给每一个覆盖到的片段都设置上颜色,我们就可以在屏幕上看到一个着色非常棒的图形了。
  
  1. //incoming values from the vertex shader stage.
  2. //if the vertices of a primitive have different values, they are interpolated!
  3. varying lowp vec3 colorVarying;
  4. void main()
  5. {
  6. //create a vec4 from the vec3 by padding a 1.0 for alpha
  7. //and assign that color to be this fragment's color
  8. gl_FragColor = vec4(colorVarying, 1.0);
  9. }
gl_FragColor定义为vec4,所以我们需要在后面加一个alpha值。alpha通道定义了颜色的不透明度。这里我们需要三角形是完全不透明的,将它设为1.0。
注意:GLSL不支持从int到float的隐式类型转换,alpha值不能写为”1”,必须是”1.0”。
到这里我就结束这节课了。你可以试着修改颜色,顶点的位置,和增加一些三角形。要确保你已经明白了这些基本原则。
Cheers,
Carsten

[jimmyzhouj 翻译] Nehe iOS OpenGL ES 2.0教程 --Lesson 02相关推荐

  1. OpenGL ES 2 0 (iOS)[06 1]:基础纹理

    前言:如果你没有 OpenGL ES 2 的基础知识,请先移步 <OpenGL ES 2.0 (iOS) 笔记大纲> 学习一下基础的知识. 目录 一.软件运行效果演示 (一).最终效果 ( ...

  2. OpenGL ES 2.0 for Android教程(一)

    OpenGL ES 2 前言&第一章 文章传送门 OpenGL ES 2.0 for Android教程(二) OpenGL ES 2.0 for Android教程(三) OpenGL ES ...

  3. 【AR实验室】OpenGL ES绘制相机(OpenGL ES 1.0版本)

    0x00 - 前言 之前做一些移动端的AR应用以及目前看到的一些AR应用,基本上都是这样一个套路:手机背景显示现实场景,然后在该背景上进行图形学绘制.至于图形学绘制时,相机外参的解算使用的是V-SLA ...

  4. OpenGL ES 2.0 for iPhone Tutorial

    来源:http://www.raywenderlich.com/3664/opengl-es-2-0-for-iphone-tutorial If you're new here, you may w ...

  5. IOS – OpenGL ES 调节图像单色 GPUImageMonochromeFilter

    目录 一.简介 二.效果演示 三.源码下载 四.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

  6. IOS – OpenGL ES 调节图像色度 GPUImageHueFilter

    目录 一.简介 二.效果演示 三.源码下载 二.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

  7. IOS – OpenGL ES 调节图像色彩替换 GPUImageFalseColorFilter

    目录 一.简介 二.效果演示 三.源码下载 四.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

  8. IOS – OpenGL ES 调节图像阴影 GPUImageHighlightShadowFilter

    目录 一.简介 二.效果演示 三.源码下载 四.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

  9. IOS – OpenGL ES 调节图像灰色 GPUImageGrayscaleFilter

    目录 一.简介 二.效果演示 三.源码下载 四.猜你喜欢 零基础 OpenGL (ES) 学习路线推荐 : OpenGL (ES) 学习目录 >> OpenGL ES 基础 零基础 Ope ...

最新文章

  1. [转]autoid文件上传
  2. 漫扯:从polling到Websocket(ZZ)
  3. jQuery使用ajax跨域请求获取数据
  4. 公安网络安全部门封杀的2000家淘宝钓鱼网站
  5. VS2017新建HTML项目,VS2017创建项目模板和项模板(方便实用)
  6. 九号机器人田奇峰_九号公司成功登陆科创板
  7. 银行家算法及其c++代码实现
  8. [Asp.net Mvc]通过UrlHelper扩展为js,css静态文件添加版本号
  9. DirectX修复工具
  10. 利用matlab求解二维水动力,[转载]MIKE系列软件之二维河口与海岸模拟软件MIKE 21...
  11. uniapp实现打印PDF文件
  12. 应试教育——人性的扼杀
  13. android 10.0 Camera2 去掉后置摄像头 仅支持前置摄像头功能
  14. postman——集合——执行集合——脚本的执行顺序——验证
  15. linux系统进去是guest用户t,ubuntu普通用户变为root用户后,只能guest身份进系统(linux的权限问题)...
  16. Metasploit Framework(3)Meterpreter
  17. 用pyth写一个代码输入数字到列表中,直到输入空值为止 ,依次将每个数字的最大数挑出来,生成一个新的列表...
  18. DolphinPHP 框架wangeditor编辑器图片路径改为绝对链接
  19. 计算机说话技巧,每天学点说话技巧,让你成为一个沟通达人
  20. Android 9.0 切换系统语言

热门文章

  1. 学习笔记:Ng的深度书籍学习记录
  2. 利用stress-ng压测来理解linux平均负载
  3. 想拥有属于自己的名片吗?用word其实就可以做!为自己做一个吧!
  4. 【收藏款】平面设计标准尺寸规范总结
  5. 成功的背后!(给所有IT人)(转载来自http://blog.csdn.net/ysuncn/archive/2007/10/07/1814127.aspx)
  6. Mendeley中正确设置GB/T 7714-2005中文参考文献格式
  7. 未来社区系统整体解决方案
  8. Project2007操作手册(原创)
  9. 冰点还原精灵 7.30 简体中文版 每次重启 您的电脑都是焕然一新
  10. 页式管理 多级页表