2013年8月29日 星期四

Rendering 3D Anaglyph in OpenGL - 6

since: 2013/08/26
update: 2013/08/29

reference:
0. 原文: Rendering 3D Anaglyph in OpenGL
1. I touchs: Rendering 3D Anaglyph in OpenGL - 1
2. I touchs: Rendering 3D Anaglyph in OpenGL - 2
3. I touchs: Rendering 3D Anaglyph in OpenGL - 3
4. I touchs: Rendering 3D Anaglyph in OpenGL - 4
5. I touchs: Rendering 3D Anaglyph in OpenGL - 5

I.
Mac OS X 實作

    1. 新增專案:
        Xcode > File > New > Project... > OS X > Application >
        Command Line Tool > Next

       Product Name: 3DAnaglyph       
       Company Identifier: com.blogspot
       Type: C++
       Use Automatic Reference Counting: checked
       > Next > Create

    2. 新增 Framework:
       OpenGL
        - OpenGL is the premier environment for developing portable,
           interactive 2D and 3D graphics applications.

       GLUT
        - OpenGL Utility Toolkit

    3. 新增檔案:
        a. 立體攝影機類別:
            Xcode > File > New > File...
            > OS X > C and C++ > C++ Class > Next


              Save As: StereoCamera.cpp
              Targets: 3DAnaglyph (勾選)
              > Create

        b. 繪圖函式:
            Xcode > File > New > File...
            > OS X > C and C++ > C++ Class > Next


              Save As: draw.cpp
              Targets: 3DAnaglyph (勾選)
              > Create

    4. 整個專案檔案架構如下:

    5. 新增: 立體攝影機
        a. 開啓 StereoCamera.h 檔案, 修改如下:
#ifndef ___DAnaglyph__StereoCamera__
#define ___DAnaglyph__StereoCamera__

#include <iostream>

//@add
#include <OpenGL/OpenGL.h> // OpenGL Library
#include <GLUT/GLUT.h> // OpenGL Utility Toolkit
#include <math.h>


#endif /* defined(___DAnaglyph__StereoCamera__) */

        b. 開啓 StereoCamera.cpp 檔案, 修改如下:
#include "StereoCamera.h"

//@add
class StereoCamera
{
public:
   
    // 建構子
    StereoCamera(
                 float Convergence, // 收斂距離
                 float EyeSeparation, // 二眼間隔
                 float AspectRatio, // 寬高比
                 float FOV, // 視野角度
                 float NearClippingDistance, // 近截距
                 float FarClippingDistance // 遠截距
                 )
    {
        mConvergence            = Convergence;
        mEyeSeparation          = EyeSeparation;
        mAspectRatio            = AspectRatio;
        //mFOV                    = FOV * PI / 180.0f;
        //@update
        mFOV                    = FOV * M_PI / 180.0f;
        mNearClippingDistance   = NearClippingDistance;
        mFarClippingDistance    = FarClippingDistance;
    }
   
    // 套用左視錐
    void ApplyLeftFrustum()
    {
        float top, bottom, left, right;
        top     = mNearClippingDistance * tan(mFOV/2);
        bottom  = -top;
        float a = mAspectRatio * tan(mFOV/2) * mConvergence;
        float b = a - mEyeSeparation/2;
        float c = a + mEyeSeparation/2;
        left    = -b * mNearClippingDistance/mConvergence;
        right   =  c * mNearClippingDistance/mConvergence;
       
        // 設置投影矩陣 (會後做)
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glFrustum(left, right, bottom, top, mNearClippingDistance, mFarClippingDistance);
       
        // 設置 模型/視野 矩陣 (會先做)
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        // 將世界座標往右位移: 二眼間隔的一半距離
        glTranslatef(mEyeSeparation/2, 0.0f, 0.0f);
    }
   
    // 套用右視錐
    void ApplyRightFrustum()
    {
        float top, bottom, left, right;
        top     = mNearClippingDistance * tan(mFOV/2);
        bottom  = -top;
        float a = mAspectRatio * tan(mFOV/2) * mConvergence;
        float b = a - mEyeSeparation/2;
        float c = a + mEyeSeparation/2;
        left    =  -c * mNearClippingDistance/mConvergence;
        right   =   b * mNearClippingDistance/mConvergence;
       
        // 設置投影矩陣 (會後做)
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity(); 
        glFrustum(left, right, bottom, top, mNearClippingDistance, mFarClippingDistance);
       
        // 設置 模型/視野 矩陣 (會先做)
        glMatrixMode(GL_MODELVIEW);                   
        glLoadIdentity(); 
        // 將世界座標往左位移: 二眼間隔的一半距離
        glTranslatef(-mEyeSeparation/2, 0.0f, 0.0f);
    }
   
    // 內部變數
private:
    float mConvergence;
    float mEyeSeparation;
    float mAspectRatio;
    float mFOV;
    float mNearClippingDistance;
    float mFarClippingDistance;
};


    6. 新增: 繪圖函式
        a. 開啓 draw.h 檔案, 修改如下:
#ifndef ___DAnaglyph__draw__
#define ___DAnaglyph__draw__

//#include <iostream>
//@add
#include "StereoCamera.cpp"

#endif /* defined(___DAnaglyph__draw__) */

//@add

void DrawGLScene();
void PlaceSceneElements();


        b. 開啓 draw.cpp 檔案, 修改如下:
//@add
// 主要的繪圖函式

void DrawGLScene()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    // 設定立體攝影機系統
    StereoCamera cam(
                     2000.0f,     // Convergence (收斂距離)
                     35.0f,       // Eye Separation (二眼間隔)
                     1.3333f,     // Aspect Ratio (寬高比)
                     45.0f,       // FOV along Y in degrees (沿著 Y 軸的視野角度)
                     10.0f,       // Near Clipping Distance (近截距)
                     20000.0f);   // Far Clipping Distance (遠截距)
   
    cam.ApplyLeftFrustum();
    glColorMask(true, false, false, false); // 只將紅色寫入緩沖區(紅色遮罩)
    PlaceSceneElements(); // 在場景中放入物體
   
    glClear(GL_DEPTH_BUFFER_BIT) ;
   
    cam.ApplyRightFrustum();
    glColorMask(false, true, true, false); // 只將綠色及藍色寫入緩沖區(綠藍色遮罩)
    PlaceSceneElements(); // 在場景中放入物體
   
    glColorMask(true, true, true, true); // 恢復將所有色彩寫入緩沖區
   
    //@add: flush!
    glFlush();
   
    //@add: swap the double buffer
    glutSwapBuffers();
}

//@add
// 在場景中放入物體

void PlaceSceneElements()
{
    // 沿著負 Z 軸, 位移到適當的深度
    glTranslatef(0.0f, 0.0f, -1800.0f);
   
    // 旋囀場景以便觀看
    glRotatef(-60.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(-45.0f, 0.0f, 0.0f, 1.0f);
   
    // 繪製相交的環形圓紋曲面(tori)
    glPushMatrix();
    glTranslatef(0.0f, 0.0f, 240.0f);
    glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
    glColor3f(0.2, 0.2, 0.6);
    glutSolidTorus(40, 200, 20, 30);
    glColor3f(0.7f, 0.7f, 0.7f);
    glutWireTorus(40, 200, 20, 30);
    glPopMatrix();
   
    glPushMatrix();
    glTranslatef(240.0f, 0.0f, 240.0f);
    glColor3f(0.2, 0.2, 0.6);
    glutSolidTorus(40, 200, 20, 30);
    glColor3f(0.7f, 0.7f, 0.7f);
    glutWireTorus(40, 200, 20, 30);
    glPopMatrix();
}


    7. 修改主程式:
        開啓 main.cpp 檔案, 修改如下:
//#include <iostream>
//@add

#include "draw.h"

//@add: initialize OpenGL state
void init()
{
    // This determines the background color, in this case: gray
    // state: current clear color

    glClearColor(0.5, 0.5, 0.5, 1.0); // R, G, B, A
   
    // Determines which matrix to apply operations to.
    // value: GL_PROJECTION or GL_MODELVIEW

    glMatrixMode(GL_PROJECTION); // default
   
    // Loads the Identity matrix
    glLoadIdentity();
   
    // 投影(Projection)
    //
    // default: 正投影(Orthographic Projection)

}


//int main(int argc, const char * argv[])
//@update: just remove "const"
int main(int argc, char * argv[])
{
    // insert code here...
    //std::cout << "Hello, World!\n";

   
    // Init glut
    glutInit(&argc, argv);
   
    // Display mode
    int mode = GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH;
    glutInitDisplayMode(mode);
   
    // Init Window
    glutInitWindowSize(800, 600);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("3D Anaglyph");
   
    // initialize: set OpenGL state
    init();
   
    // Callback Functions Registration
    glutDisplayFunc(DrawGLScene); // drawing
   
    // enter event processing loop
    glutMainLoop();
   
    // will not be executed
    return 0;
}


    8. 編譯並執行:

Rendering 3D Anaglyph in OpenGL - 5

since: 2013/08/25
update: 2013/08/29

reference:
0. 原文: Rendering 3D Anaglyph in OpenGL
1. I touchs: Rendering 3D Anaglyph in OpenGL - 1
2. I touchs: Rendering 3D Anaglyph in OpenGL - 2
3. I touchs: Rendering 3D Anaglyph in OpenGL - 3
4. I touchs: Rendering 3D Anaglyph in OpenGL - 4

H.
程式碼片斷

      1. 這裡有一個片段的程式碼, 用來展示如何將之前的方程式包裹成一個
          小類別 StereoCamera:(立體攝影機)

class StereoCamera
{
public:
    // 建構子
    StereoCamera(  
        float Convergence, // 收斂距離
        float EyeSeparation, // 二眼間隔
        float AspectRatio, // 寬高比
        float FOV, // 視野角度
        float NearClippingDistance, // 近截距
        float FarClippingDistance // 遠截距
        )
    {
        mConvergence            = Convergence;
        mEyeSeparation          = EyeSeparation;
        mAspectRatio            = AspectRatio;
        mFOV                    = FOV * PI / 180.0f;
        mNearClippingDistance   = NearClippingDistance;
        mFarClippingDistance    = FarClippingDistance;
    }

    // 套用左視錐
    void ApplyLeftFrustum()
    {
        float top, bottom, left, right;

        top     = mNearClippingDistance * tan(mFOV/2);
        bottom  = -top;

        float a = mAspectRatio * tan(mFOV/2) * mConvergence;

        float b = a - mEyeSeparation/2;
        float c = a + mEyeSeparation/2;

        left    = -b * mNearClippingDistance/mConvergence;
        right   =  c * mNearClippingDistance/mConvergence;

        // 設置投影矩陣 (會後做)
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();  
        glFrustum(left, right, bottom, top,
                  mNearClippingDistance, mFarClippingDistance);

        // 設置 模型/視野 矩陣 (會先做)      
        glMatrixMode(GL_MODELVIEW);                    
        glLoadIdentity();  
        // 將世界座標往右位移: 二眼間隔的一半距離
        glTranslatef(mEyeSeparation/2, 0.0f, 0.0f);      
    }

    // 套用右視錐
    void ApplyRightFrustum()
    {
        float top, bottom, left, right;

        top     = mNearClippingDistance * tan(mFOV/2);
        bottom  = -top;

        float a = mAspectRatio * tan(mFOV/2) * mConvergence;

        float b = a - mEyeSeparation/2;
        float c = a + mEyeSeparation/2;

        left    =  -c * mNearClippingDistance/mConvergence;
        right   =   b * mNearClippingDistance/mConvergence;

        // 設置投影矩陣 (會後做)
        glMatrixMode(GL_PROJECTION);                       
        glLoadIdentity();  
        glFrustum(left, right, bottom, top,
                  mNearClippingDistance, mFarClippingDistance);

        // 設置 模型/視野 矩陣 (會先做)
        glMatrixMode(GL_MODELVIEW);                    
        glLoadIdentity();  
        // 將世界座標往左位移: 二眼間隔的一半距離
        glTranslatef(-mEyeSeparation/2, 0.0f, 0.0f);
    }

// 內部變數
private:
    float mConvergence;
    float mEyeSeparation;
    float mAspectRatio;
    float mFOV;
    float mNearClippingDistance;
    float mFarClippingDistance;
};

      2. 程式碼精確地做到了先前我們對圖示與方程式所作的描述. 一旦你建立了
          StereoCamera
物件, 就可以呼叫 ApplyLeftFrustum() 與 ApplyRightFrustum()
          來各別設置非對稱的視錐. 請注意, 在這些方法中, 實際上會先作 模型/視野 的
          轉置(沿著 X 軸作位移), 然後接著才是作投影的轉置.(與實際的程式碼流程相反)
          這樣的作用是: 將攝影機從原點位移一段距離. 如此, 在 OpenGL 中, 攝影機本身
          沒有做任何的轉置. 我們所做的是: 使用 模型/視野 的轉置, 將世界座標相對於
          概念上的攝影機向某個方向移動.           

      3. 為了要使用上方的類別, 你可以撰寫如下的 OpenGL 繪圖函式:

// 主要的繪圖函式
void DrawGLScene(GLvoid)                                   
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 設定立體攝影機系統
    StereoCamera cam(
        2000.0f,     // Convergence (收斂距離)
        35.0f,       // Eye Separation (二眼間隔)
        1.3333f,     // Aspect Ratio (寬高比)
        45.0f,       // FOV along Y in degrees (沿著 Y 軸的視野角度)
        10.0f,       // Near Clipping Distance (近截距)
        20000.0f);   // Far Clipping Distance (遠截距)

    cam.ApplyLeftFrustum();
    glColorMask(true, false, false, false); // 只將紅色寫入緩沖區(紅色遮罩)

    PlaceSceneElements(); // 在場景中放入物體

    glClear(GL_DEPTH_BUFFER_BIT) ;

    cam.ApplyRightFrustum();
    glColorMask(false, true, true, false); // 只將綠色及藍色寫入緩沖區(綠藍色遮罩)

    PlaceSceneElements(); // 在場景中放入物體

    glColorMask(true, true, true, true); // 恢復將所有色彩寫入緩沖區
}

// 在場景中放入物體
void PlaceSceneElements()
{
   // 沿著負 Z 軸, 位移到適當的深度
    glTranslatef(0.0f, 0.0f, -1800.0f);

    // 旋囀場景以便觀看
    glRotatef(-60.0f, 1.0f, 0.0f, 0.0f);
    glRotatef(-45.0f, 0.0f, 0.0f, 1.0f);

    // 繪製相交的環形圓紋曲面(tori)
    glPushMatrix();
        glTranslatef(0.0f, 0.0f, 240.0f);
        glRotatef(90.0f, 1.0f, 0.0f, 0.0f);
        glColor3f(0.2, 0.2, 0.6);
        glutSolidTorus(40, 200, 20, 30);
        glColor3f(0.7f, 0.7f, 0.7f);
        glutWireTorus(40, 200, 20, 30);
    glPopMatrix();

    glPushMatrix();
        glTranslatef(240.0f, 0.0f, 240.0f);
        glColor3f(0.2, 0.2, 0.6);
        glutSolidTorus(40, 200, 20, 30);
        glColor3f(0.7f, 0.7f, 0.7f);
        glutWireTorus(40, 200, 20, 30);
    glPopMatrix();
}

      4. 一開始的繪圖函式先清除顏色與深度的緩衝區. 然後, 設置立體攝影機系統.
          套用左邊的視錐, 並告知 OpenGL 在顏色緩沖區中, 只允許有紅色的成分.
          接著, 呼叫一般的程序來繪製場景. 之後, 清除深度緩衝區, 但是保留顏色緩衝區
          (在其中只會有紅頻的值). 隨著深度緩衝區清除後, 我們啓用右邊的視錐, 並告知
          OpenGL 在顏色緩沖區中, 只允許有綠色和藍色的成分. 然後, 再一次呼叫一般的
          繪製程序. 注意, 左眼所看到的色彩場景顏色與右眼所看到的色彩場景, 在整個
         色彩空間中, 並沒有重疊到, 因此不需要明確地對顏色作混色(blending)或堆積
         (accumulation)的處理. 最後, 啓用所有色頻, 場景就會被繪製成浮雕的樣子.  
         注意到, 我們必須畫二次幾何圖形. 代表著: 畫面更新率(率 frame rate) 降為
         單一視錐的一半. 這是典型的立體繪製. 如果你想知道上面的程式碼片斷輸出的
         結果為何, 就是這樣子: 

                                              圖十: 上方程式列輸出的繪製結果

      5. 我(原作者)有留下一段影片, 是在撰寫此篇(稍微冗長)教學文章時所作的.
          這段影片使用我先前提過的相同理論與程式碼. 試著用 720p 全螢幕來觀看,
          以獲得最佳的效果:
           ▶ Rendering Stereoscopic 3D Anaglyph in OpenGL - YouTube

      6. 延伸閱讀:
          a. Paul Bourke: Information related to stereographics
          b. NVIDIA GTC 2010: Implementing Stereoscopic 3D in Your Applications

          盡情享受!

Rendering 3D Anaglyph in OpenGL - 4

since: 2013/08/24
update: 2013/08/29

reference:
0. 原文: Rendering 3D Anaglyph in OpenGL
1. I touchs: Rendering 3D Anaglyph in OpenGL - 1
2. I touchs: Rendering 3D Anaglyph in OpenGL - 2
3. I touchs: Rendering 3D Anaglyph in OpenGL - 3

G.
立體視錐變量

      1. 到此為止, 我們已經有充分的認識, 可以計算立體視錐的變量, 以用在將來的
          OpenGL 程式中. 觀察以下的圖示:

                                                      圖九: 計算視錐變量

      2. 雖然上面這張圖看起來蠻可怕的, 但是實際上並沒有加入新的資料. 如果, 到
          目前為止你都瞭解所有討論的東西, 你將可以輕而易舉地從頭到尾聽懂這簡單
          的計算. 如之前所述, 二個攝影機位在 X 軸上的 L 點與 R 點. L與R這二點的間隔
          為 Deye, 並且它們的位移是對稱於原點的. 二台攝影機的方向是平行的, 都是
         朝著負 Z 軸方向. 視錐的近截距(near clipping distance)為 Dnear , 收斂距離為 C.
         從頂視圖來看, 虛擬螢幕的二個端點為 A 與 B. A 點為二個視錐的左邊會合之處,        
         而 B 點為二個視錐的右邊會合之處.

      3. 在 OpenGL 中, 建立非對稱視錐的唯一方式是經由 glFrustum(). 而 
           gluPerspective() 只能建立對稱的視錐, 因此在此例中不能使用.
           gluPerspective() 採用自然觀看的變量, 例如: 沿著 Y 方向的視野
           (field of view) θFOVY (見: 圖. 七(a)), 以及視錐的寬高比(aspect ratio),
           近截距和遠截距. 然而, 對於 glFrustum(), 你必須要提供近截面
           (near clipping plane)的上, 底, 左與右邊的座標, 而且還要有近截距和遠截距.
           我們將會從上方的二個視錐幾何中, 計算這些變量.

      4. 在圖中, 對應於虛擬螢幕的單一視錐等同於 AOB. 其沿著 Y 方向的視野為:
          θFOVY , 並且寬高比為 raspect (在 gluPerspective() 中也是如此).
         
      5. glFrustum() 函式:
          glFrustum( GLdouble leftX, GLdouble rightX,
                                GLdouble bottomY, GLdouble topY,
                                GLdouble nearZ, GLdouble farZ);


      6. 所以, glFrustum() 中的頂部和底部座標計算為: 

top=DneartanθFOVY2


bottom=top

          這些值, 同時適用於左邊和右邊的視錐.

          虛擬螢幕的一半寬度 a 為:

a=raspectCtanθFOVY2

         接著, 看看左邊的視錐 ALB. 近截面與 LL 左邊相交的距離為 dleft , 而與 LL
         右邊相交的距離為 dright. 在三角形 ΔALL 與三角形 ΔBLL中,

dleftb=drightc=DnearC
         而且, 我們也知道:
b=aDeye2

c=a+Deye2

         所以, 我們就能夠很快地計算出 dleftdright. 同樣地, 在右邊的視錐 ARB 中,
         藉由交換 b 與 c 的值, 就可以獲得 dleftdright 的值. 

Rendering 3D Anaglyph in OpenGL - 3

since: 2013/08/22
update: 2013/08/29

reference:
0. 原文: Rendering 3D Anaglyph in OpenGL
1. I touchs: Rendering 3D Anaglyph in OpenGL - 1
2. I touchs: Rendering 3D Anaglyph in OpenGL - 2

F.
OpenGL 攝影機:

     1. 剩下唯一要討論的任務是: 如何在 OpenGL 裡設置雙重的攝影機. 在非立體模式,
         你只需要一個攝影機, 其觀看的參數由 glFrustum()gluPerspective() 所定義.
         所得到的視錐(平截頭體 frustum)看起來如下所示:  

                                                      圖七(a): 單一視錐

                                                      圖七(b): 單一視錐(從上方垂直觀看)

     2. 然而, 繪製出立體感需要設置雙重的攝影機, 類似以下的狀況: 

                                                      圖八(a): 立體表現的雙攝影機系統

     3. 在這個結構當中, 我們有二個視錐: 一個來自 L 點(提供給左眼), 另一個來自R 點
         (
提供給右眼). 距離 LR 為兩眼間隔, 所以 L 點跟 R 點分別沿著 X 軸的原點位移
         了負 LR/2 與正
LR/2 的量. 觀察力敏銳的讀者, 可能已經注意到上圖的二個視錐,
        
與之前展示的
單一視錐是不同的, 我們並不是將單一視錐沿著 X 軸位移來得到雙
         攝影機系統的.
事實上, 上方顯示的這二個
視錐是非對稱的(asymmetric), 而單一
         視錐是
對稱的(symmetric)(p.s.幾何形狀, 請看下二張圖來更加瞭解).   
         如果這二個視錐是對稱的, 並且在之後作了橫向位移, 它們將不會收斂在一起. 
         非對稱才會致使這二個視錐在收斂距離處會合. 位在收斂距離上的洋紅色矩形
         代表
立體上的虛擬螢幕. 任何在虛擬螢幕上的頂點, 顯示上是零視差的. 而接近
         或遠離這個距離的頂點, 會產生適量的負或正視差.


     4.下面的圖, 顯示從頂部垂直觀看同一個的系統:

                                                      圖八(b): 立體視錐 (從頂部垂直觀看)

     5. 上方, 可以明顯地看出視錐的不對稱. 並且注意到, 每個視錐的觀看方向是相互
         平行的, 同時也與 Z 軸平行,
單一視錐也是一樣. 這是設置一對立體攝影機的正確
         方式. 還有另一種設置雙重攝影機的方式稱作: 內八字(toed-in)攝影機, 它採用
         對稱的視錐, 但是左右的觀看方向互為一個角度. 這足以說明, 內八字(toed-in)
         攝影機的設置是不正確的.  
 

     6. 還有另外一張沿著 Z 軸來顯示立體攝影機系統的圖: 

                                                      圖八(c): 立體視錐 (靠在椅背上垂直觀看)