2012年4月8日 星期日

OpenGL ES 入門: 四. 光效之一

since: 2012/04/05
update: 2012/04/08

reference:
1. 原文:
iPhone Development: OpenGL ES From the Ground Up, Part 4: Let There Be Light!

2. 翻譯:
從零開始學習OpenGL ES之四 – 光效

3. 清大資工-楊熙年老師-多媒體技術與應用-圖形

光效之一: 啟動光效與光源, 光源屬性

A. 說明:
     1. OpenGL 在沒有設置光效的情況下仍然可以看見東西, 它只提供一種十分單調
        的整體光.

     2. Shade Model(陰影模型):
          OpenGL ES 定義了兩種 shade model, GL_FLATGL_SMOOTH.

          a. 從發光的角度來看, GL_FLAT 將指定三角形上的每個像素都同等對待.
              多邊形上的每個像素都具有相同的顏色, 陰影等. 它提供了足夠的視覺暗示
              使其看上去有立體感而且它的計算比每個像素按不同方法計算更為廉價,
              但是在這種方式下, 物體看上去極為不真實. 因此, 將不會討論 GL_FLAT.

          b. 要讓 3D 物體盡量真實, 應該使用 GL_SMOOTH 繪圖模式, 它使用了一種
              平滑但較快速的陰影算法, 稱為 Gouraud 算法.  GL_SMOOTH 是繪圖模式
              的預設值.

              備註: Gouraud 著色法是以發明人來命名, 這個方法和平面著色法相差並不多,
                        不同的地方在於 Gouraud 著色法是計算表面上每個端點的方向向量
                        計算該點的亮度, 再利用內插法 (interpolation) 計算出面上每個點 (pixel)
                        的亮度. 利用這個方法, 可讓面與面之間的接合處並不那麼明顯. 但這個
                        方法也會使物體看起來較為模糊, 同時需時更長的計算時間. (reference 3.)

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

B. 繪製多面體光效的前置作業
      1. 說明: 我們將參考 OpenGL ES 新手入門: 二. 簡單繪圖之五 的畫圖為基礎,
                    來新增另一個畫圖的方法.

      2. 開啓 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; // 畫透視多面體
- (void)drawLight; // 畫多面體光效

@end
....

      3. 開啓 ViewController.m 檔案, 修改如下:
....
// 畫多面體光效
- (void)drawLight
{
    NSLog(@"ViewController => drawLight");
    // Draw code here
    //@update
    //
    // 1. 建立了一個靜態變數來跟蹤物體的旋轉
    static GLfloat rot = 0.0;
   
    // This is the same result as using Vertex3D, just faster to type and
    // can be made const this way
    //
    // 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);
   
    // 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];
}
....

      4. 開啓 GLView.m 檔案, 修改如下:
....
- (void)drawView
{
    NSLog(@"step 06. GLView => drawView");
   
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFrameBuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderBuffer);
   
    // GO: step 07. ViewController => drawView:
    //[self.delegate drawView:self];
    //@update for drawing
    //[self.delegate drawTriangle3D]; // 畫三角形
    //[self.delegate drawSquare]; // 畫正方形
    //[self.delegate drawVertexColor]; // 畫頂點顏色
    //[self.delegate drawIcosahedron]; // 畫二十面體
    //[self.delegate drawPerspective]; // 畫透視多面體
    [self.delegate drawLight]; // 畫多面體光效
   
    [self.glContext presentRenderbuffer:GL_RENDERBUFFER_OES];
   
    //@add for Rendering Frequency
    int intCount = [self.currentRunCount intValue] + 1;
    self.currentRunCount = [NSNumber numberWithInt:intCount];
   
    if (intCount >= self.totalRunTime * kRenderingFrequency) {

        [self stopAnimation];
    }
   
    //@update for run once
    //[self stopAnimation];
}
....

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

C. 啟動光效
      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);
   
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    glLoadIdentity();
}
....

    說明: 通常, 光效只需在設定時啟動一次. 不需要在繪圖開始前後打開和關閉.
              可能有些特效的情況需要在程序執行時打開或關閉, 但是大部分情況下,
              你只需在程序啟動時打開它.

      2. 編譯並執行:
    啟動了光效, 但是沒有創建任何光源. 因此, 繪製的物體都被渲染成絕對的黑色.

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

D. 啟動光源
      1. 說明: OpenGL ES 允許你創建 8 個光源. 有一個常數對應於這些光源中的一個,
                   常數為 GL_LIGHT0GL_LIGHT7 . 可以任意組合這些光源中的五個,
                   儘管習慣上從 GL_LIGHT0 作為第一個光源, 然後是 GL_LIGHT1 等等.

      2. 開啓 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);
   
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height); 
   
    glMatrixMode(GL_MODELVIEW);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動光源
    glEnable(GL_LIGHT0);
   
    glLoadIdentity();
}
....

      3. 編譯並執行:


    說明: 結果為 "" 轉 "" 轉 "" 的循環效果, 需要再設定光源的一些屬性.

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

E. 光源屬性
     1. 說明:
          a. 在 OpenGL ES中, 光由三個元素組成, 分別是環境元素(ambient component),
              散射元素(diffuse component)和鏡射元素(specular component)

          b. 鏡射元素(specular component):
              鏡射元素定義了光線直接照射並反射到觀察者從而形成了物體上的 "熱點"
              或光澤. 

          c. 散射元素(diffuse component):
              散射元素定義了比較平均定向光源, 在物體面向光線的一面具有光澤.

          d. 環境元素(ambient component):
              環境光則沒有明顯的光源. 其光線折射於許多物體, 因此無法確定其來源.
              環境元素平均作用於場景中的所有物體的所有面.

     2. 環境光
         a. 說明: 在光效中有越多的環境元素, 那麼就越不會產生引人注目的效果. 所有
                       光線的環境元素會融合在一起產生效果
, 意思是場景中的總環境光效是由
                       所有啟動光源環境光組合在一起所決定的, 如果你使用了不止一個光源,
                       那麼最好是只指定一個光源的環境元素, 而設定其他所有光源的環境因素
                       為黑 ({0.0, 0.0, 0.0, 1.0}), 從而很容易地調整場景的環境光效.

         b. 例子:
             指定一個很暗的白色光源:
             const GLfloat light0Ambient[] = {0.05, 0.05, 0.05, 1.0};
             glLightfv(GL_LIGHT0, GL_AMBIENT, light0Ambient);

             備註: 使用像這樣的很低的環境元素值使場景看上去更引人注目, 但同時也
                       意味着物體沒有面向光線的面或者有其他物體擋住的物體將在場景中
                       看得不是很清楚.

     3. 散射光
          a. 說明: 在現實世界中, 散射光線是諸如穿透光纖或從一堵白牆反射的光線.
                        散射光線是發散的, 因而參數較柔和的光, 一般不會像直射光一樣產生光斑.
                        在 OpenGL ES 中, 散射元素它使光線均勻地散布到物體之上. 然而, 不像
                        環境光, 由於它是定向光, 只有面向光線的物體面才會反射散射光, 而場景中
                        的所有多面體都會被環境光照射.

          b. 例子:
             設定場景中的第一個散射元素:
             const GLfloat light0Diffuse[] = {0.5, 0.5, 0.5, 1.0};
             glLightfv
(GL_LIGHT0, GL_DIFFUSE, light0Diffuse);


     4. 鏡射光
          a. 說明: 這種類型的光是十分直接的, 它們會以熱點光暈的形式反射到觀察者
                        的眼中. 如果你想產生聚光燈的效果, 那麼應該設置一個很大的鏡射元素
                        值及很小的散射和環境元素值(還需要定義其它一些參數).

           b. 例子:
               const GLfloat light0Specular[] = {0.7, 0.7, 0.7, 1.0};
               glLightfv(GL_LIGHT0, GL_SPECULAR, light0Specular);

沒有留言:

張貼留言

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