[OpenGL ES 01]OpenGL ES之初体验
[OpenGL ES 01]OpenGL ES之初体验
罗朝辉 (http://blog.csdn.net/kesalin)
本文遵循“署名-非商业用途-保持一致”创作公用协议OpenGL ES 是专门为手持设备制定的 3D 规范,它是 OpenGL 的简化版,该规范由khronos.org制定,目前最新规范版本为 3.0。 OpenGL ES 可以在不同手机系统上实现,也可以在浏览器上实现(Web GL)。目前较新的 iOS 支持OpenGL ES 2.0,在这里,我将介绍如何在 iOS 上使用 OpenGL ES,我是OpenGL ES 的初学者,错误之处难免,欢迎指出,共同提高。
1),打开XCode(我使用的是4.2),创建一个 Empty Application。
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413389.png)
2),命名为 Tutorial01,选择Device Family为iPhone,保持默认选中的use Automatic Reference Counting来使用自动引用计数。
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413390.png)
3),添加需要用到的库,在iOS平台上进行OpenGL ES 开发,OpenGLES.framework和QuartzCore.framework这两个库是必须的,选中Target:Tutorial01,在Build phase->Link Binary With Libraries中点击 + 号来添加这两个库:
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413391.png)
添加完毕,工程结构如下图,你可以把这两个 framework 拖到 Frameworks 文件夹中,谁也不想工程结构乱七八糟的吧?
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413392.png)
4),至此,编译运行,模拟器是一片空白的!因为Empty Application模版就是Empty,里面甚至连一个Window都木有。因此,我们需要添加一个 Window。右击 Supporting Files文件夹,选择 New File->User Interface->Window:
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413393.png)
输入名称:MainWindow
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413394.png)
5),为了让 AppDelegate 与 Window 关联起来,我们还需要在MainWindow.xib中创建一个Object对象。选中MainWindow.xib,向其中拖入一个 Object 对象:
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413395.png)
添加完毕,效果如下:
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413396.png)
6),然后我们修改该 Object 的Custom Class为 AppDelegate,这样它在 xib 中代表代码中的 AppDelegate了。
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413397.png)
7),为了将 Window与App Delegate 关联起来,我们需要在 AppDelegate.h中的代码 window 属性前添加 IBOutlet 修饰符:
@property (strong, nonatomic) IBOutlet UIWindow *window;
8),选中MainWindow.xib,右击 AppDelegate,将Outlet window拖拽到其上方的 Window上,这样AppDelegate中的window就与真实的 Window 关联起来。
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413398.png)
9),同样,我们还需要修改File's Owner的 Custom Class 为 UIApplication,使用与8)中同样的拖拽技巧,将 File's Owner的 delegate 与 App Delegate 关联起来。
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413399.png)
10),至此准备工作完毕,不妨编译运行一下,模拟器依然一片空白,那是因为我们还没有在 Window 上添加 view,下面我们将来添加一个 view。
1),虽然 iOS 5在 GLKit 中提供了方便使用 OpenGL ES 的辅助 GLKView,但在这里,我们还是从零开始手工打造我们自己 GL ES view,从而更进一步了解在 iOS 上 OpenGL ES 是使用的。在Tutorial01目录中 New File,选择 User Interface->View作为模版,命名为 OpenGLView:
![[OpenGL ES 01]OpenGL ES之初体味](http://img.reader8.net/uploadfile/jiaocheng/2014017/1442/2014011401423413400.png)
2),修改 OpenGLView.h为:
#import <UIKit/UIKit.h>#import <QuartzCore/QuartzCore.h>#include <OpenGLES/ES2/gl.h>#include <OpenGLES/ES2/glext.h>@interface OpenGLView : UIView { CAEAGLLayer* _eaglLayer; EAGLContext* _context; GLuint _colorRenderBuffer; GLuint _frameBuffer;}@end这些变量在后面会有介绍。
3),在 OpenGLView.m 中添加如下函数:
+ (Class)layerClass { // 只有 [CAEAGLLayer class] 类型的 layer 才支持在其上描绘 OpenGL 内容。 return [CAEAGLLayer class];}为了让 UIView 显示 opengl 内容,我们必须将默认的 layer 类型修改为 CAEAGLLayer 类型(这种动态修改返回类类型的手段在 [深入浅出Cocoa]详解键值观察(KVO)及其实现机理 一文中也有应用)。
4),默认的 CALayer 是透明的,我们需要将它设置为 opaque 才能看到在它上面描绘的东西。为此,我们使用匿名 category 技巧,在 OpenGLView.m的开头(在@implementation
// 使用匿名 category 来声明私有成员@interface OpenGLView()-(void)setupLayer;@end
接着,在 @implementation 与 @end 之间,添加 setupLayer 的实现:
- (void)setupLayer{ _eaglLayer = (CAEAGLLayer*) self.layer; // CALayer 默认是透明的,必须将它设为不透明才能让其可见 _eaglLayer.opaque = YES; // 设置描绘属性,在这里设置不维持渲染内容以及颜色格式为 RGBA8 _eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];}5),至此 layer 的配置已经就绪,下面我们来创建与设置与 OpenGL ES 相关的东西。首先,我们需要创建OpenGL ES 渲染上下文(在iOS中对应的实现为EAGLContext),这个 context 管理所有使用OpenGL ES 进行描绘的状态,命令以及资源信息。然后,需要将它设置为当前 context,因为我们要使用 OpenGL ES 进行渲染(描绘)。在匿名 category 中添加 -(void)setupContext; 声明,并在@implement与@end之间添加其实现。这与使用 Core Graphics 进行描绘必须创建 Core Graphics Context 的道理是一样。
- (void)setupContext { // 指定 OpenGL 渲染 API 的版本,在这里我们使用 OpenGL ES 2.0 EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2; _context = [[EAGLContext alloc] initWithAPI:api]; if (!_context) { NSLog(@"Failed to initialize OpenGLES 2.0 context"); exit(1); } // 设置为当前上下文 if (![EAGLContext setCurrentContext:_context]) { NSLog(@"Failed to set current OpenGL context"); exit(1); }}6),创建 renderbuffer
有了上下文,openGL还需要在一块 buffer 上进行描绘,这块 buffer 就是 RenderBuffer(OpenGL ES 总共有三大不同用途的color buffer,depth buffer 和 stencil buffer,这里是最基本的 color buffer)。下面,我们依然创建私有方法 setupRenderBuffer 来生成 color buffer:
- (void)setupRenderBuffer { glGenRenderbuffers(1, &_colorRenderBuffer); glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer); // 为 color renderbuffer 分配存储空间 [_context renderbufferStorage:GL_RENDERBUFFER fromDrawable:_eaglLayer]; glGenRenderbuffers 的原型为:
void glGenRenderbuffers (GLsizei n, GLuint* renderbuffers)
它是为 renderbuffer 申请一个 id(或曰名字)。参数 n 表示申请生成 renderbuffer 的个数,而 renderbuffers 返回分配给 renderbuffer 的 id,注意:返回的 id 不会为0,id 0 是OpenGL ES 保留的,我们也不能使用 id 为0的 renderbuffer。
glBindRenderbuffer 的原型为:
void glBindRenderbuffer (GLenum target, GLuint renderbuffer)
这个函数将指定 id 的 renderbuffer 设置为当前 renderbuffer。参数 target 必须为 GL_RENDERBUFFER,参数 renderbuffer 是就是使用 glGenRenderbuffers 生成的 id。当指定 id 的 renderbuffer 第一次被设置为当前 renderbuffer 时,会初始化该 renderbuffer 对象,其初始值为:
width 和 height:像素单位的宽和高,默认值为0;
internal format:内部格式,三大 buffer 格式之一 -- color,depth or stencil;
Color bit-depth:仅当内部格式为 color 时,设置颜色的 bit-depth,默认值为0;
Depth bit-depth:仅当内部格式为 depth时,默认值为0;
Stencil bit-depth: 仅当内部格式为 stencil,默认值为0;
函数 - (
- (void)setupFrameBuffer { glGenFramebuffers(1, &_frameBuffer); // 设置为当前 framebuffer glBindFramebuffer(GL_FRAMEBUFFER, _frameBuffer); // 将 _colorRenderBuffer 装配到 GL_COLOR_ATTACHMENT0 这个装配点上 glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderBuffer);}setupFrameBuffer 大体与前面的 setupRenderBuffer 相同,由 glGenFramebuffers分配的 id也不可能是 0,id 为 0 的 framebuffer 是OpenGL ES 保留的,它指向窗口系统提供的 framebuffer,我们同样不能使用 id 为 0 的framebuffer,否则系统会出错。glFramebufferRenderbuffer的函数原型为:
void glFramebufferRenderbuffer (GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
该函数是将相关 buffer(三大buffer之一)attach到framebuffer上(如果 renderbuffer不为 0,知道前面为什么说glGenRenderbuffers 返回的id 不会为 0 吧)或从 framebuffer上detach(如果 renderbuffer为 0)。参数 attachment 是指定 renderbuffer 被装配到那个装配点上,其值是GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT中的一个,分别对应 color,depth和 stencil三大buffer。
8),当 UIView 在进行布局变化之后,由于 layer 的宽高变化,导致原来创建的 renderbuffer不再相符,我们需要销毁既有 renderbuffer 和 framebuffer。下面,我们依然创建私有方法 destoryRenderAndFrameBuffer 来销毁生成的 buffer:
- (void)destoryRenderAndFrameBuffer{ glDeleteFramebuffers(1, &_frameBuffer); _frameBuffer = 0; glDeleteRenderbuffers(1, &_colorRenderBuffer); _colorRenderBuffer = 0;}9), 至此,理论也讲得足够多了,让我们来画点东西看看效果如何。下面,我们依然创建私有方法 render 来进行真正的描绘:
- (void)render { glClearColor(0, 1.0, 0, 1.0); glClear(GL_COLOR_BUFFER_BIT); [_context presentRenderbuffer:GL_RENDERBUFFER];}glClearColor (三,进行渲染
1,有了前面的准备工作,我们来看看我们的成果吧。首先在 AppDelegate中使用 OpenGLView作为 window 的view,修改 AppDelegate.h为:
#import <UIKit/UIKit.h>#import "OpenGLView.h"@interface AppDelegate : UIResponder <UIApplicationDelegate>{ OpenGLView* _glView;}@property (strong, nonatomic) IBOutlet UIWindow *window;@property (strong, retain) IBOutlet OpenGLView *glView;@end2,在 AppDelegate.m 中实现如下代码:
@implementation AppDelegate@synthesize window = _window;@synthesize glView = _glView;- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; CGRect screenBounds = [[UIScreen mainScreen] bounds]; self.glView = [[OpenGLView alloc] initWithFrame:screenBounds]; [self.window addSubview:self.glView]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES;}由于我们使用 ARC,所以不必担心资源的释放。
3,返回 OpenGLView.m,在其中添加函数:
5,如果你还没有保存你的代码,选择 File-Source Control->Commit, 提交你的代码到 git 中吧,时常提交代码是个好习惯。后续文章我们还将使用到在这里编写的代码。本文源代码可以在这里查看与下载:https://github.com/kesalin/OpenGLES
四,Refference
OpenGL ES 2.0 for iPhone
OpenGL ES 2.0 Programming Guide