[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

OpenGL ES 2.0 for iPhone

OpenGL ES 2.0 Programming Guide


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM