《iPhone 3D 编程》第一章:快速入门指南

****************************************************************************html

申明:本系列教程原稿来自网络,翻译目的仅供学习与参看,请匆用于商业目的,若是产生商业等纠纷均与翻译人、该译稿发表人无关。转载务必保留此申明。java

内容:《iPhone 3D 编程》第一章:快速入门指南ios

原文地址:http://ofps.oreilly.com/titles/9780596804824/chquick.html 译文地址:http://blog.csdn.net/favormm/article/details/6888328c++

 

****************************************************************************程序员

 

 

第一章:快速入门指南

 

        在本章,将从零基础开始,教你学会编写第一个应用程序。这个应用程序就是”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 开发架构api

 

\       

        在最底层,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。xcode

 

iPhone支持的图形技术:

Quartz 2D Rendering Engine

支持alpha通道,层与多采样抗锯齿的失量库。这个库在MacOS X上同样可用。若是你的应用想运用这个技术,就必须关联Quartz Core.framework(framework是苹果公司对库与资源的一种打包方式)

Core Graphics

Quartz的C语言接口,一样在Mac OS X也是有效的。

UIKit

iPhone上原生态的窗口框架,除此以外,UIKit还封装了Quartz的Objective_C版。在Mac OS X上与此框架类似的是Cocoa的组件AppKit。

Cocoa Touch

iPhone开发架构中的层概念,它包括了UIKit与其它几个framework.

Core Animation

Objective-C封装的能够轻松实现复杂动画的framework.

OpenGL ES

渲染2D与3D图形并支持硬件加速的底层c语言的API 集.

EAGL

一些创建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就诞生了。

 

        在近年,图形学技术的飞速发展远远超出了摩尔定律。.[1]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实现,正如我前面所说, 我相信它在开发当中更优雅更有效。

 

作个小总结,你能够从下面四种方法中选择来开发你的应用:

  1. 只用OpenGL ES 1.1。
  2. 只用OpenGL ES 2.0。
  3. 在运行时判断是否支持ES 2.0,若是支持则用,若是不支持就用ES 1.1。
  4. 为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能够在这儿下载:http://developer.apple.com/iphone/

 

        它是一个以.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,做为一个新手,选择它是明智的。

图1.2

 \

 

        选择模版后,会有一个对话框要求你输入工程名字。完成以后,你就能够看到Xcode的工做窗口了。在Build菜单里有一个Build and Run,选择它开始编译并运行,你也能够按快捷键⌘-Return。编译完成后,模拟器将会被启动,其中有一个方形的图形在模拟器上上下移动,如图1.3所示。若是你想退出这个应用程序,直接按⌘-Q便可。

 

图1.3 OpenGL模版应用

 \

部署到真机

 

        这一步并非必须的。若是你想要布署到真机上,那么你必须注册苹果的iPhone Developer Program。这样才容许你真机调试。获取这个准证是一个繁杂的过程,可是幸亏一台设备只需作一次。如今苹果推出了一个简单的方法来处理这些流程。你能够登陆iPhoneDev Center (http://developer.apple.com/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_context;

}

- (void) drawView;

@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

    }

    return self;

}

 

 

        这是一个Objective-C的初始化方法,有点相似C++中的构造函数。返回值类型由一个小括号包住,有点像C中的强制类型转换。if那一句同时完成了几个处理:首先调用基类的initWithFrame,并将返回结果赋值给self, 最后再判断是否成功。

 

In Objective-C parlance, you don't call methods on objects;you 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.layer; //[1]

        eaglLayer.opaque = YES; //[2]

       

        m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; //[3]

       

        if (!m_context || ![EAGLContext setCurrentContext:m_context]) { //[4]

            [self release];

            return nil;//[5]

        }

       

        // OpenGL Initialization

    }

    return self;

}

 

代码分析:

[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, renderbuffer;

    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_window;

    GLView* m_view;

}

 

@property (nonatomic, retain) IBOutlet UIWindow *m_window;

 

@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_window;

        前面说过,@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 renderer; 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_context;

    IRenderingEngine* m_renderingEngine;

    float m_timestamp;

}

 

- (void) drawView: (CADisplayLink*) displayLink;

- (void) didRotate: (NSNotification*) notification;

 

@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.layer;

        eaglLayer.opaque = YES;

       

        m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

       

        if (!m_context || ![EAGLContext setCurrentContext:m_context]) {

            [self release];

            return nil;

        }

       

        m_renderingEngine = CreateRenderer1();       

       

        [m_context

         renderbufferStorage:GL_RENDERBUFFER

         fromDrawable: eaglLayer];

       

        m_renderingEngine->Initialize(CGRectGetWidth(frame), CGRectGetHeight(frame));

       

        [self drawView: nil];

        m_timestamp = CACurrentMediaTime();

       

        CADisplayLink* displayLink;

        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];

    }

    return self;

}

 

- (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_timestamp;

        m_timestamp = displayLink.timestamp;

        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() const;

    void UpdateAnimation(float timeStep) {}

    void OnRotate(DeviceOrientation newOrientation) {}

private:

    GLuint m_framebuffer;

    GLuint m_renderbuffer;

};

 

IRenderingEngine* CreateRenderer1()

{

    return new RenderingEngine1();

}

 

For now, UpdateAnimation and OnRotate are implemented withstubs; you'll add support for the rotation feature after we get up and running.

Example 1.9 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() const;

    void UpdateAnimation(float timeStep);

    void OnRotate(DeviceOrientation newOrientation);

private:

    float m_currentAngle;

    GLuint m_framebuffer;

    GLuint m_renderbuffer;

};

 

OnRotate的实现以下:

 

void RenderingEngine1::OnRotate(DeviceOrientation orientation)

{

    float angle = 0;

   

    switch (orientation) {

        case DeviceOrientationLandscapeLeft:

            angle = 270;

            break;

           

        case DeviceOrientationPortraitUpsideDown:

            angle = 180;

            break;

           

        case DeviceOrientationLandscapeRight:

            angle = 90;

            break;

    }

   

    m_currentAngle = angle;

}

 

        注意在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() const;

    void UpdateAnimation(float timeStep);

    void OnRotate(DeviceOrientation newOrientation);

private:

    float RotationDirection() const;

    float m_desiredAngle;

    float m_currentAngle;

    GLuint m_framebuffer;

    GLuint 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);

   

    // Initialize the rotation animation state.

    OnRotate(DeviceOrientationPortrait);

    m_currentAngle = m_desiredAngle;

}

 

        如今去修改OnRotate中的代码,将里面的当前角度量变改成目标角度变量:

 

 

void RenderingEngine1::OnRotate(DeviceOrientation orientation)

{

    float angle = 0;

   

    switch (orientation) {

            ...

    }

   

    m_desiredAngle = angle;

}

 

 

        在实现UpdateAnimation方法以前,让咱们想一想应用如何肯定箭头符号的旋转方向,是顺时针仍是逆时针呢?方法很简单,判断目标角度是否大于当前角度便可。若是用把设备从270度朝向改成0度朝向,增长的角度和小于360度。

 

        关于RotationDirection(),用它来判读箭头符号是顺时针仍是逆时针旋转。咱们要控制m_currentAngle与m_desiredAngle这两个变量的值在[0,360)之间(0包括,360不包括)。

float RenderingEngine1::RotationDirection() const

{

    float delta = m_desiredAngle - m_currentAngle;

    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)

        return;

   

    float degrees = timeStep * 360 * RevolutionsPerSecond;

    m_currentAngle += degrees * direction;

   

    // 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_desiredAngle;

}

 

        是否是至关简单呀?但最后两行有待明说一下。由于角度是浮点型的,因此很容易跳过目标值,特别是时间步进值比较在的状况下。这两行的做用是,若是捕获到角度越界,就纠正其实到正确值。在这儿,你不是实现一个摇动的罗盘,因此只需简单纠正值便可, 不过摇动的罗盘也是一个很吸引人的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的支持也能够。

 

Shaders

 

        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 Position;

attribute vec4 SourceColor;

varying vec4 DestinationColor;

uniform mat4 Projection;

uniform mat4 Modelview;

 

void main(void)

{

    DestinationColor = SourceColor;

    gl_Position = Projection * Modelview * Position;

}

);

 

        能够看到,shader里声明了attribute ,varying ,uniform类型的变量,你能够简单的理解为shader与人外界的链接点。vertex shader里也只简单的传递了一个颜色值,并进行了标准的变换。关于变换是下一章的内容。示例1.14中的fragment shade更是简洁。

 

示例1.14 Simple.frag

 

const char* SimpleFragmentShader = STRINGIFY(

 

varying lowp vec4 DestinationColor;

 

void main(void)

{

    gl_FragColor = DestinationColor;

}

);

 

        一样的,把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路径便可。

 

GLView

 

        可能你还记得,在建立OpenGL上下文的时候,传递了一个版本常量,这儿正是须要修改的部份。在Classes分组中打开GLView.mm并将下面代码:

 

m_context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

 

if (!m_context || ![EAGLContext setCurrentContext:m_context]) {

    [self release];

    return nil;

}

 

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];

    return nil;

}

 

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 = false;

 

        对于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 renderer; 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() const;

    void UpdateAnimation(float timeStep);

    void OnRotate(DeviceOrientation newOrientation);

private:

    float RotationDirection() const;

    GLuint BuildShader(const char* source, GLenum shaderType) const;

    GLuint BuildProgram(const char* vShader, const char* fShader) const;

    void ApplyOrtho(float maxX, float maxY) const;

    void ApplyRotation(float degrees) const;

    float m_desiredAngle;

    float m_currentAngle;

    GLuint m_simpleProgram;

    GLuint m_framebuffer;

    GLuint m_renderbuffer;

};

 

        可能你已想到,会修改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_desiredAngle;

}

 

 

        这个方法里调用了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 compileSuccess;

    glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);

   

    if (compileSuccess == GL_FALSE) {

        GLchar messages[256];

        glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);

        std::cout << messages;

        exit(1);

    }

   

    return shaderHandle;

}

 

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 linkSuccess;

    glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);

    if (linkSuccess == GL_FALSE) {

        GLchar messages[256];

        glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);

        std::cout << messages;

        exit(1);

    }

   

    return programHandle;

}

 

        在示例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涉及到的一些概念。若是你对图形学已很是熟悉,那你能够跳过它。