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. GLKitiOS 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 bufferframe 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 的一些屬性(contextdelegate), 在此順便
          介紹 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 buffercolor 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 (方法)
           這是用手動的方式, 從目前 viewcontext 中, 取得一個 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. GLKViewOpenGL 在處理 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 來產生原始的像素資料, 我們需要一個指向此圖片
               的參照. 這個很容易, 我們可以使用 UIImageCGImageRef 屬性.

           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. 編譯並執行
         立方體已貼上紋理了, 
立方體的正面看起來還好, 側面卻不正確的伸展.