[OpenGL ES 01]iOS上OpenGL ES之初体验
[OpenGL ES 01]OpenGL ES之初体验
罗朝辉 (http://www.cnblogs.com/kesalin/)
一,什么是 OpenGL ES?
OpenGL ES 是专门为手持设备制定的 3D 规范,它是 OpenGL 的简化版,该规范由khronos.org制定,目前最新规范版本为 3.0。 OpenGL ES 可以在不同手机系统上实现,也可以在浏览器上实现(Web GL)。目前较新的 iOS 支持OpenGL ES 2.0,在这里,我将介绍如何在 iOS 上使用 OpenGL ES 2.0。
二,在iOS上如何使用OpenGL ES?
1,准备工作
1),打开XCode(我使用的是4.2),创建一个 Empty Application。
2),命名为 Tutorial01,选择Device Family为iPhone,保持默认选中的use Automatic Reference Counting来使用自动引用计数。
3),添加需要用到的库,在iOS平台上进行OpenGL ES 开发,OpenGLES.framework和QuartzCore.framework这两个库是必须的,选中Target:Tutorial01,在Build phase->Link Binary With Libraries中点击 + 号来添加这两个库:
添加完毕,工程结构如下图,你可以把这两个 framework 拖到 Frameworks 文件夹中,谁也不想工程结构乱七八糟的吧?
4),至此,编译运行,模拟器是一片空白的!因为Empty Application模版就是Empty,里面甚至连一个Window都木有。因此,我们需要添加一个 Window。右击 Supporting Files文件夹,选择 New File->User Interface->Window:
输入名称:MainWindow
5),为了让 AppDelegate 与 Window 关联起来,我们还需要在MainWindow.xib中创建一个Object对象。选中MainWindow.xib,向其中拖入一个 Object 对象:
添加完毕,效果如下:
6),然后我们修改该 Object 的Custom Class为 AppDelegate,这样它在 xib 中代表代码中的 AppDelegate了。
7),为了将 Window与App Delegate 关联起来,我们需要在 AppDelegate.h中的代码 window 属性前添加 IBOutlet 修饰符:
@property (strong, nonatomic) IBOutlet UIWindow *window;
8),选中MainWindow.xib,右击 AppDelegate,将Outlet window拖拽到其上方的 Window上,这样AppDelegate中的window就与真实的 Window 关联起来。
9),同样,我们还需要修改File's Owner的 Custom Class 为 UIApplication,使用与8)中同样的拖拽技巧,将 File's Owner的 delegate 与 App Delegate 关联起来。
10),至此准备工作完毕,不妨编译运行一下,模拟器依然一片空白,那是因为我们还没有在 Window 上添加 view,下面我们将来添加一个 view。
2,设置 OpenGL ES 运行环境
1),虽然 iOS 5在 GLKit 中提供了方便使用 OpenGL ES 的辅助 GLKView,但在这里,我们还是从零开始手工打造我们自己 GL ES view,从而更进一步了解在 iOS 上 OpenGL ES 是使用的。在Tutorial01目录中 New File,选择 User Interface->View作为模版,命名为 OpenGLView:
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 OpenGLView 的上面)添加匿名 category,并声明私有函数 setupLayer:
// 使用匿名 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;
函数 - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable; 在内部使用 drawable(在这里是 EAGLLayer)的相关信息(还记得在 setupLayer 时设置了drawableProperties的一些属性信息么?)作为参数调用了 glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); 后者 glRenderbufferStorage 指定存储在 renderbuffer 中图像的宽高以及颜色格式,并按照此规格为之分配存储空间。在这里,将使用我们在前面设置 eaglLayer 的颜色格式 RGBA8, 以及 eaglLayer 的宽高作为参数调用 glRenderbufferStorage。
7),创建 framebuffer object
framebuffer object 通常也被称之为 FBO,它相当于 buffer(color, depth, stencil)的管理者,三大buffer 可以附加到一个 FBO 上。我们是用 FBO 来在 off-screen buffer上进行渲染。下面,我们依然创建私有方法 setupFrameBuffer 来生成 frame buffer:
- (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 (GLclampf red, GLclampf green, GLclampf blue, GLclampfalpha) 用来设置清屏颜色,默认为黑色;glClear (GLbitfieldmask)用来指定要用清屏颜色来清除由mask指定的buffer,mask 可以是 GL_COLOR_BUFFER_BIT,GL_DEPTH_BUFFER_BIT和GL_STENCIL_BUFFER_BIT的自由组合。在这里我们只使用到 color buffer,所以清除的就是 clolor buffer。- (BOOL)presentRenderbuffer:(NSUInteger)target 是将指定 renderbuffer 呈现在屏幕上,在这里我们指定的是前面已经绑定为当前 renderbuffer 的那个,在 renderbuffer 可以被呈现之前,必须调用renderbufferStorage:fromDrawable:
为之分配存储空间。在前面设置 drawable 属性时,我们设置 kEAGLDrawablePropertyRetainedBacking 为FALSE,表示不想保持呈现的内容,因此在下一次呈现时,应用程序必须完全重绘一次。将该设置为 TRUE 对性能和资源影像较大,因此只有当renderbuffer需要保持其内容不变时,我们才设置 kEAGLDrawablePropertyRetainedBacking 为 TRUE。
三,进行渲染
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; @end
2,在 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,在其中添加函数:
- (void)layoutSubviews { [self setupLayer]; [self setupContext]; [self destoryRenderAndFrameBuffer]; [self setupRenderBuffer]; [self setupFrameBuffer]; [self render]; }
4,编译运行,小功告成:
5,如果你还没有保存你的代码,选择 File-Source Control->Commit, 提交你的代码到 git 中吧,时常提交代码是个好习惯。后续文章我们还将使用到在这里编写的代码。本文源代码可以在这里查看与下载:https://github.com/kesalin/OpenGLES
四,Refference