2012年3月31日 星期六

OpenGL ES 入門: 二. 簡單繪圖之二

since: 2012/03/31
update: 2012/03/31

reference:
1. 原文:
iPhone Development: OpenGL ES From the Ground Up, Part 2: A Look at Simple Drawing

2. 翻譯:
從零開始學習OpenGL ES之二 – 簡單繪圖概述


3. DirectX、OpenGL及各種圖型函式庫

簡單繪圖之二: 畫三角形渲染頻率

A. 畫三角形
     1. 開啓 ViewController.m 檔案, 修改如下
....
//@add for <GLViewDelegate> method
- (void)drawView:(GLView *)view
{
    NSLog(@"step 07. ViewController => drawView:");
   
    // Draw code here
    /*
    glClearColor(0.5f, 0.5f, 0.5f, 1); // 將顏色定義為灰色
    glClear(GL_COLOR_BUFFER_BIT); // 執行清除操作
    */
   
    /*************************************************/
    /* 畫三角形 */
   
    // 建立三個頂點
    //
    // 三個頂點的 z 值是一樣的,其值(-3.0)是處於原點 "之後" 的
    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
   
    // 建立三角形
    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);
   
    // 將 OpenGL 中的矩陣設為單位矩陣.
    // 所謂的單位矩陣是矩陣乘法下的單位元素,
    // 任何矩陣乘上單位矩陣都還是等於自己
    //
    // 所以 glLoadIdentity() 的作用是不希望之前的矩陣資料,
    // 殘留到現在的運算.
    glLoadIdentity();
   
    // 告訴 OpenGL 所有的繪製工作是在一個灰色背景上進行.
    //
    // 設定: R, G, B, alpha(1.0, 代表完全不透明)
    // 白色: (1.0, 1.0, 1.0, 1.0)
    // 黑色: (0.0, 0.0, 0.0, 1.0)
    glClearColor(0.7, 0.7, 0.7, 1.0);
   
    // 通知 OpenGL 清除以前的一切圖形並將其設為 clear 顏色.
    //
    // color buffer - 顏色緩存, 保存當前 frame 各像素的顏色, 基本上就是你在屏幕上
    //                          看到的.

    //
    // depth buffer - 深度緩存(z-buffer), 保存每個潛在像素離觀察者距離的信息,
    //                          使用此信息可以確定一個像素是否需要被繪製出來
    //
    // 記住: 在繪製一個 frame 之前, 必須清除這兩個緩存以保證不會和以前的內容混雜.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    // 啟動 OpenGL 的 vertex arrays(頂點陣列)的特性.
    // 作為基本準則, 我們可以先啟動最後再關閉我們使用的功能
    //
    glEnableClientState(GL_VERTEX_ARRAY);
   
    // 設置繪圖時所需的顏色
    // 此行代碼將繪圖顏色設為鮮艷的紅色
    //
    // 現在, 直到下次調用 glColor4f() 前所有的圖形都是以紅色繪製
    // 有一些例外的情況, 例如繪製紋理形狀時, 但基本上, 這樣設定顏色可以使顏色保持.
    glColor4f(1.0, 0.0, 0.0, 1.0);
   
    // 由於我們使用頂點陣列, 我們必須通知 OpenGL 頂點的陣列在什麼地方.
    //
    // 頂點陣列只是一個 GLfloat 的 C 陣列, 每三個值代表一個頂點.
    // 我們創建了 Triangle3D 物體, 但在內存中, 它完全等同於 9 個連續的 GLfloat,
    // 所以我們可以傳遞此三角形物體的位址.
    //
    // glVertexPointer()
    // 第一個參數, 指示了多少個 GLfloat 代表一個頂點
    // 第二個參數, 告訴 OpenGL 頂點是由 GLfloat 構成
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
   
    // 通知 OpenGL 通過剛才提交的頂點陣列來繪製三角形
    //
    // 第一個參數, 告訴 OpenGL 繪製什麼.
    // 儘管 OpenGL ES 不支持繪製三角形之外的四邊形或其他多邊形, 但它仍然支持
    // 一些其他繪圖模式,
如繪製點, 線, 線迴路, 三角形條和三角形扇.
    glDrawArrays(GL_TRIANGLES, 0, 9);
   
    // 最後, 我們要禁止先前啟動了的特性以保證不會被其他地方的代碼弄混
    glDisableClientState(GL_VERTEX_ARRAY);
}
....

     2. 編譯並執行

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

B. 渲染頻率
      1. 開啓 GLView.h 檔案, 修改如下:
....
@interface GLView : UIView
{
....
    //@add for Rendering Frequency
    NSTimer *animationTimer;               // 計時器
    NSTimeInterval animationInterval; // 間隔多久執行(秒)
    NSNumber *currentRunCount;        // 目前執行次數
    int totalRunTime;                                 // 總執行時間(秒)
}
....
//@add for Rendering Frequency
@property (nonatomic, assign) NSTimer *animationTimer;
@property (nonatomic, assign) NSTimeInterval animationInterval;
@property (nonatomic, strong) NSNumber *currentRunCount;
@property (assign) int totalRunTime;
....
//@add for Rendering Frequency
- (void)startAnimation;
- (void)stopAnimation;
....

      2. 開啓 GLView.m 檔案, 修改如下:
....
//@add for Rendering Frequency
@synthesize animationTimer = _animationTimer;
@synthesize animationInterval = _animationInterval;
@synthesize currentRunCount = _currentRunCount;
@synthesize totalRunTime = _totalRunTime;
....
//@add for Rendering Frequency
-(NSNumber *)currentRunCount
{
    if (!_currentRunCount) {
        _currentRunCount = [[NSNumber alloc] initWithInt:0];
    }
    return _currentRunCount;
}

//@add for Rendering Frequency
- (void)setAnimationTimer:(NSTimer *)newTimer
{
    [_animationTimer invalidate];
    _animationTimer = newTimer;
}

//@add for Rendering Frequency
- (void)setAnimationInterval:(NSTimeInterval)interval
{
    _animationInterval = interval;
   
    if (self.animationTimer)
    {
        [self stopAnimation];
        [self startAnimation];
    }
}

//@add for Rendering Frequency
- (void)startAnimation
{
    [self.animationTimer invalidate];
   
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}

//@add for Rendering Frequency
- (void)stopAnimation
{
    [self.animationTimer invalidate];
    self.animationTimer = nil;
}
....
//@add: overide
- (void)layoutSubviews
{
    NSLog(@"step 02. GLView => layoutSubviews");
   
    [EAGLContext setCurrentContext:self.glContext];
   
    //@add for Rendering Frequency
    [self setAnimationInterval: 1.0 / kRenderingFrequency];
    self.totalRunTime = 10; // 秒
   
    [self destroyFrameBuffer];
   
    [self createFrameBuffer];
   
    //[self drawView];
    //@update for Rendering Frequency
    [self startAnimation];
}

//@add
- (void)drawView
{
    NSLog(@"step 06. GLView => drawView");
       
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFrameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderBuffer);
   
    // GO: step 07. ViewController => drawView:
    [self.delegate drawView:self];
   
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER_OES];
   
    //@add for Rendering Frequency
    int intCount = [self.currentRunCount intValue] + 1;
    self.currentRunCount = [NSNumber numberWithInt:intCount];
   
    if (intCount >= self.totalRunTime * kRenderingFrequency) {

        [self stopAnimation];
    }
}

//@add
- (void)dealloc
{
    //@add for Rendering Frequency
    [self stopAnimation];
   
    if ([EAGLContext currentContext] == self.glContext) {
        [EAGLContext setCurrentContext:nil];
    }
}
....

      3. 開啓 ViewController.m 檔案, 修改如下:
....
//@add for <GLViewDelegate> method
- (void)drawView:(GLView *)view
{
    NSLog(@"step 07. ViewController => drawView:");
   
    // Draw code here
    /*************************************************/
    /* 畫三角形 */
   
    //@add for Rendering Frequency
    static GLfloat rotation = 0.0;
   
    // 建立三個頂點
    //
    // 三個頂點的 z 值是一樣的,其值(-3.0)是處於原點 "之後" 的
    Vertex3D    vertex1 = Vertex3DMake(0.0, 1.0, -3.0);
    Vertex3D    vertex2 = Vertex3DMake(1.0, 0.0, -3.0);
    Vertex3D    vertex3 = Vertex3DMake(-1.0, 0.0, -3.0);
   
    // 建立三角形
    Triangle3D  triangle = Triangle3DMake(vertex1, vertex2, vertex3);
   
    // 將 OpenGL 中的矩陣設為單位矩陣.
    // 所謂的單位矩陣是矩陣乘法下的單位元素,
    // 任何矩陣乘上單位矩陣都還是等於自己
    //
    // 所以 glLoadIdentity() 的作用是不希望之前的矩陣資料,
    // 殘留到現在的運算.
    glLoadIdentity();
   
    //@add for Rendering Frequency
    glRotatef(rotation, 0.0, 0.0, 1.0);
   
    // 告訴 OpenGL 所有的繪製工作是在一個灰色背景上進行.
    //
    // 設定: R, G, B, alpha(1.0, 代表完全不透明)
    // 白色: (1.0, 1.0, 1.0, 1.0)
    // 黑色: (0.0, 0.0, 0.0, 1.0)
    glClearColor(0.7, 0.7, 0.7, 1.0);
   
    // 通知 OpenGL 清除以前的一切圖形並將其設為 clear 顏色.
    //
    // color buffer - 顏色緩存, 保存當前 frame 各像素的顏色, 基本上就是你在屏幕上
    //                          看到的.

    //
    // depth buffer - 深度緩存(z-buffer), 保存每個潛在像素離觀察者距離的信息,
    //                           使用此信息可以確定一個像素是否需要被繪製出來
    //
    // 記住: 在繪製一個 frame 之前, 必須清除這兩個緩存以保證不會和以前的內容混雜.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    // 啟動 OpenGL 的 vertex arrays(頂點陣列)的特性.
    // 作為基本準則, 我們可以先啟動最後再關閉我們使用的功能
    //
    glEnableClientState(GL_VERTEX_ARRAY);
   
    // 設置繪圖時所需的顏色
    // 此行代碼將繪圖顏色設為鮮艷的紅色
    //
    // 現在, 直到下次調用 glColor4f() 前所有的圖形都是以紅色繪製
    // 有一些例外的情況, 例如繪製紋理形狀時, 但基本上, 這樣設定顏色可以使顏色保持.
    glColor4f(1.0, 0.0, 0.0, 1.0);
   
    // 由於我們使用頂點陣列, 我們必須通知 OpenGL 頂點的陣列在什麼地方.
    //
    // 頂點陣列只是一個 GLfloat 的 C 陣列, 每三個值代表一個頂點.
    // 我們創建了 Triangle3D 物體, 但在內存中, 它完全等同於 9 個連續的 GLfloat,
    // 所以我們可以傳遞此三角形物體的位址.
    //
    // glVertexPointer()
    // 第一個參數, 指示了多少個 GLfloat 代表一個頂點
    // 第二個參數, 告訴 OpenGL 頂點是由 GLfloat 構成
    glVertexPointer(3, GL_FLOAT, 0, &triangle);
   
    // 通知 OpenGL 通過剛才提交的頂點陣列來繪製三角形
    //
    // 第一個參數, 告訴 OpenGL 繪製什麼.
    // 儘管 OpenGL ES 不支持繪製三角形之外的四邊形或其他多邊形, 但它仍然支持
    // 一些其他繪圖模式,
如繪製點, 線, 線迴路, 三角形條和三角形扇.
    glDrawArrays(GL_TRIANGLES, 0, 9);
   
    // 最後, 我們要禁止先前啟動了的特性以保證不會被其他地方的代碼弄混
    glDisableClientState(GL_VERTEX_ARRAY);
   
    //@add for Rendering Frequency
    rotation += 0.5;
}
....

      4. 編譯並執行
          三角形將沿着原點緩緩轉動


2012年3月30日 星期五

OpenGL ES 入門: 二. 簡單繪圖之一

since: 2012/03/30
update: 2012/03/30

reference:
1. 原文:
iPhone Development: OpenGL ES From the Ground Up, Part 2: A Look at Simple Drawing

2. 翻譯:
從零開始學習OpenGL ES之二 – 簡單繪圖概述


3. Developer's Note: Protocol & Delegate

簡單繪圖之一: 繪圖架構

A. 說明
      1. 為了儘量接近原始文章的架構, 因此需要使用到 Delegate (委任) 的方式.

      2. 先不使用 OpenGL ES 2, 而使用 OpenGL ES 1.

      3. 目前主要有三個類別檔案: GLView, ViewControllerAppDelegate,
           功能調整如下:

            a. GLView:
                (1). 將定義一個 protocol: <GLViewDelegate>, 並新增一個將實作此 protocol
                       的變數 delegate

                (2). <GLViewDelegate> protocol 宣告了二個需要被實作的方法:
                       // 用來設置 view
                       - (void)setupView:(GLView *)view;

                       // 這是最主要繪圖的地方
                       - (void)drawView:(GLView *)view;

                (3). GLView 類別新增了以下的方法:
                       // 於繪圖前, 清除緩衝區內容
                       - (void)destroyFrameBuffer;

                       // 建立緩衝區內容, 並呼叫 delegate 的 setupView:
                       - (BOOL)createFrameBuffer;

                       // 呼叫 delegate 的 drawView: , 並實際將內容畫到螢幕上
                       - (void)drawView;

                       說明: 以上三個方法將藉由覆寫本身(GLView)的 layoutSubviews方法,
                                  而依序自動被呼叫.

            b. ViewController:
                (1). 宣告 ViewController 類別, 必須遵循 <GLViewDelegate> protocol 的規範.

                (2). ViewController 必須實作 <GLViewDelegate> protocol 所宣告的二個方法:
                       // 用來設置 view
                       - (void)setupView:(GLView *)view;   

                       // 這是最主要繪圖的地方
                       - (void)drawView:(GLView *)view;

           c. AppDelegate:
               在 application:didFinishLaunchingWithOptions: 方法內, 將 GLView 實體
               的 delegate 指向 ViewController 實體.

               self.glView.delegate = self.viewController;


      4. 整個繪圖架構流程大致如下:
        
         步驟 01. AppDelegate => didFinishLaunchingWithOptions

         步驟 02. GLView => layoutSubviews

                步驟 03. GLView => destroyFrameBuffer

                步驟 04. GLView => createFrameBuffer

                        步驟 05. ViewController => setupView:

                步驟 06. GLView => drawView

                        步驟 07. ViewController => drawView:

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

B. 開啓 GLView.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
#import "OpenGLESCommon.h"
#import "ConstantsAndMacros.h"

//@add for protocol
@protocol GLViewDelegate;

@interface GLView : UIView
{
    //@add
    EAGLContext *glContext;
   
    //@add
    GLint backingWidth;
    GLint backingHeight;
    GLuint viewRenderBuffer;
    GLuint viewFrameBuffer;
    GLuint depthRenderBuffer;
   
    id<GLViewDelegate> delegate;
}

//@add
@property (nonatomic, strong) EAGLContext *glContext;
@property (assign) /* weak ref */ id <GLViewDelegate> delegate;

//@add
- (void)destroyFrameBuffer;
- (BOOL)createFrameBuffer;
- (void)drawView;

@end

//@add for protocol
@protocol GLViewDelegate

- (void)setupView:(GLView *)view;
- (void)drawView:(GLView *)view;

@end

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

C. 開啓 GLView.m 檔案, 修改如下:
#import "GLView.h"

@implementation GLView

//@add
@synthesize glContext = _glContext;
@synthesize delegate = _delegate;

//@add
- (EAGLContext *)glContext
{
    if (_glContext == nil) {
        //_glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

        //@update
        _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
    }
   
    return _glContext;
}
....
//@update: comment it
/*
- (id)initWithFrame:(CGRect)frame
{
....
}
*/

....
//@add: overide
- (void)layoutSubviews
{
    NSLog(@"step 02. GLView => layoutSubviews");
   
    [EAGLContext setCurrentContext:self.glContext];
   
    [self destroyFrameBuffer];
   
    [self createFrameBuffer];
   
    [self drawView];
}

//@add
- (void)destroyFrameBuffer
{
    NSLog(@"step 03. GLView => destroyFrameBuffer");
   
    glDeleteFramebuffersOES(1, &viewFrameBuffer);
    viewFrameBuffer = 0;
   
    glDeleteRenderbuffersOES(1, &viewRenderBuffer);
    viewRenderBuffer = 0;
   
    if(depthRenderBuffer)
    {
        glDeleteRenderbuffersOES(1, &depthRenderBuffer);
        depthRenderBuffer = 0;
    }
}

//@add
- (BOOL)createFrameBuffer
{
    NSLog(@"step 04. GLView => createFrameBuffer");

    // 從 UIView 取得 layer (CALayer), 向下轉型成 CAEAGLLayer.
    // 在此為安全的, 因為 layerClass 方法已被覆寫)
    CAEAGLLayer *eaglLayer = (CAEAGLLayer*) super.layer;
   
    // 直接使用 OpenGL 設定不透明度
    eaglLayer.opaque = YES;
   
    // 定義: 渲染(render)與幅(frame)緩衝區(buffer)
    glGenFramebuffersOES(1, &viewFrameBuffer);
    glGenRenderbuffersOES(1, &viewRenderBuffer);
   
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFrameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderBuffer);
   
    [self.glContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:eaglLayer];
   
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderBuffer);
   
    /**** 以下此段非必要 ****/
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);

    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
   
    if (USE_DEPTH_BUFFER)
    {
        glGenRenderbuffersOES(1, &depthRenderBuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderBuffer);

        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);

        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderBuffer);

    }
   
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES)
    {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));

        return NO;
    }
    /**** 以上此段非必要 ****/
   
    // GO: step 05. ViewController => setupView:
    [self.delegate setupView:self];
   
    return YES;
}

//@add
- (void)drawView
{
    NSLog(@"step 06. GLView => drawView");
       
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFrameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderBuffer);
   
    // GO: step 07. ViewController => drawView:
    [self.delegate drawView:self];
   
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER_OES];
}
....

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

D. 開啓 ViewController.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import "GLView.h"

//@interface ViewController : UIViewController
//@update
@interface ViewController : UIViewController <GLViewDelegate>
{
}
@end

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

E. 開啓 ViewController.m 檔案, 修改如下:
....
//@add for <GLViewDelegate> method
-(void)setupView:(GLView *)view
{
    NSLog(@"step 05. ViewController => setupView:");
   
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0;
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
   
    CGRect rect = view.bounds;
     glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
   
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    glLoadIdentity();
}

//@add for <GLViewDelegate> method
- (void)drawView:(GLView *)view
{
    NSLog(@"step 07. ViewController => drawView:");

    // Draw code here
    glClearColor(0.5f, 0.5f, 0.5f, 1); // 將顏色定義為灰色
    glClear(GL_COLOR_BUFFER_BIT); // 執行清除操作
}
....
//@update: comment it
/*
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        // Custom initialization
    }
    return self;
}
*/
....

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

F. 開啓 AppDelegate.m 檔案, 修改如下:
....
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    /*
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    */
    //@update
   
    NSLog(@"step 01. AppDelegate => didFinishLaunchingWithOptions");
   
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
   
    self.window = [[UIWindow alloc] initWithFrame:screenBounds];
    self.viewController = [[ViewController alloc] init];
    self.glView = [[GLView alloc] initWithFrame:screenBounds];
   
    self.window.rootViewController = viewController;
    self.viewController.view = self.glView;
   
    //@add for delegate
    self.glView.delegate = self.viewController;
   
    [self.window addSubview:self.viewController.view];
    [self.window makeKeyAndVisible];

    return YES;
}
....

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

G. 編譯並執行

OpenGLESBegin[3779:fb03]
step 01. AppDelegate => didFinishLaunchingWithOptions
OpenGLESBegin[3779:fb03] step 02. GLView => layoutSubviews
OpenGLESBegin[3779:fb03] step 03. GLView => destroyFrameBuffer
OpenGLESBegin[3779:fb03] step 04. GLView => createFrameBuffer
OpenGLESBegin[3779:fb03] step 05. ViewController => setupView:
OpenGLESBegin[3779:fb03] step 06. GLView => drawView
OpenGLESBegin[3779:fb03] step 07. ViewController => drawView:

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

H. 備註
      1. 很多 code 原本應該是由 template 產生的, 但是找不到原始可用的 template,
         所以變成自行 coding, 因此缺少了許多的說明; 目前先將重心放在畫圖本身.

      2. 以後畫圖的 code, 幾乎都是寫在 ViewController.m 裡的 drawView: 內.
//@add for <GLViewDelegate> method
- (void)drawView:(GLView *)view
{
    NSLog(@"step 07. ViewController => drawView:");
   
    // Draw code here
    ....
}

2012年3月29日 星期四

OpenGL ES 入門: 一. 基本概念

since: 2012/03/29
update: 2012/04/08

reference:
1. 原文:
    iPhone Development: OpenGL ES from the Ground Up: Table of Contents
    (iPhone Development: OpenGL ES From the Ground Up, Part 1: Basic Concepts)

2. 翻譯:
從零開始學習OpenGL ES之一 – 基本概念

3. I touchs: OpenGL ES 小試

基本概念

A. 說明
      1. 由於原文 / 譯文的 Empty OpenGL Xcode project template 已無法下載,
          因此主要參考譯文的版本來重新建立專案.

      2. 本文章著重在實作的部分, 相關的概念說明, 請參考原文 / 譯文的內容.

      3. 原本嘗試使用 OpenGL Game Project 來實作, 發現並沒有很簡潔, 因此決定
          採用 Empty Application Project.

      4. 本專案採用 Xcode 4.3.2, iOS SDK 5.1.

      5. 由於版本的差異與相關參考資料的不足, 本系列文章的實驗性質較重, 可能會
          更改較多次, 或者無法全部實作.

           (update: 2012/03/30)
      6. 有找到一個範例檔, 可以參考: Empty.OpenGL.ES.Application.zip
           (請直接點選 "聯通下載" 或點 "聯通下載右方箭頭" 出現的 "電信下載")

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

B. 相關詞彙
      1. vertex: 頂點, 代表三維空間中的一個點.
          說明: OpenGL ES 只支援三角形.

      2. winding: 卷繞, 表示頂點繪製的次序.
          說明: a. 預設情況下, OpenGL 的三角形只有一個可見面(front face: 前面), 不被
                         繪製的一面稱為 backface(背面), 確認可見面, 才能使OpenGL只做一半
                         的計算.

                     b. 預設情況下, 以反時針次序繪製頂點的構成的面是 frontface. OpenGL
                         使用 Backface Culling(隱面消除)的技術來避免繪製視窗中多邊形的
                         不可見面.

      3. render: 渲染, 描繪, 繪圖

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

C. 新增專案
       Xcode > File > New > Project... > iOS > Application >
       Empty Application > Next
       Product Name: OpenGLESBegin
       Company Identifier: com.blogspot
       Device Family: iPhone
       Use Automatic Reference Counting: checked
       > Next > Create

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

D. 新增 Framework
     新增以下的 Framework:
     OpenGLES
     
- Is used for visualizing 2D and 3D data.

     QuartzCore

     - Add 2D graphics rendering support.

     GLKit
     - P
rovides libraries of commonly needed functions and classes to reduce the effort
       needed to create a new OpenGL ES 2.0 application or the effort required to port
       an existing OpenGL ES 1.1 application to OpenGL ES 2.0.


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

E. 新增 UIViewController 子類別
   1. 說明: view controller 在此專案裡是非必要的, 只是不想在將來看到以下的訊息,
                 所以就把它加進來了.

                 Application windows are expected to have a root view controller
                 at the end of application launch.

     
   2. Xcode > File > New > File...
       > iOS > Cocoa Touch > Objective-C class > Next
       Class: ViewController
       Subclass of: UIViewController
       > Next > Create

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

F.
新增 UIView 子類別
      Xcode > File > New > File...
      > iOS > Cocoa Touch > Objective-C class > Next
      Class: GLView
      Subclass of: UIView
      > Next > Create

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

G. 開啓 GLView.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import <OpenGLES/EAGL.h>
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

@interface GLView : UIView
{
    //@add
    EAGLContext *glContext;
}

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

//@add
- (void)drawView;

@end

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

H. 開啓 GLView.m 檔案, 修改如下:
#import "GLView.h"

@implementation GLView

//@add
@synthesize glContext = _glContext;

//@add
- (EAGLContext *)glContext
{
    if (_glContext == nil) {
        _glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    }
   
    return _glContext;
}

//@add: 覆寫 layerClass 方法(類似 typeof), 回傳一個 OpenGL Layer 的類別
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        //@add
        //      從 UIView 取得 layer (CALayer), 向下轉型成 CAEAGLLayer.
        //      在此為安全的, 因為 layerClass 方法已被覆寫)
        CAEAGLLayer *eaglLayer = (CAEAGLLayer*) super.layer;
       
        // 直接使用 OpenGL 設定不透明度
        eaglLayer.opaque = YES;
       
        // 設定 Current Context
        [EAGLContext setCurrentContext:self.glContext];
       
        //@add: OpenGL 初始化
        // 定義:渲染(render)與幅(frame)緩衝區(buffer); GLuint 同義於 unsigned int
        GLuint framebuffer, renderbuffer;
       
        glGenFramebuffersOES(1, &framebuffer);
        glGenRenderbuffersOES(1, &renderbuffer);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, renderbuffer);
       
        [self.glContext renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:eaglLayer];
       
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER_OES, renderbuffer);
       
        // 建構一個對應的座標系統
        glViewport(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
       
        [self drawView];
    }
    return self;
}

//@add
- (void)drawView
{
    glClearColor(0.5f, 0.5f, 0.5f, 1); // 將顏色定義為灰色
    glClear(GL_COLOR_BUFFER_BIT); // 執行清除操作
   
    // 先將定義好的灰色填入緩衝區, 接著發佈到螢幕上
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER_OES];
}

//@add
- (void)dealloc
{
    if ([EAGLContext currentContext] == self.glContext) {
        [EAGLContext setCurrentContext:nil];
    }
}

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

I. 開啓 AppDelegate.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import "ViewController.h"
#import "GLView.h"

@interface AppDelegate : UIResponder <UIApplicationDelegate>
{
    UIWindow *window;
    //@add
    ViewController *viewController;
    GLView *glView;
}

@property (strong, nonatomic) UIWindow *window;
//@add
@property (strong, nonatomic) ViewController *viewController;
@property (strong, nonatomic) GLView *glView;

@end

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

J. 開啓 AppDelegate.m 檔案, 修改如下:
#import "AppDelegate.h"

@implementation AppDelegate

@synthesize window = _window;
//@add
@synthesize glView = _glView;
@synthesize viewController = _viewController;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    /*
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    */
    //@update
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
    self.window = [[UIWindow alloc] initWithFrame:screenBounds];
    self.viewController = [[ViewController alloc] init];
    self.glView = [[GLView alloc] initWithFrame:screenBounds];
   
    self.window.rootViewController = viewController;
    self.viewController.view = self.glView;
   
    [self.window addSubview:self.viewController.view];
    [self.window makeKeyAndVisible];
   
    return YES;
}
....

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

K. 隱藏狀態欄位
      開啓 OpenGLESBegin-Info.plist 檔案, 新增一筆資料如下:
      Key: Status bar is initially hidden
      Value: YES

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

L. 編譯並執行

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

M. 新增 OpenGL ES 的通用檔案
    1. Xcode > File > New > File...
           > iOS > C and C++ > Header File > Next

          Save As: OpenGLESCommon.h         
          Targets: OpenGLESBegin (勾選)
         > Create

    2. 開啓 OpenGLESCommon.h 檔案, 修改如下:
/*
#ifndef OpenGLESBegin_OpenGLESCommon_h
#define OpenGLESBegin_OpenGLESCommon_h

#endif
*/

#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

#pragma mark -
#pragma mark Vertex3D
#pragma mark -

// 頂點定義
typedef struct {
    GLfloat    x;
    GLfloat y;
    GLfloat z;
} Vertex3D;

// 單個頂點
static inline Vertex3D Vertex3DMake(CGFloat inX, CGFloat inY, CGFloat inZ)
{
    Vertex3D ret;
   
    ret.x = inX;
    ret.y = inY;
    ret.z = inZ;
   
    return ret;
}

// 三維空間中任何兩點間的直線距離
static inline GLfloat Vertex3DCalculateDistanceBetweenVertices(Vertex3D first, Vertex3D second)
{
    GLfloat deltaX = second.x - first.x;
    GLfloat deltaY = second.y - first.y;
    GLfloat deltaZ = second.z - first.z;
   
    return sqrtf(deltaX*deltaX + deltaY*deltaY + deltaZ*deltaZ ); // sqrtf: 開根號
}

#pragma mark -
#pragma mark Triangle3D
#pragma mark -

// 三角形定義
typedef struct {
    Vertex3D v1;
    Vertex3D v2;
    Vertex3D v3;
} Triangle3D;

// 三角形物體
static inline Triangle3D Triangle3DMake(Vertex3D inV1, Vertex3D inV2, Vertex3D inV3)
{
    Triangle3D ret;
   
    ret.v1 = inV1;
    ret.v2 = inV2;
    ret.v3 = inV3;
   
    return ret;
}

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

N. 新增常數與巨集定義檔案
      1. Xcode > File > New > File...
           > iOS > C and C++ > Header File > Next

          Save As: ConstantsAndMacros.h 
          Targets: OpenGLESBegin (勾選)
         > Create      
       
         2. 開啓 ConstantsAndMacros.h 檔案, 修改如下: (update: 2012/03/30)
/*
#ifndef OpenGLESBegin_ConstantsAndMacros_h
#define OpenGLESBegin_ConstantsAndMacros_h

#endif

*/


// How many times a second to refresh the screen

#define kRenderingFrequency             15.0

// Defines whether to setup and use a depth buffer
#define USE_DEPTH_BUFFER                1

// Macros
#define DEGREES_TO_RADIANS(__ANGLE__) ((__ANGLE__) / 180.0 * M_PI)

      2. 開啓 OpenGLESCommon.h 檔案, 修改如下: (2012/04/08 update)
....
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>
//@add
#import "ConstantsAndMacros.h"
....