2012年4月4日 星期三

OpenGL ES 入門: 三. 透視觀點

since: 2012/04/04
update: 2012/04/04

reference:
1. 原文:
iPhone Development: OpenGL ES From the Ground Up, Part 3: Viewports in Perspective

2. 翻譯:
從零開始學習OpenGL ES之三 – 透視

透視觀點

A. 說明
     1. viewport: 可看成是虛擬的相機鏡頭(觀察者)所照到的範圍.

     2. 在 OpenGL ES 中具有的兩種不同的 viewport 類型:
         正交(orthographic)和透視(perspective)

     3. (orthographic):
         這種類型的 viewport, 視線永遠不會交匯而且物體不會改變其大小. 沒有透視效果,
         看上去是不真實的. 對於正交 viewport, 就像你將攝影機置於鐵軌上, 但這些鐵軌永
         遠不會交匯. 它們將隨着遠離你的視線而繼續保持等距. 無論你定義了多大的
         viewport, 這些線仍保持等距. 正交 viewport 的優點是容易定義, 因為線永不交匯.

     4. 透視(perspective):
         物體會隨着移遠而越來越小, 視線會在物體移離觀察者時最終交匯. 這是對真實視覺
         的模擬; 人們就是以這種方式觀察世界的. 隨着視線越來越遠, 你可以看到更廣闊的
         世界, 所以如果你使用透視, 那麼你定義的空間將不是一個立方體, 而是稱為錐台
          (frustum) 的可見空間.

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

B. 正交 viewport 設定方式
    1. 說明: 在使用 glViewport() 函數定義 viewport 前, 你可以通過 glOrthof()
                   通知 OpenGL ES 你希望使用正交 viewport.

    2. 例子:
    // 獲取視窗的尺寸
   
CGRect rect = view.bounds;

    // 設定 viewport 空間的寬度為兩個單位, 沿 x 軸從 -1.0 到 +1.0
    glOrthof(-1.0,                                                 // Left
                      1.0,                                                 // Right

    // 設定底部和頂部
    //
    // 定義空間的 X 和 Y 坐標的寬高比與視窗的寬高比.
    // 確保 viewport 的 x 和 y 坐標遵循一樣的比例.
                     -1.0 / (rect.size.width / rect.size.height),   // Bottom
                      1.0 / (rect.size.width / rect.size.height),   // Top

    // 定義 near (遠) 和 far (近) 範圍來描述觀察的深度
    //
    // near 參數說明了 viewport 開始的位置. 如果我們站在原點處, viewport 就位於
    // 我們的面前, 所以習慣上使用 .01 或 .001 作為正交 viewport 的起點. 這使得
    // viewport 處於原點 "前方" 一點點.
    //
    // far 可以根據你程序的需要來設定. 如果你程序中的物體永遠不會遠過20個單位,
    // 那麼你不需要將 far 設置為 20,000 個單位. 具體的數字隨程序的不同而不同.
                      0.01,                                         // Near
                      10000.0);                                     // Far

    // 使用視窗矩形來呼叫 glViewport()
    glViewport(0, 0, rect.size.width, rect.size.height);

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

C. 透視 viewport 設定方式
     1. 說明: 
         a. 要設定透視 viewport , 我們使用 glFrustumf() 函數.

         b. 要計算錐台,我們首先要理解視野 (field of vision) 的概念, 它是由兩個角度
             定義的, 並決定了左右與上下的視野寬度(有窄視野寬視角之分).

         c. 如果用攝影術語描述, 你可將視野當作虛擬相機的虛擬光圈的焦距. 窄視野很像
             攝遠鏡頭, 它造就了一個緩慢增長的長錐台. 寬視角就像廣角鏡, 它造就了一個
             增長很快的錐台.

     2. 例子:
CGRect rect = view.bounds; 

// 取視角 (45度) 一半的正切值
GLfloat size = .01 * tanf(DEGREES_TO_RADIANS(45.0) / 2.0); 

glFrustumf(-size,                                           // Left
            size,                                           // Right
           -size / (rect.size.width / rect.size.height),     // Bottom
            size / (rect.size.width / rect.size.height),    // Top
            .01,                                           // Near
            1000.0);                                      // Far

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

D. 繪製透視多面體的前置作業
     1. 開啓 GLView.h 檔案, 修改如下:
....
@protocol GLViewDelegate

@required
- (void)setupView:(GLView *)view;

@optional
//@update for drawing
- (void)drawView:(GLView *)view;
- (void)drawTriangle3D; // 畫三角形
- (void)drawSquare; // 畫正方形
- (void)drawVertexColor; // 畫頂點顏色
- (void)drawIcosahedron; // 畫二十面體
- (void)drawPerspective; // 畫透視多面體

@end
....

     2. 開啓 ViewController.m 檔案, 修改如下:
....
// 畫透視多面體
- (void)drawPerspective
{
    NSLog(@"ViewController => drawPerspective");
    // Draw code here
}
....

     3. 開啓 GLView.m 檔案, 修改如下:
....
- (void)drawView
{
....
    //[self.delegate drawTriangle3D]; // 畫三角形
    //[self.delegate drawSquare]; // 畫正方形
    //[self.delegate drawVertexColor]; // 畫頂點顏色
    //[self.delegate drawIcosahedron]; // 畫二十面體
    [self.delegate drawPerspective]; // 畫透視多面體
....
}
....

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


E. 開啓 ViewController.m 檔案, 修改如下:
....
// 畫透視多面體
- (void)drawPerspective
{
    NSLog(@"ViewController => drawPerspective");
    // Draw code here
    //
    // 1. 建立了一個靜態變數來跟蹤物體的旋轉
    static GLfloat rot = 0.0;
   
    // 2. 定義頂點陣列:
    //
    // 使用了一個與前不同的方法, 但結果是一樣的. 由於我們的幾何體根本不會變化,
    // 所以我們將其定義為 const, 這樣就不需要每一 frame 都分配/清除記憶體:
    static const Vertex3D vertices[]= {
        {0, -0.525731, 0.850651},             // vertices[0]
        {0.850651, 0, 0.525731},              // vertices[1]
        {0.850651, 0, -0.525731},             // vertices[2]
        {-0.850651, 0, -0.525731},            // vertices[3]
        {-0.850651, 0, 0.525731},             // vertices[4]
        {-0.525731, 0.850651, 0},             // vertices[5]
        {0.525731, 0.850651, 0},              // vertices[6]
        {0.525731, -0.850651, 0},             // vertices[7]
        {-0.525731, -0.850651, 0},            // vertices[8]
        {0, -0.525731, -0.850651},            // vertices[9]
        {0, 0.525731, -0.850651},             // vertices[10]
        {0, 0.525731, 0.850651}               // vertices[11]
    };
   
    // 3. 建立一個顏色陣列:
    //
    // 創建一個 Color3D 物件陣列, 每項對應於前一個陣列的頂點:
    static const Color3D colors[] = {
        {1.0, 0.0, 0.0, 1.0},
        {1.0, 0.5, 0.0, 1.0},
        {1.0, 1.0, 0.0, 1.0},
        {0.5, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.0, 1.0},
        {0.0, 1.0, 0.5, 1.0},
        {0.0, 1.0, 1.0, 1.0},
        {0.0, 0.5, 1.0, 1.0},
        {0.0, 0.0, 1.0, 1.0},
        {0.5, 0.0, 1.0, 1.0},
        {1.0, 0.0, 1.0, 1.0},
        {1.0, 0.0, 0.5, 1.0}
    };
   
    // 4. 建立二十面體:
    //
    // 上述十二個頂點本身並未描述形狀. OpenGL 需要知道怎樣將它們聯繫在一起,
    // 所以我們建立了一個整數陣列 (GLubyte) 指向構成各三角形的頂點.
    //
    // 二十面體的第一個面的三個數是 1,2,6 代表繪製處於索引1 (0.850651, 0, 0.525731),
    // 2 (0.850651, 0, 0.525731), 和 6 (0.525731, 0.850651, 0) 之間的三角形.
    static const GLubyte icosahedronFaces[] = {
        1, 2, 6,
        1, 7, 2,
        3, 4, 5,
        4, 3, 8,
        6, 5, 11,
        5, 6, 10,
        9, 10, 2,
        10, 9, 3,
        7, 8, 9,
        8, 7, 0,
        11, 0, 1,
        0, 11, 4,
        6, 2, 10,
        1, 6, 11,
        3, 5, 10,
        5, 4, 11,
        2, 7, 9,
        7, 1, 0,
        3, 9, 8,
        4, 8, 0,
    };
   
    // 5.
    //
    // 加載單元矩陣
    glLoadIdentity();
   
    // 移動
    //glTranslatef(0.0f,0.0f,-3.0f);
   
    // 旋轉
    //glRotatef(rot,1.0f,1.0f,1.0f);   
   
    // 設置背景色
    glClearColor(0.7, 0.7, 0.7, 1.0);
   
    // 清除緩存
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    // 啟動頂點陣列
    glEnableClientState(GL_VERTEX_ARRAY);
   
    // 啟動顏色陣列
    glEnableClientState(GL_COLOR_ARRAY);
   
    // 提供頂點陣列內容給 OpenGL
    glVertexPointer(3, GL_FLOAT, 0, vertices);
   
    // 提供顏色陣列內容給 OpenGL
    glColorPointer(4, GL_FLOAT, 0, colors);
   
    for (int i = 1; i <= 30; i++)
    {
        glLoadIdentity();
       
        // 移動
        glTranslatef(0.0f,-1.5,-3.0f * (GLfloat)i);
       
        // 旋轉
        glRotatef(rot, 1.0, 1.0, 1.0);
       
        // 6. 不使用 glDrawArrays(), 而是使用 glDrawElements():
        //
        // 備註: 如果你按繪製的正確次序提供頂點, 那麼你應該使用 glDrawArrays(),
        //       但是如果你提供一個陣列然後用另一個以索引值區分頂點次序的陣列的話,
        //       那麼你應該使用 glDrawElements().
        glDrawElements(GL_TRIANGLES, 60, GL_UNSIGNED_BYTE, icosahedronFaces);
    }
   
    // 7. 執行禁止功能
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_COLOR_ARRAY);
   
    // 8. 根據上一個 frame 繪製的時間增加旋轉變量值:
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot += 50 * timeSinceLastDraw;
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
....
//@add for <GLViewDelegate> method

-(void)setupView:(GLView *)view
{
    NSLog(@"step 05. ViewController => setupView:");
   
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0; // 設定為 45 度視野
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
   
    CGRect rect = view.bounds;

    // 設定透視 viewport (基於視野角度計算錐台)
    glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
   
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    glLoadIdentity();
}
....

    編譯並執行:
    隨着多面體遠離你, 它們會變得越來越小, 正像火車鐵軌一樣.

---------------------------------------------------------------------------------
F. 正交多面體的情況
      1. 開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view
{
    NSLog(@"step 05. ViewController => setupView:");
   
    const GLfloat zNear = 0.01, zFar = 1000.0, fieldOfView = 45.0; // 設定為 45 度視野
    GLfloat size;
    glEnable(GL_DEPTH_TEST);
    glMatrixMode(GL_PROJECTION);
    size = zNear * tanf(DEGREES_TO_RADIANS(fieldOfView) / 2.0);
   
    CGRect rect = view.bounds;

    // 設定透視 viewport (基於視野角度計算錐台)
    /*
    glFrustumf(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
    */
   
    // 設定正交 viewport (基於視野角度計算錐台)
    /* // not work
    glOrthof(-size, size, -size / (rect.size.width / rect.size.height), size /
               (rect.size.width / rect.size.height), zNear, zFar);
    */
    glOrthof(-1.0,                                // Left
             1.0,                                          // Right
             -1.0 / (rect.size.width / rect.size.height),   // Bottom
             1.0 / (rect.size.width / rect.size.height),   // Top
             0.01,                                         // Near
             10000.0);                                     // Far   
    
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    glLoadIdentity();
}
....

      2. 編譯並執行:
    說明: 沒有透視. 第一個二十面體後面的二十九個二十面體完全被第一個擋住了.
               因為沒有透視, 後面的各幾何體的形狀完全取決於其前方的物體.

沒有留言:

張貼留言

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