文章目錄
- 一,什麼是 OpenGL ES?
- 二,在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
OpenGL ES 2.0 for iPhone
OpenGL ES 2.0 Programming Guide