2012年6月23日 星期六

OpenGL ES 2.0 with GLKit Part 1 - 1

since: 2012/06/22
update: 2012/06/23


reference:
1.
Beginning OpenGL ES 2.0 with GLKit Part 1 | Ray Wenderlich

GLKView

A. 說明:
      1. GLKit 是 iOS 5 的新 API, 它可以讓用 OpenGL ES 來開發 App 較以往更為容易.
         
      2. GLKit 包含四個主要的部分:
          a. GLKView/GLKViewController
              這些類別將以往在 OpenGL ES 中, 所使用的一成不變的程式碼作大量的
              抽象化處理, 以方便來設置基本的 OpenGL ES 專案.   

          b. GLKEffects
              這些類別實作了使用在 OpenGL ES 1.0 中普遍的著色行為(shading behaviors),
              使得轉變到 OpenGL ES 2.0 更加容易. 它們也是使用手工的方式來處理一些
              基本的光源與貼圖的工作.

          c. GLMath
              在 iOS 5 的遊戲中, 相當重要的是每個遊戲都需要有各自的數學函式庫,
              而這些數學函式庫都包含共同常規的向量與矩陣運算. 現在使用 GLMath,
              將提供大部份共同常規的數學函式.

          d. GLKTextureLoader
              這個類別使得在 OpenGL ES 中, 載入圖片作為紋理貼圖更加容易. 較以往
              必須寫一個複雜的方法來處理不同圖片的檔案格式; 現在, 只要呼叫單一的
              方法, 就可以載入紋理了.

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

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

      2. 編譯並執行:
          得到全白的畫面

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

C. GLKView 介紹
     1. 說明:
         a. 要開始使用 OpenGL ES 2.0 的第一件事, 是幫 window (UIWindow) 新增
             一個 subview, 使它可以利用 OpenGL 來繪製物體. 如果你之前有用一般的
             方式來寫過 OpenGL ES 2.0 的程式, 就會知道需要使用大量一成不變(刻版)
             的程式碼來完成, 例如: 建立 render buffer 與 frame buffer 等等.   

         b. 現在, 可以輕易的使用 GLKit 的新類別: GLKView 來完成. 無論何時, 當你
             想要在一個 view 裡使用 OpenGL 來繪製, 你只需簡單地新增一個 GLKView
             (它是 UIView 的一個標準子類別) 並且設定好一些屬性即可.    

         c. 接著, 你可以設定一個類別作為 GLKView 的委派(delegate), 當需要被繪製時,
             該類別的某個方法就會被呼叫. (你可以在這個方法中, 放入你的 OpenGL
             繪圖指令)

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

     2. 新增 Framework:
     QuartzCore
     - Add 2D graphics rendering support.

     OpenGLES
     
- Is used for visualizing 2D and 3D data.

     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.


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

     3. 開啟 AppDelegate.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
//@add
#import <GLKit/GLKit.h>

//@interface AppDelegate : UIResponder <UIApplicationDelegate>
//@update for set as GLKView's delegate
@interface AppDelegate : UIResponder <UIApplicationDelegate, GLKViewDelegate>

@property (strong, nonatomic) UIWindow *window;

@end


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

     4. 開啟 AppDelegate.m 檔案, 修改如下:
....
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    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;
   
    // step 5: Add the GLKView as a subview
    [self.window addSubview:view];
   
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
....
#pragma mark - GLKViewDelegate
//@add: for implement delegate method
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    // specify the RGB and alpha (transparency) values to use when clearing the screen.
    glClearColor(1.0, 0.0, 0.0, 1.0);
   
    // actually perform the clearing.
    glClear(GL_COLOR_BUFFER_BIT);
}

@end

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

     5. 編譯並執行:
        對於完全是 OpenGL 新手的人來說, 可能無法感受到;
        對於以前曾用一般 OpenGL 的方式做過的人而言, 的確是方便許多了.

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

D. 選擇性的參考資料: GLKView 的其它屬性與方法
      1. 說明:
          目前我們只設定了 GLKView 的一些屬性(context 與 delegate), 在此順便
          介紹 GLKView 的其它屬性與方法, 將來可能會用到.

      2. drawableColorFormat (屬性)
           a. OpenGL context 有一個用來儲存顯示在螢幕上的顏色之 buffer. 你可以
               使用這個屬性來設定在 buffer 中, 每個像素的顏色格式.

           b. 這個屬性的預設值是: GLKViewDrawableColorFormatRGBA8888,
               代表著: 在 buffer 中, 針對 RGBA 各自都使用了 8 bits(因此每個像素
               使用了 4 bytes), 這樣的好處是: 使用了最寬廣的顏色範圍, 可以讓 App
               看起來較美觀.

           c. 但是, 如果你的 App 可以僅使用較低範圍的顏色, 你可能想要將此屬性
               的值設為: GLKViewDrawableColorFormatRGB565, 這會讓你的 App
               花費較少的資源(記憶體與處理時間).

      3. drawableDepthFormat (屬性)
           a. OpenGL context 也可以選擇性地連結另一個 buffer: depth buffer. 它可以
               幫助確認當物體較靠近觀察者前方(相對於較遠的)時, 才顯示其像素.

           b. 這個方式預設為: OpenGL 將最靠近觀察者的物件之每個像素儲存到 buffer 裡. 
               當要開始繪製出像素時, 它會檢查 depth buffer 看看是否已經有畫出較接近
               觀察者的像素, 如果有的話便將目前的像素摒棄掉, 否則就將此像素加入到
               depth buffer 與 color buffer 裡.

           c. 你可以設定這個屬性來選擇 depth buffer 的形式, 預設值為:
               GLKViewDrawableDepthFormatNone, 代表沒有啟用 depth buffer 功能.

           d. 但是如果你想要啓用此功能(通常使用在 3D 遊戲中), 你應當選擇:
               GLKViewDrawableDepthFormat16 或 GLKViewDrawableDepthFormat24.
               二者差別在於: 設為 GLKViewDrawableDepthFormat16 時, 你的 App 會使用
               較少的資源, 但是當二個物體非常靠近時, 就可能會發生渲染的問題.

      4. drawableStencilFormat (屬性)
           a. OpenGL context 可以連結的另一個選擇性 buffer 為: stencil buffer.
               模板緩衝可以協助你限制只在螢幕的特定部分作繪製的工作.
               通常它對於處理某些東西特別有用, 例如: 陰影. 你可以使用 stencil buffer
               來確保陰影投射(cast)到地板上. 

           b. 這個屬性的預設值為: GLKViewDrawableStencilFormatNone, 代表:
               沒有使用 stencil buffer, 但是你可以啟用它, 設定為唯一可選的值:
               GLKViewDrawableStencilFormat8. 

      5. drawableMultisample (屬性)
           a. 最後一個可經由 GLKView 屬性設定的選擇性 buffer 為: multisampling buffer.
               如果你曾經用 OpenGL 來畫線, 並且注意到 "鋸齒狀線條" 情形, 多重採樣緩衝
               可以協助處理這個問題(抗鋸齒化).

           b. 基本上它所處理的是: 取代對每個像素呼叫一次 fragment shader 的作法.
               而將像素劃分為更小的單位, 並且在較小層級的細節處理上多次呼叫
               fragment shader. 其結果通常會使幾何物體的邊緣顯得更加光滑.

           c. 但是, 小心設置這個屬性, 因為它需要花費更多的處理時間與記憶體.
               預設值為: GLKViewDrawableMultisampleNone, 但是你可以啟用它,
               設定為唯一可選的值: GLKViewDrawableMultisample4X.  

      6. drawableHeight/drawableWidth (屬性)
           這些是唯讀的屬性, 用來指出 buffers 的高與寬. 以 view 的 bounds 與
           contentSize 為基準, 當 bounds 與 contentSize 改變時, buffers 就會自動調整大小.

      7. snapshot (方法)
           這是用手動的方式, 從目前 view 的 context 中, 取得一個 UIImage.

      8. bindDrawable (方法)
           a. OpenGL 還有另外一個 buffer: frame buffer, 基本上 frame buffer 是其它
               我們討論過的 buffers (color buffer, depth buffer, stencil buffer 等等) 之集合.

           b. 在呼叫 glkView:drawInRect 方法之前, GLKit 會在場景的背後將設定的屬性
               綁定到 frame buffer 裡. 但是, 如果你的遊戲 App 需要變更不同的 frame buffer
               來執行一些其它種類的渲染(例如, 你要使用不同的紋理貼圖), 你可以使用
               bindDrawable 方法來告訴 GLKit 去重新綁定到你設定的 frame buffer.

      9. deleteDrawable (方法)
           a. GLKView 與 OpenGL 在處理 buffers 時, 會使用到大量的實體記憶體. 假如
               你的 GLKView 無法顯示出來, 你可能會發現先暫時地取消配置記憶體, 直到
               內容再次可被顯示出來, 是有效的方式. 如果你想要這麼做, 只需要使用
               deleteDrawable 方法即可.

           b. 下一次, 當 view 要被繪製出來時, GLKView 會在場景的背後自動重新配置
               記憶體, 相當方便不是嗎?

      10. enableSetNeedsDisplay (屬性) and display (方法)
            a. 預設情況下, GLKView 只會在有需要時才會自行更新, 亦即: 當 views
                第一次被繪製出來, 長寬大小改變, 等等. 然而, 對於遊戲 App 而言,
                你經常需要去重新繪製每個 frame!

            b. 我們可以藉由將 enableSetNeedsDisplay 設為 false 來停用 GLKView 的
                這個預設行為. 然後, 當我們要更新螢幕內容時, 可藉由在 GLKView 上
                呼叫 display 方法, 來控制重新繪製的時機. 

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

E. 更新 GLKView
      1. 開啓 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>
{
    //@add
    float _curRed;
    BOOL _increasing;
}

@property (strong, nonatomic) UIWindow *window;

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

@end

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

      2. 開啓 AppDelegate.m 檔案, 修改如下:
....
// step 01
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //@add
    _increasing = YES;
    _curRed = 0.0;
....
}

....
// step 02
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    //@add
    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);
}

....
// step03
//@add: for synchronize the time we render with OpenGL
//             to the rate at which the screen refreshes.

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

....
// step04
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
....
    view.delegate = self;
   
    //@add: for synchronize
    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
    [self.window addSubview:view];
   
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}
....

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

      3. 編譯並執行
         你會看到脈動式的 "緊急警報" 效果 (黑 -> 漸紅 -> 紅)



2012年6月21日 星期四

OpenGL ES 2.0 Tutorial Part 2 - 2

since: 2012/06/21
update: 2012/06/21


reference:
1. OpenGL ES 2.0 for iPhone Tutorial Part 2: Textures | Ray Wenderlich
2. I touchs: OpenGL ES 2.0 Tutorial Part 2 - 1

紋理貼圖之二

A. 修正被伸展的效果
      1. 說明:
          a. 造成伸展效果的原因是: 我們目前只是為每個頂點設定一個紋理座標, 並且
              重覆使用這些頂點.

          b. 例如, 我們把立方體的前面左下角對應到 (0, 0). 但是在左邊那一面, 相同的
              頂點
卻變成了在右下方, 所以如果此時對應到的紋理座標同樣為 (0, 0) 就
              沒有意義了, 它應該是 (1, 0).  

          c. 在 OpenGL 裡, 你不能把頂點只是當成頂點座標, 它是由以下所組合而成的
              獨一無二的點: 座標, 顏色, 紋理座標以及任何在你的結構中所擁有的其它元素.

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

      2. 開啓 OpenGLView.m 檔案, 修改如下:
//setp01
#import "OpenGLView.h"

//@add for projection
#import "CC3GLMatrix.h"

//@add
#define TEX_COORD_MAX   1
....

//setp02
/*
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, 0}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}},
    {{1, -1, -1}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, -1}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}}
};
*/

//@update
// 要確保紋理座標能正確的對應:
// 為每個面建立各自不同的: 頂點, 顏色與紋理座標
const Vertex Vertices[] = {
    // Front
    {{1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Back
    {{1, 1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{-1, -1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, -1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Left
    {{-1, -1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}},
    // Right
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{1, 1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{1, -1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Top
    {{1, 1, 0}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, 1, -2}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, 1, -2}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, 1, 0}, {0, 0, 0, 1}, {0, 0}},
    // Bottom
    {{1, -1, -2}, {1, 0, 0, 1}, {TEX_COORD_MAX, 0}},
    {{1, -1, 0}, {0, 1, 0, 1}, {TEX_COORD_MAX, TEX_COORD_MAX}},
    {{-1, -1, 0}, {0, 0, 1, 1}, {0, TEX_COORD_MAX}},
    {{-1, -1, -2}, {0, 0, 0, 1}, {0, 0}}
};
....

//setp03
/*
const GLubyte Indices[] = {
    // Front
    0, 1, 2,
    2, 3, 0,
    // Back
    4, 6, 5,
    4, 7, 6,
    // Left
    2, 7, 3,
    7, 6, 2,
    // Right
    0, 4, 1,
    4, 1, 5,
    // Top
    6, 2, 1,
    1, 6, 5,
    // Bottom
    0, 3, 7,
    0, 7, 4   
};
*/

//@update
const GLubyte Indices[] = {
    // Front
    0, 1, 2,
    2, 3, 0,
    // Back
    4, 5, 6,
    4, 5, 7,
    // Left
    8, 9, 10,
    10, 11, 8,
    // Right
    12, 13, 14,
    14, 15, 12,
    // Top
    16, 17, 18,
    18, 19, 16,
    // Bottom
    20, 21, 22,
    22, 23, 20
};
....

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

      3. 編譯並執行:

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

B. 重複貼圖
      1. 說明:
          在 OpenGL 裡, 如果你喜歡的話, 可以很容易地在物體表面上建立重複的
          紋理貼圖. 我們所要使用的地磚紋理, 最好是無縫的紋理.

      2. 開啓 OpenGLView.m 檔案, 修改如下:
....
//@add
//#define TEX_COORD_MAX   1
#define TEX_COORD_MAX   4
....

        說明: 現在我們讓立方體的每個面從左下角 (0, 0) 到右上角 (4, 4) 都對應到
                   紋理貼圖上.

      3. 編譯並執行:
          可以看到沿著立方體表面的重複紋理.

          說明: 能夠這樣自動完成的原因是 GL_TEXTURE_WRAP_S 與
                     GL_TEXTURE_WRAP_T 的預設值為 GL_REPEAT. 如果不想讓紋理
                     如此重復的話, 你可以利用 glTexParameteri 來覆寫預設的值.

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

C. 加入花樣
      1. 開啓 OpenGLView.h 檔案, 修改如下:
....
@interface OpenGLView : UIView
{
.... 
    //@add
    //
    // 我們分別需要二個 vertex / index buffers
    // 一個給立方體, 另一個給擁有 "魚骨花樣" 的那一個表面
    GLuint _vertexBuffer;
    GLuint _indexBuffer;
    GLuint _vertexBuffer2;
    GLuint _indexBuffer2;
}
....

      2. 開啓 OpenGLView.m 檔案, 修改如下:
....
// step01
//@add: 為要繪製魚骨紋理的矩形定義一組新的頂點集合
//
// 我們將它設置的比前方表面還要小一些, 並且將 Z 座標的值稍微調高一些, 讓它浮現.
// 不然的話, 在作深度測試時就會被摒棄掉.

const Vertex Vertices2[] = {
    {{0.5, -0.5, 0.01}, {1, 1, 1, 1}, {1, 1}},
    {{0.5, 0.5, 0.01}, {1, 1, 1, 1}, {1, 0}},
    {{-0.5, 0.5, 0.01}, {1, 1, 1, 1}, {0, 0}},
    {{-0.5, -0.5, 0.01}, {1, 1, 1, 1}, {0, 1}},
};

//@add
const GLubyte Indices2[] = {
    1, 0, 2, 3
};

@implementation OpenGLView
....

// step02
- (void)setupVBOs
{
    /*
    GLuint vertexBuffer;
    // Create a new Vertex Buffer object
    glGenBuffers(1, &vertexBuffer);
   
    // Bind "vertexBuffer" to GL_ARRAY_BUFFER
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
   
    // Send the data over to OpenGL-land
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
   
   
    GLuint indexBuffer;
    // Create a new Index Buffer object
    glGenBuffers(1, &indexBuffer);
   
    // Bind "indexBuffer" to GL_ELEMENT_ARRAY_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
   
    // Send the data over to OpenGL-land
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
    */
   
    // 原本的頂點 Buffer
    glGenBuffers(1, &_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), Vertices, GL_STATIC_DRAW);
   
    //原本的索引 Buffer
    glGenBuffers(1, &_indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
   
    // 包含魚骨的矩形之頂點 Buffer
    glGenBuffers(1, &_vertexBuffer2);
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices2), Vertices2, GL_STATIC_DRAW);
   
    // 包含魚骨的矩形之索引 Buffer
    glGenBuffers(1, &_indexBuffer2);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer2);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices2), Indices2, GL_STATIC_DRAW);
}
....

// step03
....
- (void)render:(CADisplayLink *)displayLink
{
....
    glViewport(0, 0, self.frame.size.width, self.frame.size.height);
   
    //@add: right after call to glViewport
    // 繪製紋理前先將立方體的 vertex/index buffer 綁定
    // (不能夠再假設它已設定好了)
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
....
}
....

// step04
....
- (void)render:(CADisplayLink *)displayLink
{
....
    //@add: right before [_context presentRenderbuffer:]
    //
    // 綁定包含魚骨的矩形之 vertex/index buffers
    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer2);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer2);
   
    // 載入魚骨的紋理
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, _fishTexture);
    glUniform1i(_textureUniform, 0);
   
    glUniformMatrix4fv(_modelViewUniform, 1, 0, modelView.glMatrix);
   
    // 設定屬性
    glVertexAttribPointer(_positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0);
    glVertexAttribPointer(_colorSlot, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 3));
    glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7));
   
    // 使用 GL_TRIANGLE_STRIP 來產生三角形, 可降低 index buffer 的大小.
    glDrawElements(GL_TRIANGLE_STRIP, sizeof(Indices2)/sizeof(Indices2[0]), GL_UNSIGNED_BYTE, 0);
       
   
    // Present the render/color buffer to the UIView's layer!
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
....

      3. 編譯並執行:

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

D. 啟用渲染效果
      1. 開啓 OpenGLView.m 檔案, 修改如下:
....
- (void)render:(CADisplayLink *)displayLink
{
    //@add for blending
    //
    // 設定 blending 的規則
    //
    // GL_ONE:(for the source) 渲染所有來源圖片的像素.
    //
    // GL_ONE_MINUS_SRC_ALPHA:(for the destination)
    // 渲染所有目標圖片資料, 除了已設置的來源圖片.

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
   
    // 啟用渲染
    glEnable(GL_BLEND);
....
}

    備註: 關於渲染的模式, 可參考: 這篇教學文章.

      2. 編譯並執行:

2012年6月19日 星期二

OpenGL ES 2.0 Tutorial Part 2 - 1

since: 2012/06/18
update: 2012/06/19


reference:
1. OpenGL ES 2.0 for iPhone Tutorial Part 2: Textures | Ray Wenderlich
2. I touchs: OpenGL ES 2.0 Tutorial Part 1 - 7

紋理貼圖之一

A. 下載 "紋理" 檔案.
      1. 下載由原作者的 lovely wife 所製作的 紋理 檔案.

      2. 解壓縮後, 並將二個檔案(地磚與魚)加到專案裡, 記得勾選:
           Copy items into destination group's folder(if needed)



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

B. 讀取像素資料

      1. 說明:
           a. 首先, 要將圖片資料提供給 OpenGL 使用. 但是, OpenGL 無法直接使用
                PNG 的圖片資料. 取而代之, OpenGL 需要你傳入像素資料的 buffer,
                並且需要指定正確的格式.

           b. 幸運地, 使用一些內建的 Quartz2D 函式, 可以輕鬆的取得像素資料的 buffer.
               備註: 可以參考 Core Graphics 101 的教學系列文章.

      2. 有四個主要的步驟來完成此事:
           a. 取得 Core Graphics 圖片的參照
               因為要使用 Core Graphics 來產生原始的像素資料, 我們需要一個指向此圖片
               的參照. 這個很容易, 我們可以使用 UIImage 的 CGImageRef 屬性.

           b. 建立 Core Graphics 的 bitmap(點陣圖) context
               下一步是建立 Core Graphics 的 bitmap context, 它是屬於記憶體中的緩衝區,
               用來儲存原始的像素資料.

           c. 將圖片繪製到 context
               可以呼叫 Core Graphics 的簡單函式來達成, 之後緩衝區裡就會包含原始的
               像素資料.

           d. 將像素資料傳送給 OpenGL
               為此, 我們需要建立一個 OpenGL texture(紋理) 物件, 並且取得它的唯一
               ID (稱為: name), 接著就可以呼叫函式將像素資料傳送給 OpenGL. 

      3. 開啟 OpenGLView.h 檔案, 修改如下:
....
//@add for texture
- (GLuint)setupTexture:(NSString *)fileName;
....

      4. 開啟 OpenGLView.m 檔案, 修改如下:
....
//@add for texture
- (GLuint)setupTexture:(NSString *)fileName
{   
    // step 1: Get Core Graphics image reference
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }
   
   
    // step 2: Create Core Graphics bitmap context
    //
    // 2-1: get the width and height of the image
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
   
    // 2-2: allocate width * height * 4(red, green, blue, and alpha) bytes
    GLubyte * spriteData = (GLubyte *) calloc(width*height*4, sizeof(GLubyte));
   
    // The fourth parameter to CGBitmapContextCreate is the bits per component,
    // and we set this to 8 bits (1 byte).

    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast); 
   
   
    // step 3: Draw the image into the context
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);

    // Since we're done with the context at this point, we can release it.
    CGContextRelease(spriteContext);


    // step 4: Send the pixel data to OpenGL
    GLuint texName;
   
    // 4-1: create a texture object(and get its unique ID: texName)
    glGenTextures(1, &texName);
   
    // 4-2.: load our new texture name into the current texture unit.
    glBindTexture(GL_TEXTURE_2D, texName);
   
    // 4-3: set a texture parameter for our texture
    //
    // 在此將 GL_TEXTURE_MIN_FILTER 設為 GL_NEAREST
    // 說明: 即使沒有使用到 mipmaps, 仍然需要設定 GL_TEXTURE_MIN_FILTER
    //            (mipmaps: 細節層次. 主要是針對貼圖做漸層的處理, 依據解析度來決定
    //             貼圖大小)

    //
    // GL_TEXTURE_MIN_FILTER: 繪製遠方的物體時, 將紋理縮小
    // GL_NEAREST: 使用最鄰近所對應到的紋理像素來繪製頂點
    //                             (而 GL_LINEAR 為 "平滑" 的方式)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   
    // 4-4: send the pixel data buffer to OpenGL
    //
    // 在此指定傳入的像素資料格式為: GL_RGBA 與 GL_UNSIGNED_BYTE,
    // 代表: 格式為 紅, 綠, 藍與 alpha, 並各自佔有 1 byte 大小.
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
   
   
    // step 5: deallocate the pixel buffer
    // (we've sent the image data to OpenGL, OpenGL is storing the texture in the GPU)
    free(spriteData);       
   
   
    // step 6: returning the texture name
    // (we'll need to refer to it later when drawing)
    return texName;
}
....

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

C. 使用紋理資料
     1. 說明:
         到目前為止, 我們已經有一個輔助的方法(setupTexture:), 用來載入圖片,
         傳送給 OpenGL 並回傳一個紋理名稱. 接下來, 就可以使用它來幫立方體
         貼上一層外皮了.

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

     2. 開啟 SimpleVertex.glsl 檔案, 修改如下:
attribute vec4 Position;

attribute vec4 SourceColor;

varying vec4 DestinationColor;

//@add for Projection
//
// uniform: This means that we just pass in a constant value for all vertices,
//          rather than a per-vertex value.
//
// mat4: a 4×4 matrix(used to scale, rotate, or translate vertices)
//
// We'll pass in a matrix that moves our vertices around according to the Projection.
uniform mat4 Projection;
//@add
uniform mat4 Modelview;

//@add for texture
//
// for each vertex, we'll specify the coordinate on the texture that it should map to.
attribute vec2 TexCoordIn;
varying vec2 TexCoordOut;

void main(void) {

    DestinationColor = SourceColor;
   
    //gl_Position = Position;
    //@update for Projection
    //
    // Set the final position of the vertex to be the Projection multiplied by the Position.
    //gl_Position = Projection * Position;
    //@update: add applying the modelview matrix to the position.
    gl_Position = Projection * Modelview * Position;
   
    //@add for texture
    TexCoordOut = TexCoordIn;
}

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

     3. 開啟 SimpleFragment.glsl 檔案, 修改如下:
varying lowp vec4 DestinationColor;
//@add for texture
varying lowp vec2 TexCoordOut;
uniform sampler2D Texture;

void main(void) {

    //gl_FragColor = DestinationColor;
    //@update for texture
    //
    // we multiply the color by whatever is in the texture at the specified coordinate.
    // texture2D is a built-in GLSL function that samples a texture for us.
    gl_FragColor = DestinationColor * texture2D(Texture, TexCoordOut);
}

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

     4. 開啟 OpenGLView.h 檔案, 修改如下:
....
@interface OpenGLView : UIView
{
    //@add
    CAEAGLLayer *_eaglLayer;
    EAGLContext *_context;
    GLuint _colorRenderBuffer;
   
    //@add
    GLuint _positionSlot;
    GLuint _colorSlot;
   
    //@add for uniform
    GLuint _projectionUniform;
    GLuint _modelViewUniform;
   
    //@add
    float _currentRotation;
    GLuint _depthRenderBuffer;
   
    //@add for texture
    //
    // keep track of the texture names for our two textures
    GLuint _floorTexture;
    GLuint _fishTexture;
    // input attribute slot
    GLuint _texCoordSlot;
    // texture uniform slot
    GLuint _textureUniform;
}
....

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

     5. 開啟 OpenGLView.m 檔案, 修改如下:
....
// step01
typedef struct {
    float Position[3];
    float Color[4];
    //@add for texture
    float TexCoord[2]; // texture coordinates
} Vertex;

....
// step02
/*
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}},
    {{1, 1, 0}, {1, 0, 0, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}},
    {{1, -1, -1}, {1, 0, 0, 1}},
    {{1, 1, -1}, {1, 0, 0, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}}
};
*/
//@update for texture
// Add texture coordinates to Vertices
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, 0}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, 0}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, 0}, {0, 1, 0, 1}, {0, 0}},
    {{1, -1, -1}, {1, 0, 0, 1}, {1, 0}},
    {{1, 1, -1}, {1, 0, 0, 1}, {1, 1}},
    {{-1, 1, -1}, {0, 1, 0, 1}, {0, 1}},
    {{-1, -1, -1}, {0, 1, 0, 1}, {0, 0}}
};

....
// step03
- (void)compileShaders
{
....
    //@add for texture
    _texCoordSlot = glGetAttribLocation(programHandle, "TexCoordIn");
    glEnableVertexAttribArray(_texCoordSlot);
    _textureUniform = glGetUniformLocation(programHandle, "Texture");
}

....
// step04
- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
....    
        //@add for texture
        _floorTexture = [self setupTexture:@"tile_floor.png"];
        _fishTexture = [self setupTexture:@"item_powerup_fish.png"];
    }
   
    return self;
}

....
// step05
- (void)render:(CADisplayLink *)displayLink
{
....
    //@add for texture(before glDrawElements)
    glVertexAttribPointer(_texCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*) (sizeof(float) * 7)); 
   
    // activate the texture unit we want to load our texture into.
    // default: assuming GL_TEXTURE0 is already the active texture unit
    glActiveTexture(GL_TEXTURE0);
   
    // bind the texture into the current texture unit
    glBindTexture(GL_TEXTURE_2D, _floorTexture);
   
    // set the texture uniform to the index of the texture unit it's in (0).
    // defaults to 0
    glUniform1i(_textureUniform, 0);


    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]),
                   GL_UNSIGNED_BYTE, 0);
   
    // Present the render/color buffer to the UIView's layer!
    [_context presentRenderbuffer:GL_RENDERBUFFER];
}
....

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

     6. 編譯並執行
         立方體已貼上紋理了, 
立方體的正面看起來還好, 側面卻被不正確的伸展.