2014年8月5日 星期二

[紀錄] GLES Sample 1 - A Simple Tutorial to Use OpenGL ES 2.0 on iOS7


前一陣子剛好要接觸OpenGL ES,可是身邊都沒人用過,所以查了很多資料才把功能做完。
最近剛好比較有閒,索性就整理一下這方面的資料,順便記錄起來。

我使用的環境是 iOS 7 + Xcode 5.0.2

這裡也附上我參考的資料:基廉列克雜記本-OpenGL基本實作(一)
// 我覺得這個Blog有很多關於OpenGL ES的sample code,我有許多功能都是參考這個Blog完成的,頗推薦大家去看看。


下面就介紹我實作的tutorial:

1. 建立一個Project,我使用的是Empty Application。
2. 新增新的File,選擇Object-C class。
    // 除了建立Project時自動產生的AppDelegate,我另外新增了四個file:OpenGLSample為UIView的class,而OpenGLSampleViewController為UIViewController的class。






3. 新增Frameworks:OpenGLES和QuartzCore
    // 這個步驟很重要喔! 在Build Phase => Link Binary With Libraries中。

4. OpenGLSample.h

#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>

@interface OpenGLSample : UIView {
    CAEAGLLayer *eaglLayer;
    EAGLContext *context;
    
    GLuint renderBuffer, frameBuffer;
}

// OpenGL initialize
- (void)setupLayer;
- (void)setupContext;

@end

Core Animation是iOS繪圖的基礎,每個CALayer都可設定自己的繪圖動作,最後由core animation合成並顯示在螢幕上。使用OpenGL ES需使用一個特殊的Core Animation Layer:CAEAGLLayer。而每個Process都有自己的EAGLContext,使得每個OpenGL ES Process不會互相干擾。Framebuffers則是一個容器,包含attach的紋理以及renderbuffer。

因此簡單描述就是OpenGL ES命令會送到render context去讀取或改變render context的狀態,接著送往framebuffer,透過CALayer顯示在螢幕上

5. OpenGLSample.m

#import "OpenGLSample.h"

@implementation OpenGLSample

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        [self setupLayer];
        [self setupContext];
    }
    return self;
}

// Override the class method to return the CAEAGLLayer, as opposed to the normal CALayer
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (void)setupLayer {
    eaglLayer = (CAEAGLLayer *) self.layer;
    // the default CALayer is transparent, so set to opaque be seen by user.
    eaglLayer.opaque = YES;
    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}

- (void)setupContext {
    // set the api is OpenGLES 2.0
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if(!context) {
        NSLog(@"Failed to initialize OpenGLES 2.0 context.");
        exit(1);
    }
    if (![EAGLContext setCurrentContext:context]) {
        context = nil;
        NSLog(@"Failed to set current OpenGL context.");
        exit(1);
    }
}

// This function will be called when triger addSubview or reset the frame of this view.
- (void)layoutSubviews {
    [self destroyRenderAndFrameBuffer];
    [self setupRenderAndFrameBuffer];
    [self render];
}

- (void)destroyRenderAndFrameBuffer {   
    if (frameBuffer) {
        glDeleteFramebuffers(1, &frameBuffer);
        frameBuffer = 0;
    }
 
    if (renderBuffer) {
        glDeleteRenderbuffers(1, &renderBuffer);
        renderBuffer = 0;
    }
}

- (void)setupRenderAndFrameBuffer {
    // setup RenderBuffer
    glGenRenderbuffers(1, &renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];
    
    // setup FrameBuffer
    glGenFramebuffers(1, &frameBuffer);
    glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
    
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Failure with framebuffer generation");
        exit(1);
    }
}

- (void)render {
    glClearColor(1.0, 0.0, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

@end

其中要特別說明的是layoutSubviews這個function,在call addSubview或者改變frame或UIView的大小時會觸發這個事件。我們在觸發layoutSubviews事件後才call render function去畫一個桃紅色的正方形。

OpenGL ES總共有三種不同用途的buffer,分別是color buffer,depth buffer,stencil buffer。而render buffer就是color buffer。glGenRenderbuffers 會幫render buffer申請一個id,且此id不會為0。glBindRenderbuffer 會將指定id的render buffer設置為當前使用的render buffer並做初始化。

frame buffer是三個不同用途buffer的管理者,這三種不同用途的buffer都可以attach到frame buffer上。

當然不是改完上面就結束拉! 底下附上其他程式碼。

6. OpenGLSampleAppDelegate.h

#import <UIKit/UIKit.h>

@interface OpenGLSampleAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@end

7. OpenGLSampleAppDelegate.m

#import "OpenGLSampleAppDelegate.h"
#import "OpenGLSampleViewController.h"

@implementation OpenGLSampleAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch
    OpenGLSampleViewController *glViewController = [[OpenGLSampleViewController alloc] init];
    self.window.rootViewController = glViewController;
    
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

@end

上面主要增加的部份是設定rootViewController為OpenGLSampleViewController。
而OpenGLSampleViewController的程式碼請看下方。

8. OpenGLSampleViewController.h

#import <UIKit/UIKit.h>
#import "OpenGLSample.h"

@interface OpenGLSampleViewController : UIViewController {
    OpenGLSample *glView;
}

@end

9. OpenGLSampleViewController.m

#import "OpenGLSampleViewController.h"

@interface OpenGLSampleViewController ()

@end

@implementation OpenGLSampleViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    CGFloat frameWidth = self.view.frame.size.width;
    CGFloat frameHeight = self.view.frame.size.height;
    
    glView = [[OpenGLSample alloc] initWithFrame:CGRectMake(frameWidth/2-50, frameHeight/2-50, 100, 100)];
    [self.view addSubview:glView];
    
    self.view.backgroundColor = [UIColor blackColor];
}

- (void)didReceiveMemoryWarning 
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

用addSubview將glView加在self.view上,view的底色設成黑色,這樣就可以看到glView為長寬皆100的桃紅色方形。



沒有留言:

張貼留言