有哪位大哥是三维网 3dportal 会员用户呀,能否给个b站邀请码码,正在学三维设计与编程。谢谢啦

三维实时虚拟编辑系统的设计与实现 ...
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
三维实时虚拟编辑系统的设计与实现
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口后使用快捷导航没有帐号?
互联网+制造业信息化:中望3D成为助推先行者三维CAD中望3D助力医疗器械公司为世界500强欧特克正式推出2016版设计与创作套件欧特克创新模流技术亮相CHINAPLAS 2015,引浅析医疗器械企业使用盗版CAD的法律责任中东欧T.P.公司:三维CAD中望3D简化包装设计今日发帖:546 |昨日发帖:1264帖子数:5584734
|会员:1823190人
欢迎新会员:
发布||View: 333
Technopakas在众多国内外三维CAD/CAM软件中毅然选择中望3D三维设计软件,形成合作共赢新局面,从而摆脱设计瓶颈期。
发布||View: 189
买正版软件。
发布||View: 47
欧特克有限公司(“欧特克”或“Autodesk”)近日正式发布其专门面向制造业专业人员的2016版设计套件及应用。
发布||View: 57
全球二维和三维设计、工程及娱乐软件的领导者欧特克有限公司(“欧特克”或“Autodesk”)近日正式宣布与微软合作,将其3D打印平台嵌入Windows 10操作系统,从而使其3D建模软件与微软HoloLens全息眼镜实现协同操作。
发布||View: 553
三维CAD/CAM软件正版化并不是如企业想象就是天价采购,企业CAD正版化不再是负担。
发布||View: 547
AMETEK Creaform 现已推出 VXinspect,这是一款功能强大的直观 3D 检测软件,包括首件检查 (FAI) 和质量管理所需的所有工具
发布||View: 340
报告显示,中国快速普及BIM应用使其在全球经济中的设计和建筑领域极具竞争优势
发布||View: 921
上市企业为什么更应该正版化
大家都在看
三维任务之“论坛签到”三维网技术论坛九周年暨羊年春节红包有奖调查继续:西门子Solid Edge可以租用了Mastercam 公开课视频下载 【连载】【已更新【软件】AutoCAD 2016 官方下载地址
关注数字印象
关注我们的微信
Powered by当前访客身份:游客 [
当前位置:
****************************************************************************
申明:本系列教程原稿来自网络,翻译目的仅供学习与参看,请匆用于商业目的,如果产生商业等纠纷均与翻译人、该译稿发表人无关。转载务必保留此申明。
内容:《iPhone 3D&编程》第一章:快速入门指南
原文地址:
译文地址:
更好的排版下载地址:
****************************************************************************
第一章:快速入门指南
&&&&&&& 在本章,将从零基础开始,教你学会编写第一个应用程序。这个应用程序就是”HelloArrow”, 它绘制了一个箭头符号, 并能随着朝向的改变而旋转。你将学会如何用OpenGL ES 的API来绘制箭头,而OpenGL ES只是iPhone所支持的图形技术中的一种,于是在开始开发之前你可能会有困惑,哪一门图形技术是你所需要的?其实并没有明显的区分,某一技术是iPhone专有的,而某一技术归属于Mac OX开发的。
&&&&&&& 苹果公司把iPhone的公有API架构为四层:Cocoa Touch, Media Services, Core Services, and CoreOS,使其简单明了。Mac OS X的架构则的许多延伸,但是任然可以笼统的分为四层,如图1.1。
图1.1.&MacOS X 与 iPhone 开发架构
&&&&&&& 在最底层,Mac OS X与iPhone共享他们的核心架构与核心子系统,而Darwin泛指这些共享的组件。
&&&&&&& 尽管这两个平台的架构都如此相识,但是在处理OpenGL这一层是有一定区别的。在图1.1中,用粗体显示了几个OpenGL相关的类名。其中NSOpenGLView是Mac OS X中特有的,所以iPhone中没有,而EAGLContext&与CAEGLLayer&只有iPhone中才有。除了这些区别外, OpenGL 的API在这两个平台上也有不同,比如,Mac OS X支持 全生态的OpenGL,而iPhone则支持裁剪过的OpenGL, 叫着OpenGL ES。
iPhone支持的图形技术:
Quartz 2D Rendering Engine
支持alpha通道,层与多采样抗锯齿的失量库。这个库在MacOS X上一样可用。如果你的应用想运用这个技术,就必须关联Quartz Core.framework(framework是苹果公司对库与资源的一种打包方式)
Core Graphics
Quartz的C语言接口,同样在Mac OS X也是有效的。
iPhone上原生态的窗口框架,除此之外,UIKit还封装了Quartz的Objective_C版。在Mac OS X上与此框架相似的是Cocoa的组件AppKit。
Cocoa Touch
iPhone开发架构中的层概念,它包括了UIKit与其它几个framework.
Core Animation
Objective-C封装的可以轻松实现复杂动画的framework.
渲染2D与3D图形并支持硬件加速的底层c语言的API 集.
一些建立OpenGLES与UIKit之间桥梁的API. 其中EAGL的类(如CAEGLLayer)是在Quartz Core framework中定义,而其它的类(如EAGLContext)则是在OpenGL ES framework中定义的。
&&&&&&& 本书主要讲解OpenGL ES,它是唯一一个在上面例表中出现,但不是苹果公司特有的技术。OpenGL ES的标准是由KhronosGroup这个公司制定。不同的OpenGL ES生产商都支持同样的核心API,这样很轻松就可以写出可移植代码。生产商也可以添加一些已经正式定义好了的扩展接口到API中,而iPhone支持大量的这些的扩展,在本书的后面章节将会覆盖这些内容。
转向苹果开发阵营
&&& 很明显,你需要一台Mac机开发iPhone应用,然后上App Store. 具有PC开发背景的开发人员完全可以消除心中的恐惧,据我PC到Apple转变的经验,除了开始由于键盘不同造成不习惯外,其它完全适应。
&&&&&&& Xcode是苹果公司推荐的开发环境。如果你是Xcode新手,那你最好把它当着邮件客户端而非IDE。它的布局很直观,易用,学完快捷键后,我更感觉它的工作效率,并且用它工作很有趣。比如,当你打完一个结束分隔符如”)”,那么与之对应的开始分隔符”(”就会立刻变大,就好像浮出屏幕一样。这个效果很微妙,感觉也很实用,唯一不足就是缺少一个如动物的音效。可能下一代的Xcode,苹果会引入该功能。
Objective-C
&&&&&&& 现在是时候来说说被忽略了的Objective-C了。有些时候,你可能会听说:如果想开发iPhone就必须学习Objective-C。其实不然,只要是不与UIKit打交道的部份,你完全可以用纯C/C++的方式来写。这一点在OpenGL的开发当中尤其可以得到证明,因为它是CAPI。本书中的代码都是用C++写的,只有在iPhone系统与OpenGL ES建立关系的时候才会用到Objective-C。
&&&&&&& 苹果公司采用Objective-C得渊源于NeXT, NeXT是乔布斯成立的另一家公司,其拥有的技术在当时是首屈一指,可谓超前时代很多很多。但是它还未能平民化而被苹果公司于1997年所收购。因此,到现在为止,你可以在苹果的API中看到有NS前缀,当然iPhone开发也不例外。
&&&&&&& 有的人说Objective-C并没有C++那样复杂那么强大,其实这也不是什以坏事。在许多情况下,用正确的工具做正确事,Objective-C也有对号入坐的时候。由于它是C的超集,所以学习起来一点不难。
&&&&&&& 但是,在3D图形开发中, 我觉得某些C++的功能是不可缺少的。运算符重载让向量计算像原生计算方法一样成为可能。模板可以根据数字参数来形成向量或是矩阵。更重要的是,C++被广泛用于各个平台,而且在许多方面,游戏开发中都用得上。
OpenGL ES简明历史
&&&&&&& 在1982年,斯坦副在学一个叫JimClark的教授创办了世界上第一家计算机图形公司:Silicon Graphics ComputerSystems(硅谷图形计算机系统), 也就是后面的SGI家公司。由于SGI的工程师需要一套标准的3D移动与操作的方法,所以就开始设计出一套叫IrisGL的API。在90年代, SGI整理并发布于众,做为行业标准,于是OpenGL就诞生了。
&&&&&&& 在近年,图形学技术的飞速发展远远超出了摩尔定律。.[]OpenGL在保证向后兼容的情况下更新了无数代。于是许多的开发者都认为API过于臃肿,特别是随着手机移动设备革命的带动,精简OpenGL的需求已史无前例的明显。于是在2003的秋季SIGGRAPH会议中,Khronos组织声明了 OpenGL for Emebedded Systems(OpenGL ES)。
&&&&&&& OpenGL ES一出现,便受到许多产商的推宠,在许多设备上得到技持,如iPhone,Android, Symbian, Playstation 3.
&&&&&&& 苹果的所有设备上都至少支持OpenGLES API 1.1版本,交在其核心标准上加入了强劲的扩展,包括顶点缓冲对象,多纹理支持, 这两个扩展都会在后面章节中所介绍。
&&&&&&& 在2007年3月,Khronos组织发布了OpenGL ES 2.0的标准,它的出现,颠覆性的打破了向后兼容的规则,因为它剔除了许多固定通道的渲染功能,取而代之的是shadinglanguage。新的标准使操控图形的API更加简洁,同时把具特色的控制交到了开发者手里,因此许多开发者(包括我)都觉得ES2.0比ES1.1更优雅。有了这两套API,那么对同一个问题就有两种不同的解决方案。用ES2.0,就算是写一个简单的Hello World也需要做更多的额外工作。在很长的一段时间内OpenGLES 1.1的追求者可能还会继续使用,由于它的运行时低负荷。
选择适当的OpenGL ES版本
&&&&&&& 苹果的新款手持设备,如iPhone3GS,同时支持ES1.1与ES2.0,因为这些设备上有可编辑化图形管道来运行图形命令,而传统的则是运行定点数学运算。老的设备如第一代iPod touch,iPhone与iPhone 3G只有一个固定的图形渲染通道,因此只支持ES1.1。
&&&&&&& 在你开始写第一行代码之前,请确定你的图形需求。当然用最新最完善的API固然是好事,但是你得记住,支持ES 1.1的设备占大多数,因此它可能为你的应用打开更多的市场,同时开发ES 1.1应用的工作量会更少,前提是你的图形需求不高。
&&&&&&& 当然,许多高级特效可能只能用ES2.0实现,正如我前面所说, 我相信它在开发当中更优雅更有效。
做个小总结,你可以从下面四种方法中选择来开发你的应用:
只用OpenGL ES 1.1。
只用OpenGL ES 2.0。
在运行时判断是否支持ES 2.0,如果支持则用,如果不支持就用ES 1.1。
为ES 1.1与ES 2.0分别发布一个独立版本。(这方法可能有点冗余)
&&&&&&&& 在本书中我将采用第三种方法,本节的HelloArrow示例你将会看到。一个明智的选择!
&&&&&&& 前提是你已有了一架Mac电脑,接着第一步就是去苹网iPhone开发官网并下载SDK。只有拥有了SDK(免费的),你才有“利器”来开发复杂的应用程序,并在iPhone模拟器上测试它。
&&&&&&& iPhone模拟器不能模拟全部功能,比如重力感应,也不能全部反映iPhone设备的OpenGL ES 真实能力。比如, OpenGL的平滑线条功能能使渲染的时候达到多采样抗锯齿的效果,而真机上则不行。另一方面,真机有一些扩展特性,而模拟器上则不一定有。(随便说一下,我们将在本书后面章节介绍多采样的缺点。)
&&&&&&& 说了这么多,现在告诉你,你并不需要真机,因为我确保所有的示例都能在模拟器上运行,退一步说,即使模拟器不支持一些功能,这种情况当然很少,我都会巧妙的方法解决。
&&&&&&& 如果你有一部iPhone,并愿意支付费用加入苹果iPhone开发大家庭,这样就能部署你所开发的应用到真机上。(在写本书的时候这费用是$100,泽注:明明是$99/年)。其实加入开发会员并不是很痛苦的过程,我申请的时候苹果很快就接受了我的申请。如果你申请的时候花费了很长的时间,那么我建意你在等待的这段时间里,先用模拟器开发着。其实在我开发过程当中,基本每天都用模拟器开发,因为它调试运行的速度远比在真机上要快。
&&&&&&& 本书是以教程的方式撰写。可能由于你所用的开发工具版本不同,那么本书所写步骤与你的操作可能有细微差别。特别是涉及到Xcode的用户界面的细微偏差,比如,一个菜单变了名字,名是移动了位置。但是,书中的示便代码是经过设计,可以向前兼容的。
安装iPhone SDK
&&&&&&& iPhone的SDK可以在这儿下载:
&&&&&&& 它是一个以.dmg为后缀的文件, 这是苹果标准的磁盘文件格式。当你下载安以后,它会在Finder的窗口中自动打开,如果没有,那么你去磁盘上找到该文件,并打开它。这个磁盘镜像文件常常包括三个文件:一个“关于”的pdf文件,一个子目录,一个安装包实体,安装包实体的图标是一个纸盒子。双击打开安装实体,并进行多个下一步操作。在先择安装组件的时候,默认就行了。当你不想要这些组件的时候,在磁盘上找到它们并删除就行了。
小知识: &&&&&&& 作为一个苹果开发者,Xcode可能是你最常用的工具,我建意你将其拖到屏幕下方的dock栏上。你可以在/Developer/Application/Xcode目录下找到Xcode。
小知识: &&&&&&& 如果你用惯了PC,那么开始用苹果的时候,会觉得它的窗口系统很不习惯。我建意你用苹果内置的Expose或Space桌面管理器。Expose就像是无限延伸你的窗口,Space感觉就像是多个虚拟桌面。我用过许多虚拟桌面管理器,觉得Space是最好的。 &
用xCode编译OpenGL的模板应用程序
&&&&&&& 当第一次打开Xcode的时候,它会弹出一个欢迎对话框。选择上面的Create a new Xcode project按钮。(如果你的对话框被关闭了没有显示出来,那么可以选择菜单里的File-&New Project来创建新工程。)现在你会有一个如图1.2的对话框出现,它包括Xcode提供的工程模版。我们要用到的是OpenGL ES Application这个模版,注意到没,它是在iPhoneOS这一栏下面。这个模版并没有什么特别的,只不过它支持OpenGL,作为一个新手,选择它是明智的。
&&&&&&& 选择模版后,会有一个对话框要求你输入工程名字。完成之后,你就可以看到Xcode的工作窗口了。在Build菜单里有一个Build and Run,选择它开始编译并运行,你也可以按快捷键?-Return。编译完成后,模拟器将会被启动,其中有一个方形的图形在模拟器上上下移动,如图1.3所示。如果你想退出这个应用程序,直接按?-Q即可。
图1.3 OpenGL模版应用
部署到真机
&&&&&&& 这一步并不是必须的。如果你想要布署到真机上,那么你必须注册苹果的iPhone Developer Program。这样才允许你真机调试。获取这个准证是一个繁杂的过程,但是幸好一台设备只需做一次。现在苹果推出了一个简单的方法来处理这些流程。你可以登录iPhoneDev Center (/iphone/), 并进入iPhone Developer Program Portal里进行操作。
&&&&&&& 当你真机得到认证后,你可以打开Xcode的Organizer&窗口(快捷键是Control-?-O),并展开左边Provisioning Profiles&这一栏,确保你的设备名列其中。
&&&&&&& 现在你可以回到Xcode的主窗口,在左上角Overview这个下拉列表中选择你的真机,然后编译运行(?-Return),于是在你的iPhone上就会出一移动的方形图形。
固定通道渲染Hello Arrow
&&&&&&& 在前面的小节中,熟悉了开发环境,苹果提供的OpenGL工程模板应用。但是如果要理解其工程原理, 还得从其础学习。本节将用OpenGL ES 1.1的方法,从头到尾开发一个简单应用。OpenGL ES 1.x的另一个叫法是fixed-function&,与之对应的是建立在shader基础上的OpenGL ES 2.0 。我们将在本章的后面内容介绍如何修改为支持shader的应用。
&&&&&&& 为了与本书的主题相符,我对经典的HelloWorld做了一点点变化,现在就开始吧。在后面的内容中你会学到,在OpenGL中渲染的形状可以用三角形构造出来。 比如,我们可以用两个重叠的三角形绘制一个简单的图形,如图1.4所示。如有雷同,纯属巧合。
图1.4 由两个三角形组成的箭头形状
&&&&&&& 为了增加趣味性,本示例的箭头始终指向上,即使用户改变方向。
架构你的3D应用
&&&&&&& 如果你喜欢用Objective-C,那么你可以通过任何手段在任何处使用它。由于本书考虑到跨平台的代码重用, 于是只有在万不得已的情况下才用Objective-C。图1.5展示了两种架构来重用C/C++所写的代码,因为iPhone上的glue是用Objective-C写的。右边的方法是从rendering engine中分离出一个application engine(虽然同属逻辑部份), 而本中稍微复杂的示例就是采用这种方法 。
图1.5 3D应用架构
&&&&&&& 图1.6中所介绍的方法是,设计一套通用的渲染接口,并确保各个平能可用。本书代码中中的IRenderingEngine就是这个计设,当然你可任意命名。
图1.6 跨平台OpenGLES 应用
&&&&&&& 有了IRenderingEngine这个接口,你就可以创建多个渲染引擎,如图1.7所示。这样就可以达到“支持ES2.0的时候用2.0,不支持的时候用ES 1.x”,这方法就是前面所说的方法三。 Hello Arrow就用这个方法。
图1.7 同时支持ES2.0与ES 1.1的应用
&&&&&&& 随着我们关于HelloArrow代码的讲解,你将学到关于图1.7中的点点滴滴,你将会创建三个类:
RenderingEngine1 与RenderingEngine2 (可移植的C++)
大部份的工作都在这个类中,其中对OpenGL ES的调用也在其中。RenderingEngine1&用 ES 1.1 while&RenderingEngine2&用 ES 2.0。
HelloArrowAppDelegate (Objective-C)
这是一个继承自NSObject并遵循UIApplicationDelegate协议的Objective-C类。(“遵循协议“与java或C#中的”接口实现“类似。)这个类中没有用OpenGL 或 EAGL,它只是简单的初始化GLView并在应用退出的时候释放内存。
GLView (Objective-C)
继承自标准的UIView类,并用EAGL去初始化OpenGL所需的渲染surface。
&&&&&&& 启动Xcode并用最简单的一个模板:Windows-BasedApplication创建工程,命名这个工程为HelloArrow。Xcode捆绑了一个叫Interface Builder的工具,它可以用来设计与UIKit(Mac OS X下是AppKit)相关的用户界面。本书中不打算计解它,因为3D应用中不常用。为了执行效率,苹果不建意UIkit与OpenGL混用。
&&&&&&&&注意 对于一些简单的3D应用,也不需要遵循这条规则,你可以向你的OpenGL view添加一些UIKit控件也无防碍。我们将在后面一章节“OpenGL ES与UIKit混用”中介绍。
选择步骤:创建一个干净工程 下面的步骤将移除工程Interface Builder的支持。你可以选择不这样做,但我习惯性的用一个干净的工程(即没有Interface Builder的工程)。 1. Interface Builder生成的文件是xib文件,它是一个xml类型的文件,负责定义UI成员。由于你创建的是一个OpenGL应用,根本不需要这个文件,所以可以删除之。在左边的Groups & Files组中,找到Resources这个文件夹(有些情况下是Resources-iPhone),删除所有以.xib为后缀的文件,当提示的时候,选择移到回收站。 2. xib文件一般会被编译成nib的二进制文件,它在运行时被用来组建UI。为了让OS不加载nib文件,你需要在应用属性中将其删除。在工程Resources目录下找到HelloArrow-Info.plist文件,双击打开它,删除带有Main nib file这样的这一行(在靠下面的位置处)。你可以先选择这一行,然后按Delete键。 3. 由模版生成的工程,在nib中会自动关联应用的代理,由于我们不需要nib文件了,所以需要手传递字符串来动设置应用代理。在Other Sources里,打开main.m文件,你会发现UIApplicationMain这个方法的最后一个参数为nil,我们将其修改为应用的代理类(如,@&HelloArrowAppDelegate&)。@这个前缀说明这是一个Objective-C的字符串,并非C-style的字符串。 4. 由模板生成的工程有一个属性表示应用代理,Interface Builder与之关联。现在不需要了。打开HelloArrowAppDelegate.h(在Classes目录中)删掉@property这一行来删除属性声明,打开HelloArrowDelegate.m文件删掉@synthesize这一行来删除属性定义。
连接OpenGL与Quartz库
&&&&&&& 在苹果开发阵营里的framework就相当库,从技术角度说它是资源的捆绑。Bundle就是一个特殊的目录,表现为一个文件的属性,这些与MacOS X上 的都差不多。比如,应用程序往往就是bundles,找到Applications目录下的一个应用程序,在它的图标上右键弹出菜单项,有一项是show package contents,点击它你会看到其表壳下的内容。
&&&&&&& 你需要加入一些需要的framework到工程当中。选择Frameworks这个group,然后点击Action图标,或鼠标右击或按住control+鼠标左击来弹出菜单。然后选择Add-&Existing Frameworks。选择OpenGLES.Framework并点击Add按钮。同时会弹出一个对话框 ,一切按默认选择,接受就行。然后以同样的方式加入QuartzCore.framework。
注意 &&&&&&& 可能你会问,我们不是写OpenGL ES的应用程序吗,为何还要用Quartz呀?这是因为Quartz拥有展现到屏幕的层对象,OpenGL也需要这个层对象, 它是CAEGLLayer的一个实例, CAEGLLayer派生于CALayer, 而这些类都是定义在QuartzCore framework中的。
子类化UIView
&&&&&&& UIView控制屏幕中的一个矩形区域,处理用户事件,充当子view的容器。大部份的标准控件,如按钮,划块,输入框都是UIView的子孙类。在本书的示例中,我们应该避免用这些控件,由于UI部份需求量简单,我们完全可以用OpenGL自绘简单的控钮与各种小窗口。
&&&&&&& 由于在iPhone中,所有图形绘制都必须在一个view中进行,所以我们的HelloArrow必须定义一个UIView的子类。选对Classes这个目标,然后在Xcode的工具栏点击Action,在弹出的菜单中选择Add-&New file。在CocoaTouch Class这个分类下,选择Objective-C类模版, 并在Subclassof menu中选择UIView。随后弹出的对话框中输 入名字GLView.mm,并选择同时生成相应的头文件。.mm的后缀表示这个文件同时支持c++与Objective-C,在GLView.h中你可以看到如下内容:
#import &UIKit/UIKit.h& & @interface GLView : UIView { } @end
&&&&&&& 对于C/C++高手而言,这种语法有点感冒,等一下看到方法实现的语法后更有此感受。但是不用担心, 如果习惯了将会觉得非常轻松。
&&& #import与#include的功能差不多,只不过它不可能产生在同一个文件包括两次头文件的错误,与 C/C++中加入了#pragmaonce的功能类似。
&&&&&&& Objective-C的关键字都是以”@”为前缀的。@interface表示类的声明开始,@end表示类的声明结束。一个文件里可以包括多个类的声明,于是可以出现多个@interface程序块。
&&&&&&& 现在你可能都已猜到,上面的代码片段其实就是定义了一个类,类名是GLView,继承自UIView。有一点需要明确的是,数据的声明应放在大括号内, 而方法的声明则应放在结束在括号与@end之间,如下代码:
#import &UIKit/UIKit.h& & @interface GLView : UIView { &&& // Protected fields go here... }&&& // Public methods go here.. @end
&&&&&&& 在数据区声明的数据,默认是保护型的,当然你可以用关键字@provate改为private型。继续上面的代码,我们来完善它,如示例1.1所示, 我们需要引入几个与OpenGL相关的头文件。
示例1.1 GLView类的定义
#import &UIKit/UIKit.h& #import &OpenGLES/EAGL.h& #import &QuartzCore/QuartzCore.h& #import &OpenGLES/ES1/gl.h& #import &OpenGLES/ES1/glext.h& @interface GLView : UIView { &&& EAGLContext* m_ } - (void) drawV @end &
&&&&&&& 上面加入的m_context是用来管理我们的OpenGL 上下文的,它是一个EAGL类对象。而EAGL是苹果特有的API,是它让iPhoner操作系统与OpenGL关联起来的。
注意 & &&&&&&& 每一次你调用OpenGL的API, 不只是修改状态,还作用了上下文。就算在一个支持多线程的系统上, 也只能同时只能有一个当前上下文。对于iPhone,由于移动设备的资源限制,加之你的应用几乎不可能使用多个上下文,所以我不建意用多个上下文。
&&&&&&& 如果你是C/C++背景的程序员,你可能会觉得drawView这个方法声明得有点奇怪。如果你对UML语法熟悉的话,你将不会有这种奇怪感觉了, 但是在这儿与UML中的“-”表示私有方法”+”表示公有方法还是有些差别,在Objective-C中,”-”表示实例方法,”+”表示类方法。(在Objective-C中的类方法与C++中的静态方法有些类拟,不同的是,在Objective-C中,类本身就是真真的对象。)
&&&&&&& 再来看看Xcode生成的GLView.mm文件。在@implementation与@end中间的就是GLView类的定义。Xcode自动生成了三个方法:initWithFrame, drawRect(有可能被注释了), dealloc。注意这三个方法都没有在头文件声明,而是自动生成的。照此看来,我们发现Objective-C中的方法与C中的方法用法都差不多,用的时候都需要提前声明。我通常把所有主方法都声明在头文件中,这样与C++中类声明的方法保持一致。
让我们仔细来看一看第一个方法:
- (id) initWithFrame: (CGRect) frame{ &&& &&& if (self = [super initWithFrame:frame]) { &&&&&&& // Initialization code &&& } &&& }
&&&&&&& 这是一个Objective-C的初始化方法,有点类似C++中的构造函数。返回值类型由一个小括号包住,有点像C中的强制类型转换。if那一句同时完成了几个处理:首先调用基类的initWithFrame,并将返回结果赋值给self, 最后再判断是否成功。
In Objective-C parlance, you don't calyou send messages to objects. The square bracket syntax denotes a message.Rather than a comma-separated list of values, arguments are denoted with awhitespace-separated list of name-value pairs. The idea is that messages canvaguely resemble English sentences. For example, consider this statement, whichadds an element to aNSMutableDictionary:
在Objective-C的用法当中,还值得一提的是,你并不是调用某个实例的方法,而是向这个实例发送消息。中括号表示一个消息。参数列表不再是以逗号分开的方式,而是用以空格分隔开的名字-值的方式表示。这种方式的好处是可以产生可读性的英语句子。比如,加入一个元素到NSMutableDictionary:
[myDictionary setValue: 30 forKey: @&age&];
&&&&&&& 如果你试着去读这句代码,将会生成一句英语句子,当然需要适当的排序。
&&&&&&& 到现在为止,Objective-C相关知识介绍得差不多了。回到HelloArrow这个应用上来。在GLView.mm中加入layerClass这个方法,代码片段如下:
+ (Class) layerClass{ &&& return [CAEAGLLayer class]; }
&&&&&&& 这儿重写了layerClass方法,并返回支持OpenGL类型的layer。这个类方法有点类似其它语方中的typeof操作。它返回的是的对象表示类型本身,而非一个类型的实例。
注意 &&&&&&& &+&前缀表示重写的这个方法是类方法,并不是成员方法。这种重写的特性是Objective-C具有的,其它语言很少这样。
&&&&&&& 现在,回到initWithFrame这个方法中,我们在if的执行体中初始化EAGL, 代码如示例1.2。
示例1.2 EAGL实始化
- (id) initWithFrame: (CGRect) frame { &&& if (self = [super initWithFrame:frame]) { &&&&&&& CAEAGLLayer* eaglLayer = (CAEAGLLayer*) super. //[1] &&&&&&& eaglLayer.opaque = YES; //[2] &&&&&&& &&&&&&& m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; //[3] &&&&&&& &&&&&&& if (!m_context || ![EAGLContext setCurrentContext:m_context]) { //[4] &&&&&&&&&&& [self release]; &&&&&&&&&&&//[5] &&&&&&& } &&&&&&& &&&&&&& // OpenGL Initialization &&& } &&& }
代码分析:
[1] &&&获取基类(UIView)的layer属性,并将它从CALayer强制转换为&&&&&& CAEAGLLayer。这样做是安全的,因为我们重写了layerClass方法。
[2] &&&设置获取到layer的opaque属性为YES,表示我们不再用Quartz来处理半透明度。在开发OpenGL相关的应用时,这是苹果建意的方法, 你不必担心透明度问题,因为OpenGL可以处理alpha融合。
[3] &&&创建EAGLContext对象,启关联OpenGL ES的版本号,在此我们用的是ES1.1。
[4] &&&设置当前EAGLContext, 这样一来,在当前线程上的OpenGL调用都与此上下文相关。
[5] &&&如果上下文创建失败或设置当前上下文失财, 就结束并返回nil。
在示例1.2中,实例化EAGLContext的方法alloc-init的设计模式在Objective-C中是非常常见的。在Objective-C中生成一个实例往往需要两步:分配空间与实始化。但是,许多类的类方法使得生成实例更为简单。比如,用utf-8编码的方式转换NSString, 传统的方法是: NSString* destString = [[NSString alloc] initWithUTF8String:srcString]; 但是我更喜欢这样写: NSString* destString = [NSString stringWithUTF8String:srcString]; 不只是因为它更简洁,还因为它加放了自动释放机制,因此不需要再发送release消息给对象了。
&&&&&&& 接着完善OpenGL的实始化。用示例1.3的代码代替上面的注释//Open GL Initialization
示例1.3 OpenGL 实始化
&&& GLuint framebuffer, &&& glGenFramebuffersOES(1, &framebuffer); &&& glGenRenderbuffersOES(1, &renderbuffer); &&& &&& glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer); &&& glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderbuffer); &&& &&& [m_context& renderbufferStorage:GL_RENDERBUFFER_OES &&& &fromDrawable: eaglLayer]; &&& &&& glFramebufferRenderbufferOES( &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &GL_RENDERBUFFER_OES, renderbuffer); &&& &&& glViewport(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame)); &&& &&& [self drawView];
&&&&&&& 示例1.3中,开始处有两个OpenGL类型变量,一个是renderbuffer,另一个是framebuffer。简要说明一下,renderbuffer是一个2维结构,里面存一些数据(在此存的是颜色数据),framebuffer是捆绑了多个renderbuffer的结构。在后面的章节中,你将学到多更关于framebufferobjects(FBOs)的知识。
注意 &&&&& 在OpenGL ES 1.1标准中,FBOs并不在其中, 但它做为高级功能而在OpenGL扩展中出现,当然所有iPhone都支持这个扩展。在OpenGL ES 2.0 标准中,FBOs是原生态支持的。在这么简单的HelloArrow应用中,都需要如些高级的功能,感觉有点奇怪。其实所有的iPhone OpenGL应用,它们绘制图形并显示到屏幕上的过程都需要FBOs的参与。
&&&&&&& 细心的读者可能发现,readerbuffer与framebuffer都是GLuint类型,这种类型是OpenGL用来管理各种对象的。当然你也可以用unsigned int代替GLuint,因为他们本来是一样的,但是我建意你不要这么做。如果用到OpenGL相关的API的时候,还是建意用GL前缀的变量进行参数的传递。因为这样会使你的代码更具有可读性,知道哪些地方是与OpenGL有关的。
&&&&&&& 从示例1.3中我们可以看到,创建好framebuffer与renderbuffer后,紧接着就将它们与渲染通道进行绑定,当然我们也可以在后续操作中对其进行修改或取消绑定。绑定完成后,向EAGLContext的实例发送一个renderbufferStorage消息就可以创建一个storage。
注意 &&&&&&& 关于离屏页,你得用glRenderbufferStorage这个OpenGL的API来创建之,这样一来,你的renderbuffer就与一个EAGL layer关联起来。本书后面内容中会更多的涉及离屏页。
&&&&&&& 接着一行代码里,glFramebufferRenderbufferOES将renderbuffer依附到framebuffer。
&&&&&&& 接着来看glViewport这个API,你可能不理解它的作用,其实你现在可以把它想为设定坐标系,在第二章的数学与抽象中你会更加清楚它的来胧去脉。
&&&&&&& 最后一行调用了drawView这个方法,那么我们就来实现这个方法,代码如下:
- (void) drawView { &&& glClearColor(0.5f, 0.5f, 0.5f, 1); &&& glClear(GL_COLOR_BUFFER_BIT); &&& &&& [m_context presentRenderbuffer:GL_RENDERBUFFER_OES]; }
&&&&&&& OpenGL的“clear”机制可以帮我们将buffer填充为单一纯色。首先选定填充的颜色为灰色,有四个值(红,黄,蓝,alpha)。接着就用选定的颜色填充buffer。最后一行,让EAGLContext对象将renderbuffer的内容显示到屏幕上。大多数的OpenGL程序都是先渲染到一个缓冲区内,然后以原子操作的方式显示到屏幕,就像现在我们这样。
&&&&&&& Xcode提供的drawRect方法已对你没用了,因为它是基于UIKit的应用程序里刷新机制所调用的方法,在3D应用中,你需要更为精确的方法来控制图形绘制。
&&&&&&& 到此,你差不多有了一个可以看效果的OpenGLES程序,但是收尾工作还没做完。在GLView对象销毁的时候,你必须释放一些空间。你得修改dealloc方法为如下代码:
- (void) dealloc { &&& if ([EAGLContext currentContext] == m_context) &&&&&&& [EAGLContext setCurrentContext:nil]; &&& &&& [m_context release];& &&& [super dealloc]; }
&&&&&&& 现在你可以编译运行了,是不是还是不能看到灰色背景呀?这是正常的,先别忙,因为我们还有些事没做,还得修改应用代理类。
修改应用代理
The application delegate template (HelloArrowAppDelegate.h) that Xcode provided contains nothing morethan an instance of&UIWindow. Let's add a pointer to an instance ofthe&GLView&class along with a couple methoddeclarations (new/changed lines are shown in bold):
由Xcode模版生成的的应用代理类(HelloArrowAppDelegate.h)里只有一个UIWindow变量。现在我们加入GLView类的一个变量(新加入/修改了的代码以粗体显示):
#import &UIKit/UIKit.h& #import &GLView.h& & @interface HelloArrowAppDelegate : NSObject &UIApplicationDelegate& { &&& UIWindow*&m_ &&&&GLView* m_ } & @property (nonatomic, retain) IBOutlet UIWindow *m_ & @end
&&&&&&& 如果你是按着前面的小节中创建干净工程的方法来创建工程,你就不会看到@property这一行代码。InterfaceBuilder就是用Objective-C的属性机制来关联对象的,但是在本书中我们都不会用它。再次简要说明一下,@property关键字声明属性,@synthesize关键字定义附属方法。
&&&&&&& 注意到没,Xcode模版生成的是window成员变量,我反它重命名为m_window。这种命名方式将贯穿本书。
注意 &&& 我建意用Xcode的Refactor功能重命名变量,因为它可以帮你与之对应的属性(如果它存在)。选中window这个变量,鼠标右键,并选择Refactor。如果你没按前面的方法来创建一个干净的工程,那你必须用这种方法来重命名,因为xib文件已与window建立了关联。
&&&&&&& 现在来分析 HelloArrowAppDelegate.m这个文件。还记得吗,我们创建工程的时候是选择的”Window-Based Application”这个模版,模版就帮我们生成了代理类的基本框架,它实现了applicationDidFinishLaunching与dealloc这两个方法。
注意 &&&&&&& 由于你需要这个方件同时包含C++与Objective-C代码,所以你得修为后缀名为.mm。在相应的文件上鼠标右键,在弹出菜单中选择Rename。
&&&&&&& 最终代码如示例1.4
#import &HelloArrowAppDelegate.h& #import &UIKit/UIKit.h& #import &GLView.h& & @implementation HelloArrowAppDelegate & - (BOOL) application: (UIApplication*) application didFinishLaunchingWithOptions: (NSDictionary*) launchOptions&&& { &&& CGRect screenBounds = [[UIScreen mainScreen] bounds]; &&& &&& m_window = [[UIWindow alloc] initWithFrame: screenBounds]; &&& m_view = [[GLView alloc] initWithFrame: screenBounds]; &&& &&& [m_window addSubview: m_view]; &&& [m_window makeKeyAndVisible]; &&& return YES; } & - (void) dealloc { &&& [m_view release]; &&& [m_window release]; &&& [super dealloc]; } & @end
&&&&&&& 示例1.4 中构建了window与view两个对象,都是全屏的。
&&&&&&& 如果你不是控前面的方法创建干净的工程,你得做如下小修改:
&&&&&&& 在@implementation:后面加入一行代码
@synthesize m_
&&&&&&& 前面说过,@synthesize这个关键字是定义属性的附属方法,Interface Builder就用这些方法去处理的。
&&&&&&& 编译并运行,这回是不是可以看到灰色背景了呀?& 高兴吧!
设置应用图标与启动画面
&&&&&&& 应用程序的图标是可以自定义的,创建一个57*57大小且格式为PNG的图片,将其放在Xcode工程的Resources分组下。如果你的图片不是在工程目录下,那么Xcode会弹出一个对话框问你是否copy到工程目录,我们选择”Copy itemsinto destinaiton group’s folder(if needed)”。然后打开HelloArrow-Info.plist(也在Resources分组下),找到Icon file这一行, 在后面输入你的PNG文件名。
&&&&&&& iPhone会给你的图标自动加上圆角与光泽效果。如果你不想要这个效果,那么打开HelloArrow-Inof.plist文件,随便单击一个右边的+按钮,在左边列选择”Icon alreadyincludes gloss and bevel effects”, 右边输入YES。如果你的图标没有这种光泽效果,请不要这样做,因为苹果希望所有的图标在SpringBoard(iPhone内置应用)中都保持一致。
&&&&&&& 为了在Spotlight搜索中系统设置界面中看到应用图标,苹果建意同时提供一张29*29的图片。方法在上面介绍过了,只不过这张图片名字必须为Icon-Small.png,也不面要再修改plist文件。
&&&&&&& 对于应用的启动画面,方法与上面的小图标的方法差不多,只是名字必须为Default.png,也不需要修改plist文件。如果你想很好的全屏效果,那么这张图片的大小得是320*480, 其它大小都会使图片拉伸,效果很丑。其实苹果的文档说了,这张图片根本不算什么启动画面,这样做的目的只是为了更好的用户体验。并不需要你有多么有创造性的logo,苹果最初只要让你模拟程序的启动画面而已。当然, 现在有很多应用程序都忽略这条了。
处理地状态栏
&&&&&&& 现在你的应用将屏幕填充为了灰色,但是状态栏仍然显示在屏幕的顶部。一种解决办法是在didFinishLaunchingWithOptions:中加如下面代码:
[application setStatusBarHidden: YES animated: NO];
&&&&&&& 这种方法有一个问题,就是在启动画面结束之前状态栏仍然存在。下面让我们来让它在一开始的时候就去掉状态栏的显示。打开HelloArrowInfo.plist文件,新建一行,选择”Statusbar is initially hidden”, 然后勾上后面的选择框。
&&&&&&& 当然,大多数情况下,为了让用户知道当前电量与网络连接情况,状态栏是显示的。如果你的应用背景是黑色的,你可以在plist文件中新增一行并选择”Status barstyle”,在后面一列选择black style。如果不是黑色背景,那么semi-transparentstyle更加适合你。
定义并使用RenderingEngine 接口
&&&&&&& 到此,我们已有准备好了HelloArrow所需的大部份工作,如果按照图1.7中的架构,我们现在还缺少绘制引擎。那么我们加入一个C++的接口文件到工程。选中Classes分组,鼠标右键,弹出菜单中选择Add-&New,& 然后选择Cand C++, 再在右边选择Header File。我们命名这个新加文件为IRenderingEngine.hpp。注意文件后缀,.hpp表示这个文件只支持C++语法,不支持Objective-C语法。成功加入文件后,在其中写入示例1.5的代码。
示例1.5 IRenderingEngine.hpp
// Physical orientation of a handheld device, equivalent to UIDeviceOrientation enum DeviceOrientation { &&& DeviceOrientationUnknown, &&& DeviceOrientationPortrait, &&& DeviceOrientationPortraitUpsideDown, &&& DeviceOrientationLandscapeLeft, &&& DeviceOrientationLandscapeRight, &&& DeviceOrientationFaceUp, &&& DeviceOrientationFaceDown, }; & // Creates an instance of the renderer and sets up various OpenGL state. struct IRenderingEngine* CreateRenderer1(); & // Interface to the OpenGL ES consumed by GLView. struct IRenderingEngine { &&& virtual void Initialize(int width, int height) = 0;&&& &&& virtual void Render() const = 0; &&& virtual void UpdateAnimation(float timeStep) = 0; &&& virtual void OnRotate(DeviceOrientation newOrientation) = 0; &&& virtual ~IRenderingEngine() {} };
示例1.5中定义的接口,运用了一些C++中面象的方法,本书后面内容也会覆盖这些知识: 所有的接口方法都是纯虚函数。 由于接口的方法往往都是公有的,所以在这儿接口是用struct的类型定义的。(回忆一下,C++中,struct的成员访问默认是公开的,而class类型的成员访问默认是保护的。) 所有的接口都以I字母开始。 接口中只有方法,没有数据域。 接口类的创建都是通过工厂创建的设计模式创建。在这儿是通过CreateRender1创建的。 必须有一个虚析构函数以保证正确释放内存。
&&&&&&& 关于设备方向的枚举,可能你会觉得多余了,因为在iPhoneSDK的头文件(叫UIDevice.h)中存在一份类似的。其实不多余,因为我们这儿写的IRenderingEngine接口考虑了跨平台性。
&&&&&&& 因为我们的view类中会用到rendering engine接口,所以在GLView.h中得引入IRenderingEngine并声明一个该类型的指针变量,还要加入一些关于旋转与动画的变量与方法。完整的代码参看示例1.6。新增的变量与方法都用粗体显示。说明一下,有两个关于OpenGLE ES 1.1的#imports被移到RenderingEngine.hpp中去了,而EAGL相关的头文件并不是OpenGL 标准的东西,但是创建OpenGL ES上下文的时候得用到它们。
示例1.6 GLView.h
#import &IRenderingEngine.hpp& #import &OpenGLES/EAGL.h& #import &QuartzCore/QuartzCore.h& & @interface GLView : UIView { @private &&& EAGLContext* m_ &&&&IRenderingEngine* m_renderingE &&& float m_ } & - (void) drawView: (CADisplayLink*) displayLink; - (void) didRotate: (NSNotification*) & @end
&&&&&&& 示例1.7是GLView类的实现。调用rendering engine的部份已粗休高亮了。注意GLView中现在没有任何OpenGL的方法了,我们会把所有OpenGL的调用都放在rendering engine中进行。
示例1.7 GLView.mm
#import &OpenGLES/EAGLDrawable.h& #import &GLView.h& #import &mach/mach_time.h& #import &OpenGLES/ES2/gl.h& // &-- for GL_RENDERBUFFER only & @implementation GLView & + (Class) layerClass { &&& return [CAEAGLLayer class]; } & - (id) initWithFrame: (CGRect) frame { &&& if (self = [super initWithFrame:frame]) { &&&&&&& CAEAGLLayer* eaglLayer = (CAEAGLLayer*) super. &&&&&&& eaglLayer.opaque = YES; &&&&&&& &&&&&&& m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; &&&&&&& &&&&&&& if (!m_context || ![EAGLContext setCurrentContext:m_context]) { &&&&&&&&&&& [self release]; &&&&&&&&&&& &&&&&&& } &&&&&&& &&&&&&&&m_renderingEngine = CreateRenderer1();&&&&&&& &&&&&&& &&&&&&& [m_context &&&&&&& &renderbufferStorage:GL_RENDERBUFFER &&&&&&& &fromDrawable: eaglLayer]; &&&&&&& &&&&&&&&m_renderingEngine-&Initialize(CGRectGetWidth(frame), CGRectGetHeight(frame)); &&&&&&& &&&&&&& [self drawView: nil]; &&&&&&& m_timestamp = CACurrentMediaTime(); &&&&&&& &&&&&&& CADisplayLink* displayL &&&&&&& displayLink = [CADisplayLink displayLinkWithTarget:self &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& & selector:@selector(drawView:)]; &&&&&&& &&&&&&& [displayLink addToRunLoop:[NSRunLoop currentRunLoop] &&&&&&&&&&&&&&&&&&&&&&& & forMode:NSDefaultRunLoopMode]; &&&&&&& &&&&&&& [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications]; &&&&&&& &&&&&&& [[NSNotificationCenter defaultCenter] &&&&&&& &addObserver:self &&&&&&& &selector:@selector(didRotate:) &&&&&&& &name:UIDeviceOrientationDidChangeNotification &&&&&&& &object:nil]; &&& } &&& } & - (void) didRotate: (NSNotification*) notification { &&& UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation]; &&&&m_renderingEngine-&OnRotate((DeviceOrientation) orientation); &&& [self drawView: nil]; } & - (void) drawView: (CADisplayLink*) displayLink { &&& if (displayLink != nil) { &&&&&&& float elapsedSeconds = displayLink.timestamp - m_ &&&&&&& m_timestamp = displayLink. &&&&&&& m_renderingEngine-&UpdateAnimation(elapsedSeconds); &&& } &&& &&&&m_renderingEngine-&Render(); &&& [m_context presentRenderbuffer:GL_RENDERBUFFER]; } & @end
这个工程当中Objective-C相关的部份已完成了,由于renderingengine还没写完,所以无法编译通过。在此对示例1.7中的代码进行简单的总结:
&&&&&&&在initWithFrame这个方法中,用工厂类的方式创建C++实现的rendering engine。接着还注册了两个事件处理,一个是”display link”,每当屏刷新的时候就会触发事件处理,还有一个是方向改变的时候触发事件处理。
&&&&&&&在didRotate这个事件处理方法中,将iPhone特有的设备方向枚举类型强制转化为我们的跨平台的方向枚举类型,并传递到renderingengine中进一步处理。
&&&&&&&在屏幕刷新回调方法drawViw中,两次调用的时间差,并传递到rendering engine的UpdateAnimation这个方法中去。这样就可以在rendering engine中控制动画或其它模拟物理特性。
&&&&&&&drawView这个方法中还调用了rendering engine中的Render方法,然后将renderbuffer显示到屏幕。
注意 & &&&&&&& 在写本书的时候,苹果建意大家用CADisplayLink来触发OpenGL的绘制。还有一种方法就是用NSTimer触发。如果你想你的应用在iPhone OS 3.1以前的版本,那么你说去研究一下NSTimer,因为CADisplayLink是OS 3.1才加入的新支持。
实现Rendering Engine
&&&&&&& 在本小节,我们将实现一个IRenderingEngine接口的定义。选中Classes分组,鼠标右键,在弹出菜单中选择Add-&Newfile, 然后在左边选中C and C++ 分类,右边选中C++File这个模板, 新建文件命名为RenderingEngine1.cpp,由于我们将在cpp文件里直接定义类,所以不需要生成相应的头文件,于是确保”Also create RenderingEngine1.h”未被选中。然后在文件中写入示例1.8的代码。
示例1.8 RenderingEngine1 Class与工厂方法
#include &OpenGLES/ES1/gl.h& #include &OpenGLES/ES1/glext.h& #include &IRenderingEngine.hpp& & class RenderingEngine1 : public IRenderingEngine { public: &&& RenderingEngine1(); &&& void Initialize(int width, int height); &&& void Render() &&& void UpdateAnimation(float timeStep) {} &&& void OnRotate(DeviceOrientation newOrientation) {} private: &&& GLuint m_ &&& GLuint m_ }; & IRenderingEngine* CreateRenderer1() { &&& return new RenderingEngine1(); }
For now,&UpdateAnimation&and&OnRotate&are i you'll add support for the rotation feature after we get up and running.
&shows more of thecode from&RenderingEngine1.cpp&with the OpenGLinitialization code.
现在UpdateAnimation与OnRotate的实现先放一下,等程序可以运行起来以后再来实现 。
示例1.9 是RenderingEngine1中一些初始化OpenGL的代码
struct Vertex { &&& float Position[2]; &&& float Color[4]; }; & // Define the positions and colors of two triangles. const Vertex Vertices[] = { &&& {{-0.5, -0.866}, {1, 1, 0.5f, 1}}, &&& {{0.5, -0.866},& {1, 1, 0.5f, 1}}, &&& {{0, 1},&&&&&&&& {1, 1, 0.5f, 1}}, &&& {{-0.5, -0.866}, {0.5f, 0.5f, 0.5f}}, &&& {{0.5, -0.866},& {0.5f, 0.5f, 0.5f}}, &&& {{0, -0.4f},&&&& {0.5f, 0.5f, 0.5f}}, }; & RenderingEngine1::RenderingEngine1() { &&& glGenRenderbuffersOES(1, &m_renderbuffer); &&& glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_renderbuffer); } & void RenderingEngine1::Initialize(int width, int height) { &&& // Create the framebuffer object and attach the color buffer. &&& glGenFramebuffersOES(1, &m_framebuffer); &&& glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer); &&& glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_COLOR_ATTACHMENT0_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_RENDERBUFFER_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& m_renderbuffer); &&& &&& glViewport(0, 0, width, height); &&& &&& glMatrixMode(GL_PROJECTION); &&& &&& // Initialize the projection matrix. &&& const float maxX = 2; &&& const float maxY = 3; &&& glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1); &&& &&& glMatrixMode(GL_MODELVIEW); }
&&&&&&& 示例1.9中,定义了一个POD(plain old data)类型的数据结构,用来存放构成三角形的顶点数据。在下一章中你会学到,一个顶点在OpenGL中可以有多种属性。HelloArrow只用了两种属性:一个二维坐标,一个RGBA值。
&&&&&&& 在复杂的OpenGL应用中,顶点数据往往都是从外部文件中导入的。在这儿由于图形太简单,我们就直接在代码中生成顶点数据。两个三角形,六个顶点,第一个三角形是黄色,第二个是灰色。(参看图1.4)
&&&&&&& 在类的构造函数与Initialize方法中进行了framebuffer的初始化工作。在设用者(GLView)调用构造函数与Initialize之间,必须分配renderbuffer的storage,这一分配没有在rendering engine中进行的原因是它是Objective-C的语法,而renderingengine中只支持C/C++语法。
&&&&&&& 最后但很重要的一点,在Initialize中设置了视图变换与投影矩阵。投影矩阵定义了三维空间中可见场景。这些在下一章详细介绍。
这儿有一个步骤摘要表。
1.&&&&创建renderbuffer并绑定到固定渲染通道。
2.&&&&用EAGL layer创建一个renderbuffer的storage,必须要用到Objective-C的语法。
3.&&&&创建一个framebuffer并将renderbuffer附属于它。
4.&&&&用glViewport设置视图矩阵,用glOrthof设置投影矩阵。
示例1.10是Render的实现代码
示例1.10 Render实现
void RenderingEngine1::Render() const { &&& glClearColor(0.5f, 0.5f, 0.5f, 1); &&& glClear(GL_COLOR_BUFFER_BIT);&&&& //[1] &&& &&& glEnableClientState(GL_VERTEX_ARRAY);&& //[2] &&& glEnableClientState(GL_COLOR_ARRAY); &&& &&& glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &Vertices[0].Position[0]);&&&& //[3] &&& glColorPointer(4, GL_FLOAT, sizeof(Vertex), &Vertices[0].Color[0]); &&& &&& GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex); &&& glDrawArrays(GL_TRIANGLES, 0, vertexCount);&& //[4] &&& &&& glDisableClientState(GL_VERTEX_ARRAY);&&& //[5] &&& glDisableClientState(GL_COLOR_ARRAY); }
在下一章会详细说明这些代码的作用,在此只简要说明一下。
[1] 将renderbuffer设为灰色。
[2] 启用顶点属性(位置与颜色)。
[3] 向OpenGL传递顶点与颜色属性值。请看图1.8
[4] glDrawArrays这个方法的第一个参数为GL_TRIANGLES,第二个为0表示从顶点数组第一个位置开始,第三个参数vertexCount表示顶点个数。这个方法调用这前,得先调用gl*Pointer这样的方法获取顶点属性,这个方法也是向目标页绘制三角形。
[5]& 禁止这两个顶点属性,只有在绘制之前才会开启这些属性。在复杂的应用当中,可以在后续绘制中会用到复多的顶点属性,所以我们在用完顶点属性后得恢复到原始状态。由于我们这个应用简单,在此如果你不恢复也没事,但是我们得养成良好的编成习惯。
图1.8&InterleavedArrays
到此,先恭喜你一下,你已完成了一个OpenGL程序。最终效果如图1.9所示
图1.9 HelloArrow!
处理设备方向变化
&&&&&&& 在本章开始,我就说了箭头符号会随设备方向的变化而变化。在示例1.7中的代码已注册了事件的回调方法,那么接下来的事就是在renderingengine中实现这个回调方法。
&&&&&&& 首先在RenderingEngine1类中加入一个float型的成员变量m_currentAngle。它表示角度,并非是弧度。注意UpdateAnimation与OnRotate的变化(不再中空函数而变成了声明)。
class RenderingEngine1 : public IRenderingEngine { public: &&& RenderingEngine1(); &&& void Initialize(int width, int height); &&& void Render() &&&&void UpdateAnimation(float timeStep); &&& void OnRotate(DeviceOrientation newOrientation); private: &&&&float m_currentA &&& GLuint m_ &&& GLuint m_ };
OnRotate的实现如下:
void RenderingEngine1::OnRotate(DeviceOrientation orientation) { &&& float angle = 0; &&& &&& switch (orientation) { &&&&&&& case DeviceOrientationLandscapeLeft: &&&&&&&&&&& angle = 270; &&&&&&&&&&& &&&&&&&&&&& &&&&&&& case DeviceOrientationPortraitUpsideDown: &&&&&&&&&&& angle = 180; &&&&&&&&&&& &&&&&&&&&&& &&&&&&& case DeviceOrientationLandscapeRight: &&&&&&&&&&& angle = 90; &&&&&&&&&&& &&& } &&& &&& m_currentAngle = }
&&&&&&& 注意在switch语句中,Unknown,Portrait, FaceUp,FaceDown没有在分支语句当中,于是在这些情况下angle的值是为0。
&&&&&&& 现在,在Render方法中可以用glRotatef来旋转图形了,如示例1.11。新加代码已粗体显示。你会发现,还新加了glPushMatrix与glPopMatrix两行代码,这是为了防止图形变化的累积。在下一章你将会明白这些方法的意义(包括glRotatef)。
示例1.11 Render最终版
void RenderingEngine1::Render() const { &&& glClearColor(0.5f, 0.5f, 0.5f, 1); &&& glClear(GL_COLOR_BUFFER_BIT); &&& &&&&glPushMatrix(); &&& glRotatef(m_currentAngle, 0, 0, 1); &&& &&& glEnableClientState(GL_VERTEX_ARRAY); &&& glEnableClientState(GL_COLOR_ARRAY); &&& &&& glVertexPointer(2, GL_FLOAT, sizeof(Vertex), &Vertices[0].Position[0]); &&& glColorPointer(4, GL_FLOAT, sizeof(Vertex), &Vertices[0].Color[0]); &&& &&& GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex); &&& glDrawArrays(GL_TRIANGLES, 0, vertexCount); &&& &&& glDisableClientState(GL_VERTEX_ARRAY); &&& glDisableClientState(GL_COLOR_ARRAY); &&& &&&&glPopMatrix(); }
让旋转有动画效果
&&&&&&& 现在HelloArrow这个程序可以响应设备的方向移动,但是美中不足的是,一般的应用旋转都是很平滑的,而我们这个是突然旋转90度。
&&&&&&& 苹果在UIViewController这个类中提供了方法支持平滑的旋转,但是那种方法在OpenGL ES的应用程序中不太适合。原因如下:
考虑到效率问题,苹果建意避免混用Core Animation与 OpenGL ES。
绝佳的条件是renderbuffer的大小与比例应用的生命周期内不变,这有助于简单代码与执行效率。
在纯图形的应用程序中,开发者对动画与渲染需要有更完美的控制。
&&&&&&& 在示例1.12中在RenderEngine1类中加入了一个浮点类型变量m_desiredAngle,实现动画效果的时候有用。这个变量表示当前动画的结束角度,因此如果没有动画的时候m_currentAngle与m_desiredAngle应是相等的。
&&&&&&& 示例1.12中还加入了一个浮点型常量RevolutionsPerSecond来表示角速度,另外还加入了RotationDirection()这个私有方法,关于它在后面介绍。
示例1.12 RenderEngine1类的定义与实现
#include &OpenGLES/ES1/gl.h& #include &OpenGLES/ES1/glext.h& #include &IRenderingEngine.hpp& & static const float RevolutionsPerSecond = 1; & class RenderingEngine1 : public IRenderingEngine { public: &&& RenderingEngine1(); &&& void Initialize(int width, int height); &&& void Render() &&& void UpdateAnimation(float timeStep); &&& void OnRotate(DeviceOrientation newOrientation); private: &&&&float RotationDirection() &&& float m_desiredA &&& float m_currentA &&& GLuint m_ &&& GLuint m_ }; & ... & void RenderingEngine1::Initialize(int width, int height) { &&& // Create the framebuffer object and attach the color buffer. &&& glGenFramebuffersOES(1, &m_framebuffer); &&& glBindFramebufferOES(GL_FRAMEBUFFER_OES, m_framebuffer); &&& glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_COLOR_ATTACHMENT0_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_RENDERBUFFER_OES, &&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&m_renderbuffer); &&& &&& glViewport(0, 0, width, height); &&& &&& glMatrixMode(GL_PROJECTION); &&& &&& // Initialize the projection matrix. &&& const float maxX = 2; &&& const float maxY = 3; &&& glOrthof(-maxX, +maxX, -maxY, +maxY, -1, 1); &&& &&& glMatrixMode(GL_MODELVIEW); &&& &&& // Initialize the rotation animation state. &&&&OnRotate(DeviceOrientationPortrait); &&& m_currentAngle = m_desiredA }
&&&&&&& 现在去修改OnRotate中的代码,将里面的当前角度量变改为目标角度变量:
void RenderingEngine1::OnRotate(DeviceOrientation orientation) { &&& float angle = 0; &&& &&& switch (orientation) { &&&&&&&&&&& ... &&& } &&& &&& m_desiredAngle = }
&&&&&&& 在实现UpdateAnimation方法之前,让我们想想应用如何确定箭头符号的旋转方向,是顺时针还是逆时针呢?方法很简单,判断目标角度是否大于当前角度即可。如果用把设备从270度朝向改为0度朝向,增加的角度和小于360度。
&&&&&&& 关于RotationDirection(),用它来判读箭头符号是顺时针还是逆时针旋转。我们要控制m_currentAngle与m_desiredAngle这两个变量的值在[0,360)之间(0包括,360不包括)。
float RenderingEngine1::RotationDirection() const { &&& float delta = m_desiredAngle - m_currentA &&& if (delta == 0) &&&&&&& return 0; &&& &&& bool counterclockwise = ((delta & 0 && delta &= 180) || (delta & -180)); &&& return counterclockwise ? +1 : -1; }
&&&&&&& 下面是UpdateAnimation的实现,参数是以秒为单位的时间步进。
void RenderingEngine1::UpdateAnimation(float timeStep) { &&& float direction = RotationDirection(); &&& if (direction == 0) &&&&&&& &&& &&& float degrees = timeStep * 360 * RevolutionsPerS &&& m_currentAngle += degrees * &&& &&& // Ensure that the angle stays within [0, 360). &&& if (m_currentAngle &= 360) &&&&&&& m_currentAngle -= 360; &&& else if (m_currentAngle & 0) &&&&&&& m_currentAngle += 360; &&& &&& // If the rotation direction changed, then we overshot the desired angle. &&& if (RotationDirection() != direction) &&&&&&& m_currentAngle = m_desiredA }
&&&&&&& 是不是相当简单呀?但最后两行有待明说一下。因为角度是浮点型的,所以很容易跳过目标值,特别是时间步进值比较在的情况下。这两行的作用是,如果捕获到角度越界,就纠正其实到正确值。在这儿,你不是实现一个摇动的罗盘,所以只需简单纠正值即可, 不过摇动的罗盘也是一个很吸引人的iPhone应用呀!
&&&&&&& 现在你已完成了HelloArrow这个应用。完整源码,你将在本书的网站上随书源码中找到它(参看引子里的关于源码)。
用Shaders实现的Hello Arrow
&&&&&&& 在本小节,我们将创建一个支持ES2.0的rendering engine。这样我们就可以看到ES1.1与ES 2.0的区大区别。本人很赞同Khronos的ES 2.0不向后兼容ES 1.0的决定,这样使得学习起来不但简单不少还更加灵活。
&&&&&&& 由于前面良好的分层架构,现在可以很轻松的在保留ES 1.1功能的情况下加入ES 2.0支持。主要修改四处:
1.&&&&加入新文件到工程,用来编写vertex shader与fragment shader。
2.&&&&增加所需framework。
3.&&&&更新GLView的一些代码,让其使用ES 2.0的环境。
4.&&&&按照RenderingEngine1修改一份为RenderingEngine2。
&&&&&&& 下面的小节将详细讲解这些修改。关于第4外的修改,如果你不想参看RenderingEngine1面自己从头实现ES2.0的支持也可以。
&&&&&&& ES 2.0最大的特色就是shadinglanguage。Shaders分为两类,一类是vertexshader, 另一类是fragment shaders,它们以相对较小的代码段运行在图形处理芯片上。当你调用glDrawArrays后,vertex shader就负责移动顶点,而fragment则负责逐像素计算每个三角形的颜色。由于图形处理器的高度并行化, 可以同时进行数以千计的shader实例。
&&&&&&& Shader叫着GLSL(OpenGL Shading Language),是用类C的语言来做开发,类C并不表示是C。GLSL的程序是不能在Xcode中编译的,而是在运行时iPhone自已编译。我们的应用程序以C语言的字符串形式向OpenGL API提交Shader, 然后OpenGL把它编译成机器码。
注意 有些OpenGL ES的设备允许你离线编译shaders,这样一来你的应用程序就可以向OpenGL接交二进制形式的shader,而并非字符串的方式。到目前为止,iPhone只支持运行时编译shader,它由ARM处理器编译并将结果传送到图形处理器去运行,所以ARM功不可没。
&&&&&&& 首先得在工程中新一个分组用来存放shaders。在Groups&Files上鼠标右键,在弹出菜单中选择Add-&NewGroup,命名为”Shaders”。
&&&&&&& 然后在新建的Shaders分组上鼠标右键,在弹出菜单上选择Add-&New file。在othercategory中选择Empty File模版,命名为Simple.vert,在Location字段中在HelloArrow后面加上/Shader。因为这个文件不需要布署到设备上去,所以你可以取消AddTo Targets的选择框。在弹出的对话框中选择create来创建Shader目录。再用同样的方法创建一个名为Simple.frag的文件。
&&&&&&& 在说这两个文件的代码之前,我选说一个小巧门。除了用I/O操作来读到shaders外,& 以用#include的方式将他们嵌入到你的C/C++代码中。在C/C++中,多行的字符串通常比较繁琐,但是在这儿有一个宏可以让事情变得简单:
#define STRINGIFY(A)& #A
&&&&&&& 本节的后面会看到,我们会将这个宏放在renderingengine 源码中#include shaders的上面。然后整个shader(包括换行)就以字符串的形式引入-并不需要在第一行上加上双引号!
虽然STRINGIFY这个宏方便操作简单的shaders,但是我不建意在产品中用这个方法。第一,苹果的shader编译器对行数的报告不一定正确。同时,gcc的预处理器在你shader里字义了functions的时候,很有可能发生冲突。一个通用的办法就是将shader从文件中读取到一个字符串中。用Objective-C中封装的stringWithContentsOfFile就可以轻松办到。
&&&&&&& 示例1.13与示例1.14分别是vertex& shader与fragment shader。为了简洁起见,在这儿还是引用了STRINGIFY这个宏,但是在以后的shader开发中,会去除掉的。
示例1.13 Simple.vert
const char* SimpleVertexShader = STRINGIFY( &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& && attribute vec4 P attribute vec4 SourceC varying vec4 DestinationC uniform mat4 P uniform mat4 M & void main(void) { &&& DestinationColor = SourceC &&& gl_Position = Projection * Modelview * P } );
&&&&&&& 可以看到,shader里声明了attribute ,varying ,uniform类型的变量,你可以简单的理解为shader与人外界的连接点。vertex shader里也只简单的传递了一个颜色值,并进行了标准的变换。关于变换是下一章的内容。示例1.14中的fragment shade更是简洁。
示例1.14 Simple.frag
const char* SimpleFragmentShader = STRINGIFY( & varying lowp vec4 DestinationC & void main(void) { &&& gl_FragColor = DestinationC } );
&&&&&&& 同样的,把varying变量想像成连接点。这个fragment shader除了把传进过来的颜色设置一下,什么也没做。
Frameworks
&&&&&&& 请确保所有的framework都是用的SDK3.1的(或更高版本)。你可以在Xcode’s Group & Files栏选中相当的framework并鼠标右键,然后点击Get info, 这样就可以看到全路径了。
注意 &&&&&&& 这儿有一种快速修改的手动方法。首先得退出Xcode,然后在Finder中找到HelloArrow.xcodeproj,鼠标右键并择Show package contents。然后你会发现一个叫project.pbxproj的文件,用TextEdit打开它,找到SDKROOT这个宏,将它修改为正确的SDK路径即可。
&&&&&&& 可能你还记得,在创建OpenGL上下文的时候,传递了一个版本常量,这儿正是需要修改的部份。在Classes分组中打开GLView.mm并将下面代码:
m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; & if (!m_context || ![EAGLContext setCurrentContext:m_context]) { &&& [self release]; &&& } & m_renderingEngine = CreateRenderer1();
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; m_context = [[EAGLContext alloc] initWithAPI:api]; & if (!m_context || ForceES1) { &&& api = kEAGLRenderingAPIOpenGLES1; &&& m_context = [[EAGLContext alloc] initWithAPI:api]; } & if (!m_context || ![EAGLContext setCurrentContext:m_context]) { &&& [self release]; &&& } & if (api == kEAGLRenderingAPIOpenGLES1) { &&& m_renderingEngine = CreateRenderer1(); } else { &&& m_renderingEngine = CreateRenderer2(); }
&&&&&&& 上面的代码是在不支持ES2.0的设备上用ES 1.1,支持的则用ES 2.0。当然也可以强制用ES 1.1,只需将theForceES1&设为TRUE即可。将下面一行加入GLView.mm顶端。
const bool ForceES1 =
&&&&&&& 对于IRenderingEngine接口,只需要在IRenderingEngine.hpp中添加CreateRenderer2这个工厂创建方法,其它的并不需要做修改。
... & // Create an instance of the renderer and set up various OpenGL state. struct IRenderingEngine* CreateRenderer1(); struct IRenderingEngine* CreateRenderer2(); & // Interface to the OpenGL ES consumed by GLView. struct IRenderingEngine { &&& virtual void Initialize(int width, int height) = 0;&&& &&& virtual void Render() const = 0; &&& virtual void UpdateAnimation(float timeStep) = 0; &&& virtual void OnRotate(DeviceOrientation newOrientation) = 0; &&& virtual ~IRenderingEngine() {} };
RenderingEngine 实现
&&&&&&& Objective-C相关的部份已修改完了,现在继续修改核心。用Finder创建一个RenderingEngine1.cpp的拷贝(在工程中选中RenderingEngine1.cpp并鼠标右键,选中Reveal in Finder),并命名为RenderingEngine2.cpp。并把它加入到Xcode工程。右键选中Classes分组,交选择Add-&Existing Files。接着按示例1.15进行修改。新加入或修改部份用粗体显示。
示例1.15&RenderingEngine2声明
#include &OpenGLES/ES2/gl.h& #include &OpenGLES/ES2/glext.h& #include &cmath& #include &iostream& #include &IRenderingEngine.hpp& & #define STRINGIFY(A)& #A #include &../Shaders/Simple.vert& #include &../Shaders/Simple.frag& & static const float RevolutionsPerSecond = 1; & class&RenderingEngine2&: public IRenderingEngine { public: &&&&RenderingEngine2(); &&& void Initialize(int width, int height); &&& void Render() &&& void UpdateAnimation(float timeStep); &&& void OnRotate(DeviceOrientation newOrientation); private: &&& float RotationDirection() &&& GLuint BuildShader(const char* source, GLenum shaderType) &&& GLuint BuildProgram(const char* vShader, const char* fShader) &&& void ApplyOrtho(float maxX, float maxY) &&& void ApplyRotation(float degrees) &&& float m_desiredA &&& float m_currentA &&&&GLuint m_simpleP &&& GLuint m_ &&& GLuint m_ };
&&&&&&& 可能你已想到,会修改Render()这个方法的。你可以比较一下示例1.11与示例1.16。
示例1.16& OpenGL ES 2.0 的Render()
void RenderingEngine2::Render() const { &&& glClearColor(0.5f, 0.5f, 0.5f, 1); &&& glClear(GL_COLOR_BUFFER_BIT); &&& &&& ApplyRotation(m_currentAngle); &&& &&& GLuint positionSlot = glGetAttribLocation(m_simpleProgram, &Position&); &&& GLuint colorSlot = glGetAttribLocation(m_simpleProgram, &SourceColor&); &&& &&& glEnableVertexAttribArray(positionSlot); &&& glEnableVertexAttribArray(colorSlot); &&& &&& GLsizei stride = sizeof(Vertex); &&& const GLvoid* pCoords = &Vertices[0].Position[0]; &&& const GLvoid* pColors = &Vertices[0].Color[0]; &&& &&& glVertexAttribPointer(positionSlot, 2, GL_FLOAT, GL_FALSE, stride, pCoords); &&& glVertexAttribPointer(colorSlot, 4, GL_FLOAT, GL_FALSE, stride, pColors); &&& &&& GLsizei vertexCount = sizeof(Vertices) / sizeof(Vertex); &&& glDrawArrays(GL_TRIANGLES, 0, vertexCount); &&& &&& glDisableVertexAttribArray(positionSlot); &&& glDisableVertexAttribArray(colorSlot); }
&&&&&&& 正如你所看到的,1.1与2.0版本的Render()有很大区别,但总体来说,他们的操作都差不多。
&&&&&&& 在ES 2.0中,framebuffer对象不再是扩展功能,而是core API。幸运的是,OpenGL有严格的命名规则,因此修改非常机械,只需要简单的去掉OES后缀即可。对于方法,后缀是”OES”,对于常量后缀是”_OES”,这样一来修改将非常容易:
RenderingEngine2::RenderingEngine2() { &&& glGenRenderbuffers(1, &m_renderbuffer); &&& glBindRenderbuffer(GL_RENDERBUFFER, m_renderbuffer); }
&&& Initialize是最后一个需要修改的公有方法,见示例1.17。
示例1.17& RenderingEngine2 的Initalize
void RenderingEngine2::Initialize(int width, int height) { &&& // Create the framebuffer object and attach the color buffer. &&& glGenFramebuffers(1, &m_framebuffer); &&& glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); &&& glFramebufferRenderbuffer(GL_FRAMEBUFFER, &&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_COLOR_ATTACHMENT0, &&&&&&&&&&&&&&&&&&&&&&&&&&&&& GL_RENDERBUFFER, &&&&&&&&&&&&&&&&&&&&&&&&&&&&& m_renderbuffer); &&& &&& glViewport(0, 0, width, height); &&& &&& m_simpleProgram = BuildProgram(SimpleVertexShader, SimpleFragmentShader); &&& &&& glUseProgram(m_simpleProgram); &&& &&& // Initialize the projection matrix. &&& ApplyOrtho(2, 3); &&& &&& // Initialize rotation animation state. &&& OnRotate(DeviceOrientationPortrait); &&& m_currentAngle = m_desiredA }
&&&&&&& 这个方法里调用了BuildProgram这个私有方法,而BuildProgram的实现中先后调用了BuildShader这个私有方法。在OpenGL的技术中,program就是一个将多个shader连接在一起的模型。这些方法的实现见示例1.18。
示例 1.18& BuildProgram()与BuildShader()
GLuint RenderingEngine2::BuildShader(const char* source, GLenum shaderType) const { &&& GLuint shaderHandle = glCreateShader(shaderType); &&& glShaderSource(shaderHandle, 1, &source, 0); &&& glCompileShader(shaderHandle); &&& &&& GLint compileS &&& glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess); &&& &&& if (compileSuccess == GL_FALSE) { &&&&&&& GLchar messages[256]; &&&&&&& glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]); &&&&&&& std::cout && &&&&&&& exit(1); &&& } &&& &&& return shaderH } & GLuint RenderingEngine2::BuildProgram(const char* vertexShaderSource, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& const char* fragmentShaderSource) const { &&& GLuint vertexShader = BuildShader(vertexShaderSource, GL_VERTEX_SHADER); &&& GLuint fragmentShader = BuildShader(fragmentShaderSource, GL_FRAGMENT_SHADER); &&& &&& GLuint programHandle = glCreateProgram(); &&& glAttachShader(programHandle, vertexShader); &&& glAttachShader(programHandle, fragmentShader); &&& glLinkProgram(programHandle); &&& &&& GLint linkS &&& glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess); &&& if (linkSuccess == GL_FALSE) { &&&&&&& GLchar messages[256]; &&&&&&& glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]); &&&&&&& std::cout && &&&&&&& exit(1); &&& } &&& &&& return programH }
&&&&&&& 在示例1.18中,用到了控制台I/O相关方法来显示shader编译时所生的错误。不管你的shader是如何简单,你最好都处理这些错误,这对你有好处,这一点你得相信。在iPhone屏幕上是不会显示这些控制台信息的,但是你可以在Xcode的GDB窗口看到,通过菜单Run-&Console可以打开GDB窗口。图1.10就在控制台窗口中显示出错误信息。
图 1.10& 调试控制台
&&&&&&& 图1.10中显示了当前用的OpenGLES版本,现在我们要加入这些信息, 打开GLView类,并加入如下粗体代码:
if (api == kEAGLRenderingAPIOpenGLES1) { &&&&NSLog(@&Using OpenGL ES 1.1&); &&& m_renderingEngine = CreateRenderer1(); } else { &&& NSLog(@&Using OpenGL ES 2.0&); &&&&m_renderingEngine = CreateRenderer2(); }
&&&&&&& 在Objective-C中是用NSLog来输出诊断信息的,它会自动在输出字符串关加上时间戳与自动换行。(回忆一下:Objective-C中的字符串用@这个前缀来区别C的字符串。)
&&&&&&& 再来看看RenderingEngine2.cpp的内容,还有ApplyOrthof与ApplyRotation两个方法没有实现。由于ES 2.0没有glOrthof与glRotatef这两个API,所以我们得自大实现。(在下一章,我们会建立一个简单的数学库来完成这些功能。)调用glUniformMatrix4fv就是向shader中的uniform变量传值。
示例 1.19&ApplyOrtho()与ApplyRotatation()
void RenderingEngine2::ApplyOrtho(float maxX, float maxY) const { &&& float a = 1.0f / maxX; &&& float b = 1.0f / maxY; &&& float ortho[16] = { &&&&&&& a, 0,& 0, 0, &&&&&&& 0, b,& 0, 0, &&&&&&& 0, 0, -1, 0, &&&&&&& 0, 0,& 0, 1 &&& }; &&& &&& GLint projectionUniform = glGetUniformLocation(m_simpleProgram, &Projection&); &&& glUniformMatrix4fv(projectionUniform, 1, 0, &ortho[0]); } & void RenderingEngine2::ApplyRotation(float degrees) const { &&& float radians = degrees * 3.14159f / 180.0f; &&& float s = std::sin(radians); &&& float c = std::cos(radians); &&& float zRotation[16] = { &&&&&&& c, s, 0, 0, &&&&&& -s, c, 0, 0, &&&&&&& 0, 0, 1, 0, &&&&&&& 0, 0, 0, 1 &&& }; &&& &&& GLint modelviewUniform = glGetUniformLocation(m_simpleProgram, &Modelview&); &&& glUniformMatrix4fv(modelviewUniform, 1, 0, &zRotation[0]); }
&&&&&&& 不要被上面代码中的矩阵吓到,我们在下一章介绍。
&&&&&&& 最后将RenderingEngine2.cpp里的所有RenderingEngine1的字符串全改为RenderingEngine2(同时把工厂创建的方法名改为CreateRenderer2)。这样就完成了所有的修改,来支持ES2.0。很明显示,ES 2.0比ES 1.0更接近底层。(译注:”closer to the metal”是ATI的第一代GPGPU技术,见http://en.wikipedia.org/wiki/Close_to_Metal)。
&&&&&&& 在本章,我们步入了iPhone OpenGL ES开发的世界,实现了一些基础框架,在本书后面章节中会继续完善,并从零开始完成了一个应用程序 — 同时支持两个版本的OpenGL ES!
&&&&&&& 在下一章,我们将学习一些图形学基础知识,并阐述Hell Arrow涉及到的一些概念。如果你对图形学已非常熟悉,那你可以跳过它。
原文链接:
共有0个评论
有什么技术问题吗?
长平狐的其他问题
类似的话题

我要回帖

更多关于 大象册邀请码 的文章

 

随机推荐