2012年6月24日 星期日

OpenGL ES 2.0 with GLKit Part 1 - 2

since: 2012/06/24
update: 2012/06/24


reference:
1.
Beginning OpenGL ES 2.0 with GLKit Part 1 | Ray Wenderlich
2. I touchs: OpenGL ES 2.0 with GLKit Part 1 - 1

GLKViewController

A. GLKViewController 介紹
      1. 說明:
          我們可以使用更容易的方式來完成之前所作的事, 使用: GLKViewController.
          先前, 之所以使用 GLKView, 是讓我們可以了解使用 GLKViewController
          背後的重點, 並節省了撰寫那些程式碼, 還可進一步自行撰寫你想要的
          額外功能.

      2. 開啟 AppDelegate.h 檔案, 修改如下:
....
//@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate>
//@update for set the GLKViewController's delegate to the current class
@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate, GLKViewControllerDelegate>
{
    //@add
    float _curRed;
    BOOL _increasing;
}
....
//@add

//@update: comment it
//- (void)render:(CADisplayLink *)displayLink;
....

      3. 開啟 AppDelegate.m 檔案, 修改如下:
....
// step 1
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //@add
    _increasing = YES;
    _curRed = 0.0;
       
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    //@add for GLKView
    //
    // step 1: Create a OpenGL context
    //
    // 1-1: An EAGLContext manages all of the information iOS needs to draw with OpenGL.
    // 1-2: And specify what version of the API you want to use.(Here, OpenGL ES 2.0)
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    // step 2: Create a GLKView
    //
    // This creates a new instance of a GLKView, and makes it as large as the entire window.
    GLKView *view = [[GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
   
    // step 3: Set the GLKView's context
    //
    // Tell GLKView to use the OpenGL context.
    view.context = context;
   
    // step 4: Set the GLKView's delegate
    //
    // 4-1: This sets the current class (AppDelegate) as the GLKView's delegate.
    // 4-2: This means whenever the view needs to be redrawn, it will call
    //      a method named glkView:drawInRect on whatever class you specify here.
    //      We will implement this inside the App Delegate shortly to contain
    //      some basic OpenGL commands to paint the screen red.
    view.delegate = self;
   
    //@add: for synchronize
    //@update: comment it
    /*
    view.enableSetNeedsDisplay = NO;
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
    [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    */
   
    // step 5: Add the GLKView as a subview
    //@update: comment it
    //[self.window addSubview:view];
   
   
    // step 6: for GLKViewController
    //
    // 6-1: Create a GLKViewController
    //
    // This creates a new instance of a GLKViewController programatically.
    //
    // In this case, it has no XIB associated.
    GLKViewController *viewController = [[GLKViewController alloc] initWithNibName:nil bundle:nil];
   
    // 6-2: Set the GLKViewController's view
    //
    // The root view of a GLKViewController should be a GLKView,
    // so we set it to the one we already created.
    viewController.view = view;
   
    // 6-3: Set the GLKViewController's delegate
    //
    // We set the current class (AppDelegate) as the delegate of the GLKViewController.
    //
    // This means that the GLKViewController will notify us each frame
    // so we can run game logic, or when the game pauses.
    viewController.delegate = self;
   
    // 6-4: Set the preferred FPS
    //
    // The GLKViewController will call your draw method a certain number of times
    // per second.

    //
    // The default value is 30 FPS. Apple's guidelines are to set this to whatever your app
    // can reliably support to the frame rate is consistent and doesn't seem to stutter.
    //
    // This app is very simple so can easily run at 60 FPS, so we set it to that.
    //
    // If you want to see the actual number of times the OS will attempt to call your
    // update/draw methods, check the read-only framesPerSecond property.
    viewController.preferredFramesPerSecond = 60;
   
    // 6-5: Set the rootViewController
    //
    // We want this view controller to be the first thing that shows up,
    // so we add it as the rootViewController of the window.
    //
    // We no longer need to add the view as a subview of the window manually,
    // because it's the root view of the GLKViewController.
    //
    // We no longer need the code to run the render loop and tell the GLView
    // to refresh each frame –
GLKViewController does that for us in the background!
    // So go ahead and comment out the render method as well.
    self.window.rootViewController = viewController;
   
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

....
// step 2
//@update: comment it

/*
- (void)render:(CADisplayLink *)displayLink
{
    GLKView *view = [self.window.subviews objectAtIndex:0];
    [view display];
}
*/

....
// step 3
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    //@add
    //@update: comment it
    /*
    if (_increasing) {
        _curRed += 0.01;
    } else {
        _curRed -= 0.01;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }
    */
   
    // specify the RGB and alpha (transparency) values to use when clearing the screen.
    //glClearColor(1.0, 0.0, 0.0, 1.0);
    //@update
    glClearColor(_curRed, 0.0, 0.0, 1.0);
   
    // actually perform the clearing.
    glClear(GL_COLOR_BUFFER_BIT);
}

....
// step 4
#pragma mark - GLKViewControllerDelegate
//@add for implement delegate method
- (void)glkViewControllerUpdate:(GLKViewController *)controller
{
    // property: timeSinceLastUpdate
    //
    // it guarantees the animation will always proceed at the same speed,
    // regardless of the frame rate.
    if (_increasing) {
        _curRed += 1.0 * controller.timeSinceLastUpdate;
    } else {
        _curRed -= 1.0 * controller.timeSinceLastUpdate;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }
}
....

      4. 編譯並執行:
          結果與之前相同

--------------------------------------------------------------------------------

B. GLKViewController 與 Storyboards
      1. 新增 GLKViewController 子類別檔案:
          Xcode > File > New > New File...
          iOS > Cocoa Touch > Objective-C class > Next

          Class: HelloGLKitViewController
          Subclass of: GLKViewController (如果無法選取到, 就自行輸入)
          > Next > Create

****************************************************

      2. 開啟 HelloGLKitViewController.m 檔案, 修改如下:
....
// step 1:
@interface HelloGLKitViewController ()
{
    //@add
    float _curRed;
    BOOL _increasing;
}

//@add
@property (strong, nonatomic) EAGLContext *context;

@end

@implementation HelloGLKitViewController

//@add
@synthesize context = _context;

....
// step 2:
- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //@add   
    // 1. create an OpenGL ES 2.0 context
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }
   
    // 2. Our root view is a GLKView, so we cast it as one.
    //
    // We then set its context to the OpenGL context we just created.
    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
   
    // 3. Note
    //
    // We don't have to set the view controller as the view's delegate
    // – GLKViewController does this automatically behind the scenes.
}

....
// step 3:
- (void)viewDidUnload
{
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    //@add
    //
    // we check to see if the current context is our context
    if ([EAGLContext currentContext] == self.context) {
        // set it to nil if so
        [EAGLContext setCurrentContext:nil];
    }
   
    // clear out your reference to it.
    self.context = nil;
}

....
// step 4:
#pragma mark - GLKViewDelegate
//@add
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
}

....
// step 5:
#pragma mark - GLKViewControllerDelegate
//@add
- (void)update
{
    if (_increasing) {
        _curRed += 1.0 * self.timeSinceLastUpdate;
    } else {
        _curRed -= 1.0 * self.timeSinceLastUpdate;
    }
    if (_curRed >= 1.0) {
        _curRed = 1.0;
        _increasing = NO;
    }
    if (_curRed <= 0.0) {
        _curRed = 0.0;
        _increasing = YES;
    }
}
....

****************************************************

      3. 新增 Storyboard 檔案:
          Xcode > File > New > New File...
          iOS > User Interface > Storyboard > Next
          Device Family: iPhone
          Save As: MainStoryboard.storyboard
          > Create

****************************************************

      4. 開啟 MainStoryboard.storyboard 檔案, 修改如下:
          a. 從右方的 Objects panel 拖拉一個 View Controller 進來:

          b. 先選取 UI 上的 View Controller, 再點選右上方的 Identity Inspector
              將 Class 由 UIViewController 改選為: HelloGLKitViewController.

          c. 同樣地, 選取 UI 上的 View Controller 裡的 View, 再點選右上方的
              Identity Inspector, 將 Class 由 UIView 改選為: GLKView.

      5. 開啟 HelloGLKit-Info.plist 檔案, 修改如下:
          a. 在下方空白的地方按下: control + 滑鼠左鍵 > Add Row

          b. 選擇並設定:
              Key: Main storyboard file base name
              Type: String
              Value: MainStoryboard

****************************************************

       6. 移除不再使用的程式碼:
           a. 開啟 AppDelegate.h 檔案, 修改如下:  
#import <UIKit/UIKit.h>
//@add
//#import <GLKit/GLKit.h>
//#import <QuartzCore/QuartzCore.h> // used for CADisplayLink

@interface AppDelegate : UIResponder <UIApplicationDelegate>
//@update for set as GLKView's delegate
//@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate>
//@update for set the GLKViewController's delegate to the current class
//@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate, GLKViewControllerDelegate>
{
    //@add
    /*
    float _curRed;
    BOOL _increasing;
    */
}

@property (strong, nonatomic) UIWindow *window;

//@add
//@update: comment it
//- (void)render:(CADisplayLink *)displayLink;

@end

           b. 開啟 AppDelegate.m 檔案, 修改如下:
....
// step 1:
#pragma mark - GLKViewDelegate
/*

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
....
}
*/

....
// step 2:
#pragma mark - GLKViewControllerDelegate
/*

- (void)glkViewControllerUpdate:(GLKViewController *)controller
{
....
}
*/

....

// step 3:
/*
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
....
}
*/
//@update
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    return YES;
}

****************************************************

       7. 編譯並執行:
           結果與之前相同.
           備註: 到目前為止的專案架構, 已經與你選擇用 OpenGL Game 樣板來新增
                     專案非常類似了(除了有一大堆其它你可以刪除的程式碼), 以後就可以
                     選擇 OpenGL Game 樣板來建立新專案了, 這可以省下一點時間. 

--------------------------------------------------------------------------------

C. GLKViewController 與暫停
      1. 開 HelloGLKitViewController.m 檔案, 修改如下:
....
//@add
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.paused = !self.paused;
}
....

      2. 編譯並執行:
          任何時間, 輕觸螢幕, 便會停止動畫或繼續執行動畫. 在場景後面的機制,
          是 GLKViewController 停止 / 繼續 呼叫 glkView:drawInRect:update 方法.

      3. 除此之外, GLKViewController 有一個 pauseOnWillResignActive 屬性,
          預設值為 YES, 代表, 當使用者按下 home 按鈕或者接受到一個中止的訊號,
          例如有某人來電, 你的遊戲會自動地暫停. 類似地, 它也有一個
          resumeOnDidBecomeActive 屬性, 預設值為 YES, 代表當使用者回到你的 App ,
          遊戲就會自動繼續, 真是方便.    

      4. GLKViewController 的其它 "時間資訊" 屬性:
           a. timeSinceLastDraw
               從最近一次呼叫 draw 方法, 到目前的時間. 這可能會與 timeSinceLastUpdate
               不同, 因為執行 update 方法需要花一些時間.  

           b. timeSinceFirstResume 
               從 GLKViewController 第一次重新開始發送更新訊息, 到目前的時間.
               如果你的 GLKViewController 是第一個會展現的東西, 這通常代表從你的
               App 啟動(launched) 到目前的時間.

           c. timeSinceLastResume
               最近一次 GLKViewController 重新開始發送更新訊息, 到目前的時間.
               這通常代表從你的 App 最近一次從暫停恢復執行(unpaused) 到目前的時間.

      5. 開啟 HelloGLKitViewController.m 檔案, 修改如下:
....
//@add
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //@add for time info
    NSLog(@"timeSinceLastUpdate: %f", self.timeSinceLastUpdate);
    NSLog(@"timeSinceLastDraw: %f", self.timeSinceLastDraw);
    NSLog(@"timeSinceFirstResume: %f", self.timeSinceFirstResume);
    NSLog(@"timeSinceLastResume: %f", self.timeSinceLastResume);
   
    self.paused = !self.paused;
}
....

      6. 編譯並執行:
          按 home 鍵 > 回復 > 輕觸螢幕: 

HelloGLKit[1421:fb03] timeSinceLastUpdate: 0.033285
HelloGLKit[1421:fb03] timeSinceLastDraw: 0.033285
HelloGLKit[1421:fb03] timeSinceFirstResume: 7.780801
HelloGLKit[1421:fb03] timeSinceLastResume: 0.991113

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。