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 的值.