顯示具有 OpenGL ES 標籤的文章。 顯示所有文章
顯示具有 OpenGL ES 標籤的文章。 顯示所有文章

2012年9月20日 星期四

Create a GLSL based project on Linux

since: 2012/09/20
update: 2012/09/20

reference:
1. OpenGL® Application Binary Interface for Linux
2. Getting Started with the OpenGL Shading Language (GLSL)
3. I touchs: Setting up Eclipse for OpenGL on Linux Mint
4. I touchs: OpenGL: Basic Setup
5. Graphics Shaders: Theory and Practice, 2/e (Hardcover)
6. CSCI 4229/5229: Computer Graphics
     > Fall 2012 > Handouts > Introduction to OpenGL


A. 說明:
      1. 目的: 建立一個可供練習 GLSL 的基本專案架構, 包含 OpenGL 的繪圖流程,
                     shader 的處理(編譯, 附加, 連結, 變數存取....)等.

      2. 開發環境:
           作業系統: Linux Mint (基於 Ubuntu 的另一個 Linux 版本)
           整合開發環境(IDE): Eclipse
           程式語言: C, GLSL

      3. 其它:
           a. 本篇文章是延續上一篇 Setting up Eclipse for OpenGL on Linux Mint 而來,
               一開始, 我先將 Getting Started with the OpenGL Shading Language
               source code 下載下來, 因為是 C++ 專案的原因, 修改了變數轉型的問題
               (參考: OpenGL: Starting with the Shading Language-0), 接著編譯就出現了
               錯誤訊息: 無法解析有關處理 shader 的函式.

           b. 接著, 在終端機下執行:
               $ glxinfo | grep -i opengl
               備註: "shading language version string: 4.30 NVIDIA via Cg compiler
                          看來不是使用原生的 GLSL compiler, 不過我們是用程式去
                          compiler shader, 所以應該不會是問題所在.

           c. 最後, 改成建立 C 專案(而非C++), 並作一些調整, 就可以正常編譯並執行了.

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

B. 新增 Eclipse 專案
      1. 啓動 Eclipse
          Select a workspace > /home/lanli/workspace (預設) > OK

      2. 新增 C 專案:
           step 1:
           File -> New -> C Project (或: Shift+ Alt + N > C Project)

           step 2:
           Project name: GLSL_base
           Project type > Executable: Empty Project
           Toolchains: Cross GCC (或 Linux GCC 亦可)
           > Finish

      3. 專案 Libraries 設定:
           1. 滑鼠右鍵點選
               > Project Explorer 下的專案名稱(此處為: GLSL_base)
               > Properties
       
           2. C/C++ Build > Settings
               Cross GCC Linker > Libraries
               利用 "+" 新增以下的 Libraries: glut, GL, GLU
               > OK

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

C. 加入用來處理 shader 的檔案
     1. 下載 glsl_lighting-1.tar.gz 解壓縮後, 將 opengl.hshader.c 複製到
         專案目錄下(在此為: /home/lanli/workspace/GLSL_base)

     2.Eclipse 中, 點選此專案, 按下 "F5" 鍵來 Refresh, 使剛加入的檔案
         出現在專案目錄裡.

     3. 開啟 opengl.h 檔案, 修改如下:
#ifndef __OPENGL_H__
#define __OPENGL_H__


#define LINUX
//#define APPLE

#ifdef LINUX
    #define GL_GLEXT_PROTOTYPES
    #include <GL/glut.h>
#else


    #ifdef APPLE
        #include <OpenGL/gl.h>
        #include <OpenGL/glext.h>
        #include <GLUT/glut.h>
    #else
        #include <GL/gl.h>
        #include <GL/glext.h>
        #include <GL/glut.h>
    #endif /* APPLE */


#endif /* LINUX */

#endif /* __OPENGL_H__ */


說明: 主要修改 Linux 的部分:
           a. 要先定義: GL_GLEXT_PROTOTYPES
           b. 只需引入: <GL/glut.h>

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

D. 新增公用函式

     1. File -> New -> Source File
         Source file: utility.c
         Template: <Default C source template><None>
          > Finish

     2. 開啟 utility.c 檔案, 修改如下:
/*
 * utility.c
 *
 *  Created on: 2012/9/13
 *      Author: lanli
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "opengl.h"


// 用來檢查 OpenGL 可能發生的錯誤問題

void checkGLErrors(const char* caller);


/*********************************************/

void checkGLErrors(const char* caller)
{
    unsigned int gle = glGetError();
    if(gle != GL_NO_ERROR)
    {
        fprintf(stderr, "GL Error discovered from caller %s: ", caller);

        switch (gle)
        {
            case GL_INVALID_ENUM:
                fprintf(stderr, "Invalid enum.\n");
                break;

            case GL_INVALID_VALUE:
                fprintf(stderr, "Invalid value.\n");
                break;

            case GL_INVALID_OPERATION:
                fprintf(stderr, "Invalid Operation.\n");
                break;

            case GL_STACK_OVERFLOW:
                fprintf(stderr, "Stack overflow.\n");
                break;

            case GL_STACK_UNDERFLOW:
                fprintf(stderr, "Stack underflow.\n");
                break;

            case GL_OUT_OF_MEMORY:
                fprintf(stderr, "Out of memory.\n");
                break;

            case GL_INVALID_FRAMEBUFFER_OPERATION:
                fprintf(stderr, "Framebuffer object is not complete.\n");
                break;
        }
        return;
    }
    else
    {
        fprintf(stderr, "NO Error from caller: %s \n",  caller);
    }
    return;
}


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

E. 新增 "場景繪製" 相關函式
     1. File -> New -> Source File
         Source file: scene.c
         Template: <Default C source template><None>
          > Finish

     2. 開啟 scene.c 檔案, 修改如下:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "opengl.h"


/* shader functions defined in shader.c */
extern void shaderAttachFromFile(GLuint, GLenum, const char *);

void sceneInit();
void sceneRender();
void sceneCycle();


/***********************************************************/


void sceneInit()
{

    //GLint result;

    /* create program object and attach shaders */

    /* link the program and make sure that there were no errors */

    /* get uniform locations */

    /* set up lights */

    /* create 2D/3D object */

    /* call the glutIdleFunc */

    /* setup camera */

    //glLoadIdentity();

}

void sceneRender()
{

    //glClear(GL_COLOR_BUFFER_BIT);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    /* enable program and set uniform variables */

    /* render the 2D/3D object */

    glColor3f(0.2, 0.4, 0.6);
    glutWireTeapot(3);


    /* disable program */

    /* render each light */


    glutSwapBuffers();

    //glFlush();
}

void sceneCycle()
{

    // refresh the screen
    glutPostRedisplay();
}


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

F. 新增 main 主函式
     1. File -> New -> Source File
          Source file: main.c
          Template: <Default C source template><None>
           > Finish

     2. 開啟 main.c 檔案, 修改如下:
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "opengl.h"

static const int WINDOW_X = 0;
static const int WINDOW_Y = 0;
static const int WINDOW_WIDTH = 320;
static const int WINDOW_HEIGHT = 320;

/* checkGLErrors functions defined in utility.c */
extern void checkGLErrors(const char* caller);

/* scene functions defined in scene.c */
extern void sceneInit(void);
extern void sceneRender(void);
extern void sceneCycle(void);

static int g_window;

void glInit();
void keyboard(unsigned char key, int x, int y);

int main(int argc, char* argv[])
{
    /* initialize GLUT */
    glutInit(&argc, argv);
    //glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);

    glutInitWindowPosition(WINDOW_X,  WINDOW_Y);
    glutInitWindowSize(WINDOW_WIDTH,  WINDOW_HEIGHT);

    g_window = glutCreateWindow("OpenGL Shading Language");

    /* initialize GL */
    glInit();

    /* initialize scene */
    sceneInit();

    /* setup callbacks */
    glutDisplayFunc(sceneRender);
    glutIdleFunc(sceneCycle);
    glutKeyboardFunc(keyboard);
    //glutReshapeFunc(resize);
    //glutSpecialFunc(special);
    //glutMouseFunc(mouse);


    /* check GL Errors */
    checkGLErrors("main");

    /* start GLUT main loop */
    glutMainLoop();

    return 0;
}

void glInit()
{
    /* setup initial GL settings */
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepth(1.0f);
    glDisable(GL_BLEND);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CCW);
    glCullFace(GL_BACK);

    /* set projection matrix */
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(-5, 5, -5, 5, 5, 15);
    // or setPerspective

    glMatrixMode(GL_MODELVIEW);
    gluLookAt(0, 0, 10, 0, 0, 0, 0, 1, 0);
}

// User Input Callbacks
void keyboard(unsigned char key, int x, int y)
{
    switch(key)
    {
        case 27: /* escape */
            glutDestroyWindow(g_window);
            exit(0);
            break;

        case 'q': case 'Q':
             glutDestroyWindow(g_window);
            exit(EXIT_SUCCESS);
            break;

        case 'r': case 'R':
            fprintf(stderr, "call glutPostRedisplay() \n");
            // refresh the screen
            glutPostRedisplay();
            break;
    }
}


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

G. 編譯並執行
    功能選單:
    Project > Build Project
    Run > Run

2012年6月25日 星期一

OpenGL ES 2.0 with GLKit Part 2 - 2


since: 2012/06/25
update: 2012/06/25


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

GLKBaseEffect 實作

A. 建立一個 GLKBaseEffect
     開啟 HelloGLKitViewController.m 檔案, 修改如下:
....
// step 1:
@interface HelloGLKitViewController ()
{
    //@add
    float _curRed;
    BOOL _increasing;
    GLuint _vertexBuffer;
    GLuint _indexBuffer;
    GLKBaseEffect *effect;
    float _rotation;

}

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

@end

....
// step 2:
- (void)setupGL
{
    // Set the current OpenGL context to the current context.
    [EAGLContext setCurrentContext:self.context];
   
    //@add for initialize GLKBaseEffect
    self.effect = [[GLKBaseEffect alloc] init]; 
....
}

....
// step 3:
- (void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
   
    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteBuffers(1, &_indexBuffer);
   
    //@add
    self.effect = nil;
}

....

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

B. 設定 GLKBaseEffect 屬性
     1. 說明:
          a. 第一個需要設定的屬性為: 投影矩陣. 投影矩陣是用來告訴 CPU 如何在 2D
              的平面上繪製出 3D 的幾何圖形.

          b. GLKit 提供了一些便利的函式來設定投影矩陣. 我們將使用其中一個來設定:
              (1). 沿著 Y 軸的視野角度 (fovy: field of view along the y-axis),
                     即上下二個平面的.
              (2). 寬與高的比例 (aspect ratio: w / h)
              (3). 近端與遠端平面 (near and far planes)

          c. Field of view 就像是相機的鏡頭. 較小的 field of view (例如: 10), 類似攝遠
              鏡頭 - 藉由將圖片向你拉近而使圖片放大. 較大的 field of view (例如: 100),
              類似廣角鏡頭 - 它讓所有物體看起來較遠. 一般設定 Field of view 的值介於:
              65 - 75 之間.

          d. Aspect ratio 是你想要繪製的方向比例(亦即, view 的方向比例). 它與 field
              of view 結合使用, 來決定沿著 X 軸的視野大小.

          e. 近端與遠端平面, 是場景中用來框住可視空間的容量邊界. 所以, 如果某個
              物體與眼睛(相機)的距離過近於近端平面或過遠於遠端平面, 它就不會被
              繪製出來. 這是一個普遍可能會遇到的問題 - 你在繪製一個物體, 但是它沒有
              在螢幕上顯示出來. 記得要去檢查: 這個物體是否位於 near plane 與 far plane
              之間.

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

     2. 開啟 HelloGLKitViewController.m 檔案, 修改如下:
       // 設定投影矩陣
....
- (void)update
{
    ....

    //@add: set the projection matrix
    //
    // 1. get the aspect ratio of the GLKView.
    float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
   
    // 2. Create a perspective matrix: (with GLKit math library)
    //
    // We set the near plane to 4 units away from the eye, and the far plane
    // to 10 units away.

    GLKMatrix4 projectionMatrix = GLKMatrix4MakePerspective(GLKMathDegreesToRadians(65.0f), aspect, 4.0f, 10.0f);
   
    // 3. Set the projection matrix on the effect's transform property
    self.effect.transform.projectionMatrix = projectionMatrix;
}
....

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

     3. 設定 modelView 矩陣
         a. 說明:
             ModelViewMatrix 是要繪製的幾何物體效果都需要套用的轉換.
             在此, 又藉由 GLKit 數學函式庫的協助, 來執行位移, 旋轉縮放的功能.

         b. 開啟 HelloGLKitViewController.m 檔案, 修改如下: 
....
- (void)update
{
    ....
    //@add: set the projection matrix
    ....
   
    //@add: set the modelViewMatrix
    //
    // 1. Move z-coordinate backwards.
    //    Create a matrix for us that translates 6 units(Z) backwards.
    //
    // Because when we set up the vertices for the square, the z-coordinate
    // for each vertex was 0.

    GLKMatrix4 modelViewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -6.0f); 
   
    // 2. Increment rotate variable
    _rotation += 90 * self.timeSinceLastUpdate;
   
    // 3. Rotating it
    modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 0, 1);
   
    // 4. Set the model view matrix on the effect's transform property.
    self.effect.transform.modelviewMatrix = modelViewMatrix;
}
....

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

C. 在 GLKBaseEffect 上呼叫 prepareToDraw
     1. 說明:
         在將物體繪製到螢幕之前, 任何時候你在 GLKBaseEffect 上更改了屬性設定,
         都需要先呼叫此方法.

     2. 開啟 HelloGLKitViewController.m 檔案, 修改如下: 
....
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    //@add
    [self.effect prepareToDraw];
}
....

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

D. 啟用 "預先定義好的" 的屬性 
     1. 開啟 HelloGLKitViewController.m 檔案, 修改如下: 
....
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    //@add
    [self.effect prepareToDraw];
   
    //@add: Enable pre-defined attributes
    //
    // 1. bind the vertex and index buffers
    //
    // Every time before you draw, you have to tell OpenGL which vertex buffer objects
    // you should use.
So here we bind the vertex and index buffers we created earlier.
    //
    // Strictly, we didn't have to do this for this app (because they're already still bound
    // from before)
but usually you have to do this because in most games you use
    // many different vertex buffer objects.

    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
   
    // 2. Enable the pre-defined vertex attributes (position)
    //    and feed the correct values to input variables for the vertex shader.
    glEnableVertexAttribArray(GLKVertexAttribPosition);       

    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Position));
   
    // 3. Enable the pre-defined vertex attributes (color)
    //    and feed the correct values to input variables for the vertex shader.
    glEnableVertexAttribArray(GLKVertexAttribColor);

    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid *) offsetof(Vertex, Color));
}
....

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

     2. glVertexAttribPointer 參數說明:

        void glVertexAttribPointer(GLuint  index
                                   GLint  size
                                   GLenum  type
                                   GLboolean  normalized
                                   GLsizei  stride
                                   const GLvoid *pointer);

       a. index:
           即將被設定的 vertex shaderattribute 之索引位置,
           在此, 我們使用 GLKit 提供的 "預先定義好的" 常數值.
            (若不使用 GLKit 的話, 需呼叫 glGetAttribLocation 來取得)

       b. size:
           每個頂點屬性元素數量, 在此為:位置 3 個(x,y,z); 顏色 4 個(r,g,b,a).

       c. type:
           每個元素的資料型別, 在此, 位置顏色皆為 GL_FLOAT.

       d. normalized:
           是否要對頂點進行正規化處理, 在此為否: GL_FALSE

       e. stride:
           跨度: 二個連續頂點屬性之間的位元偏移量
           亦即, 包含個別頂點資料的資料結構之大小, 在此皆為: sizeof(Vertex)

       f. pointer:
           在儲存該屬性的資料結構中, 此屬性資料的起始偏移量
           在此, 可以使用 offsetof 運算子, 來找出在一個結構體中, 特定資料欄位
           起始偏移量.

           備註: 不然的話, 就要依照 struct 的內容來計算:  
            typedef struct {
            float Position[3];
            float Color[4];
            } Vertex;

            => Position 資料是位於 struct 的起始處, 因此偏移量為: 0.
            => Color 資料是位於 Position 資料之後, 因此偏移量為: sizeof(float) * 3.

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

E. 繪製幾何圖形
     1. 開啟 HelloGLKitViewController.m 檔案, 修改如下: 
....
- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(_curRed, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
   
    //@add
    [self.effect prepareToDraw];
   
    //@add: Enable pre-defined attributes
    ....
   
    //@add: Draw geometry
    glDrawElements(GL_TRIANGLES, sizeof(Indices)/sizeof(Indices[0]), GL_UNSIGNED_BYTE, 0);
}
....

     2. glDrawElements 參數說明:

        void glDrawElements(GLenum  mode
                           GLsizei  count
                           GLenum  type
                           const GLvoid *indices);

        a. mode:
            要繪製的基本圖形種類.

        b. count:
            要繪製的頂點數量.

        c. type:
            索引陣列中, 每個個別索引資料型別(GLubyte)

        d. indices:
            指向索引陣列的指標.

            備註:
            在此為特例, 由於使用了 Vertex Buffer Objects, 因此它會使用
            在 glkView:drawInRect: 函式中與 GL_ELEMENT_ARRAY_BUFFER 綁定,
            儲存在 Index Buffer object 裡的 Indices 陣列.

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

F. 編譯並執行:
     可以在螢幕上看到一個漂亮的旋轉方塊.
     (背景仍然是: 脈動式的 "緊急警報" 效果: 黑 -> 漸紅 -> 紅)

OpenGL ES 2.0 with GLKit Part 2 - 1

since: 2012/06/25
update: 2012/06/25


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

GLKBaseEffect 介紹

A. 建立簡單的正方形頂點資料
     1. 說明:
         a. 正方形的頂點將被設置如下:
             V0: 座標(1, -1), 顏色:
             V1: 座標(1, 1), 顏色:
             V2: 座標(-1, 1), 顏色:
             V3: 座標(-1, -1), 顏色:


         b. 在 OpenGL ES 中, 只能使用三角形來繪製幾何圖形, 所以我們可以利用
              二個三角形來產生正方形, 如上圖所示: 一個三角形使用頂點 V0, V1, V2;
              另一個三角形使用頂點 V2, V3, V0.
              (說明: 你可以將頂點資料組成任何產生三角形的方式)

     2. 開啟 HelloGLKitViewController.m 檔案, 修改如下:
#import "HelloGLKitViewController.h"


///////////////////////////////////////////////////////
//@start: add for vertex data */

// 用來儲存每個頂點的資訊: 位置(x,y,z), 顏色(r,g,b,a)
typedef struct {
    float Position[3];
    float Color[4];
} Vertex;

// 所有個別頂點的資訊:
// {位置1, 顏色1}, {位置2, 顏色2}, {位置3, 顏色3}, {位置4, 顏色4}
const Vertex Vertices[] = {
    {{1, -1, 0}, {1, 0, 0, 1}},
    {{1, 1, 0}, {0, 1, 0, 1}},
    {{-1, 1, 0}, {0, 0, 1, 1}},
    {{-1, -1, 0}, {0, 0, 0, 1}}
};

// 產生三角形的頂點索引:
//
// 每三個頂點組成一個三角形:
// 第一個三角形: 由頂點索引為 0, 1 與 2 所組成
// 第二個三角形: 由頂點索引為 2, 3 與 0 所組成
const GLubyte Indices[] = {
    0, 1, 2,
    2, 3, 0
};

//@ end: add for vertex data
///////////////////////////////////////////////////////
....

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

B. 建立 Vertex Buffer Objects

     1. 說明:
         a. 要將頂點資料送至 OpenGL, 最佳方式是經由頂點緩衝區物件
              (Vertex Buffer Objects).

         b. 根本上來說, Vertex Buffer Objects 是一種 OpenGL 物件, 用來將頂點資料
              儲存到緩衝區裡.

         c. 有二種類型的 vertex buffer objects, 一種用來保存(追蹤)每個頂點的資料
             (像之前的 Vertices 陣列); 另一種用來保存(追蹤)建立三角形的頂點索引
             (像之前的 Indices 陣列).

     2. 開啟 HelloGLKitViewController.h 檔案, 修改如下:
#import <GLKit/GLKit.h>

@interface HelloGLKitViewController : GLKViewController
{
}

//@add
- (void)setupGL;
- (void)tearDownGL;

@end

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

....
// step 2:
//@add
- (void)setupGL
{
    // Set the current OpenGL context to the current context.
    [EAGLContext setCurrentContext:self.context];
   
    // 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);
   
    glGenBuffers(1, &_indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);
}

....
// step 3:
//@add: delete the vertex and index buffers:
- (void)tearDownGL
{
    [EAGLContext setCurrentContext:self.context];
   
    glDeleteBuffers(1, &_vertexBuffer);
    glDeleteBuffers(1, &_indexBuffer);
}

....
// step 4:
- (void)viewDidLoad
{
....
    //@add
    [self setupGL];
}

....
// step 5:
- (void)viewDidUnload
{
    [super viewDidUnload];
    //add
    [self tearDownGL];
    ....
}

....

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

C. GLKBaseEffect 介紹

      1. 說明:
          a. 在 OpenGL ES 2.0 中, 要在場景繪製出任何的幾何圖形, 必須要建立二個
              稱為 shader 的微小程式.
              註: shader 常翻譯為著色器, 對 Fragment(像素/片段) shader 而言還算吻合;
                    但是對於主要處理頂點位置的 vertex(頂點) shader 就不貼切了.

          b. Shaders 以類似 C 語言的語法被寫成, 稱作 GLSL(OpenGL Shading Language),
              有二種基本類型的 shaders: Vertex shaderFragment shader.

          c. Vertex shader:(頂點著色器)
              在場景中的每個頂點都會呼叫一次 vertex shader 這個小程式. Vertex shader
              的工作是去執行某些計算, 例如: 光源位置, 幾何轉換(位移, 旋轉, 縮放)等,
              最後會算出每個頂點最終的位置, 並且傳遞一些資料給 fragment shader.

          d. Fragment shader:(像素著色器)
              同樣地, 在場景中的每個像素都會呼叫一次 fragment shader 這個小程式.
              Fragment shader 最主要的工作是設定每個像素最終的顏色.


          e. GLKBaseEffect 是一個輔助類別, 它為你實作了一些常用的 shaders.
              GLKBaseEffect 的目的是: 提供大部份在 OpenGL ES 1.0 中可用的功能,
              使 App 較容易從 OpenGL ES 1.0 轉成 OpenGL ES 2.0.

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

D. GLKBaseEffect 的使用步驟
      1. 建立一個 GLKBaseEffect
          通常, 當你建立 OpenGL context 時, 就會建立其中一個 GLKBaseEffect. 你能夠
          (並且應該) 對不同的幾何圖形再使用相同的 GLKBaseEffect, 並且只需重新設定
          屬性. 在場景的背後, GLKBaseEffect 只會散佈對 shaders 有所改變的那些屬性.

      2. 設定 GLKBaseEffect 屬性
          你可以設定: 光源, 轉換(位移, 旋轉, 縮放), 以及其它 GLKBaseEffect 的 shaders
          會用來繪製幾何圖形的屬性.

      3. 在 GLKBaseEffect 上呼叫 prepareToDraw
          任何時候, 你在 GLKBaseEffect 上更改一個屬性, 你需要先呼叫 prepareToDraw
          來讓 shaders 能正確地設定. 這也會賦予 GKBaseEffect 的 shaders 成為目前的
          shader program.

      4. 啟用 "預先定義好的" 的屬性
          通常, 當你建好你自己的 shaders, 它們帶有稱為 attributes參數, 並且你可以
          在 App 中寫程式碼來取得 attributes 參數的 IDs. 對 GLKBaseEffect 內建
          shaders 而言, 那些 attributes 參數是已經 "定義好的" 常數, 如: 
          GLKVertexAttribPositionGLKVertexAttribColor. 所以你需要啟用任何想要
          傳遞到 shaders 裡的參數, 並且將這些參數參照到資料.          

      5. 繪製你的幾何圖形
          一旦你都設定好之後, 就可以使用標準的 OpenGL 繪圖指令, 如: glDrawArrays
          或 glDrawElements, 它就會依照你設定的效果來繪製.  

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

E. 備註:
      1. GLKBaseEffect 最棒的一件事就是: 如果你使用它們, 你就不需要寫任何的
          shaders. 當然, 如果你想要也可以用混搭的方式: 用 GLKBaseEffect 來繪製
          某些圖形; 再用你自己寫的 shaders 繪製另外的圖形. 如果你看看 OpenGL
          的樣板專案, 就會看到這樣的例子.

     2. 在這篇教學文章中, 會將焦點放在只使用 GLKBaseEffect, 因為主要的目的是
          讓你能夠儘快的使用新的 GLKit 功能, 它是相當容易的.

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