天天看點

立體視覺:基于OpenGL的立體圖像對的軟體實作方法

1、3D驅動原理與GL_STEREO

       如果要讓3D顯示卡提供立體顯示,則必須有兩方面的技術:一是立體驅動,二是3D液晶快門。其中,立體驅動為顯示驅動部份,能将Direct 3D(或OpenGL)相容的應用軟體,由平面顯示即時轉化為立體顯示,從表面上我們所看到的現象,就是普通顯示變成了雙影顯示,這樣雙影顯示就包含了左右眼各自的圖象顯示與顯示器上;3D液晶快門則是一種高速的電子液晶快門,可根據顯示立體驅動發出的信号同步調整開關,達到分離顯示器上顯示的左右眼圖像,即時的送給左右眼睛,這樣,我們所看到的顯示器的雙影顯示就會變成立體顯示。當然,這雙影圖像必須有在一定的角度上的偏差,而角度通過預設值來調整。

       通常,立體驅動将所産生的左右圖像以交錯格式、上下格式及換頁格式三種方式儲存在顯示卡的記憶體中。交錯格式是将左右圖像各别抽取一半之後,再交錯合成一張同樣分辨率的影像。上下格式則将左右圖像各别抽取一半壓縮後,合成存入一張具有上下格式的圖像。換頁格式:則不作任何處理,将左右圖像以原有的分辨率,分别獨立存儲在記憶體中。這三種格式以換頁格式的圖像品質最好。目前我們采用的是換頁格式,也就是換頁掃瞄模式,将左右圖像交替掃瞄于顯示器的螢幕,也就是先掃瞄左眼圖像,再掃描右眼圖像。如此不斷地重複,因其掃描速度夠快(如100Hz),加上人眼的視覺暫留現象,會讓人感覺是同時顯示在螢幕上,此時,利用顯示卡發生連續的同步信号給3D液晶快門眼鏡,也就是說,當顯示左眼圖像時,3D液晶眼鏡左鏡片開通,右眼鏡片關閉:當顯示右眼圖像時,3D液晶眼鏡的右鏡片開通,左眼鏡片關閉,如此反複,左右眼就各自看到自己的圖像,而在大腦中形成立體影像。

       實時渲染三維資料,即把三維資料轉換為螢幕上可顯示的資料時,一般采用 OpenGL 或 DirectX 應用程式接口函數庫。在 OpenGL 應用程式接口中,專門提供了渲染多視角的 Quad-Buffered Stereo(四方體立體緩存)函數,而專業的 OpenGL 顯示卡對 Quad-Buffered 提供硬體上的支援。例如 NVidia Quadro、ATI-Fire GL、3Dlabs Wildcat 工作站顯示卡。如果需要立體顯示,可以在顯示卡的驅動的進階選項設定中啟用Stereo 選項并根據相關硬體做适當調整或者交換左右眼等。

       在OpenGL了中必須先使用GL_STEREO進行宏測試顯示卡是否支援stereo。

2、Toe-in Method(correct)

       使用這種方法時,相機是一個固定并且是産生對稱的平截體,每個相機指向單一的焦距。使用Toe-in建立的圖像是一個具有垂直視差的立體感,并且會産生不合适的水準增量。這種垂直視差增量會随着相機張角的增大而跳出偏移投影平面的中心。

立體視覺:基于OpenGL的立體圖像對的軟體實作方法

       Toe-in是一種不正确的方法,但它仍然用得很多,因為它采用移動相機法并且非常容易的使用平行相機産生兩個圖像對。

       Toe-in在OpenGL中采用gluPerspective()函數進行透視投影,左右相機位于不同位置但都是指向同一個焦點的。部分代碼如下:

glMatrixMode(GL_PROJECTION); // 設定投影模式

glLoadIdentity();

// 設定透視投影,相機張角,投影縱橫比,最近和最遠處

gluPerspective(camera.aperture,screenwidth/(double)screenheight,0.1,10000.0);

if (stereo) {

      CROSSPROD(camera.vd,camera.vu,right);

      Normalise(&right); // 獲得機關矢量

         // 計算相機偏移量(包括方向)

      right.x *= camera.eyesep / 2.0;

      right.y *= camera.eyesep / 2.0;

      right.z *= camera.eyesep / 2.0;

      glMatrixMode(GL_MODELVIEW);

         // 左右緩沖模式

      glDrawBuffer(GL_BACK_RIGHT);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

         // 設定相機,右眼位置為vp+right,指向焦點,方向vu

      gluLookAt(camera.vp.x + right.x,  

                camera.vp.y + right.y,

                camera.vp.z + right.z,

                focus.x,focus.y,focus.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

      glMatrixMode(GL_MODELVIEW);

      glDrawBuffer(GL_BACK_LEFT);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

      gluLookAt(camera.vp.x - right.x,

                camera.vp.y - right.y,

                camera.vp.z - right.z,

                focus.x,focus.y,focus.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

   } else {

      glMatrixMode(GL_MODELVIEW);

      glDrawBuffer(GL_BACK);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

      gluLookAt(camera.vp.x,

                camera.vp.y,

                camera.vp.z,

                focus.x,focus.y,focus.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

   }

   /* glFlush(); This isn't necessary for double buffers */

   glutSwapBuffers();

3、Offaxis frustums(correct method)

       Off-axis是一種正确的産生立體圖像對的方法,它不會産生垂直視差并且産生不緊迫的立體圖像對。需要注意的是它的投影是必須産生一個非均勻的相機平截頭體,OpenGL中支援這種投影方式。在這種情況下的視覺向量仍然是平行,并且使用glFrustum()函數去描述透視投影。

立體視覺:基于OpenGL的立體圖像對的軟體實作方法

如下圖可計算非均勻平截頭體的偏移量D:

widthdiv2 = camera.near * tan(camera.aperture/2)

D = 0.5 * camera.eyesep * camera.near / camera.fo

透視投影的左右上下前後值為:

top    =   widthdiv2;

bottom = - widthdiv2;

left   = - aspectratio * widthdiv2 ± 0.5 * camera.eyesep * camera.near / camera.fo;

right =   aspectratio * widthdiv2 ± 0.5 * camera.eyesep * camera.near / camera.fo;

near = camera.neardist

far = camera.fardist

相機的擺放如下:

gluLookAt(camera.pos.x + right.x,camera.pos.y + right.y,camera.pos.z + right.z, // 眼睛的位置

             camera.pos.x + right.x + camera.dir.x, // 相機指向,平行指向投影平面

             camera.pos.y + right.y + camera.dir.y,

             camera.pos.z + right.z + camera.dir.z,

             camera.up.x,camera.up.y,camera.up.z); // 相機向上矢量

立體視覺:基于OpenGL的立體圖像對的軟體實作方法

詳細代碼如下:

/* Misc stuff */

   ratio = camera.screenwidth / (double)camera.screenheight;

   radians = DTOR * camera.aperture / 2;

   wd2     = near * tan(radians);

   ndfl    = near / camera.focallength;

   if (stereo) {

      /* Derive the two eye positions */

      CROSSPROD(camera.vd,camera.vu,r);

      Normalise(&r);

      r.x *= camera.eyesep / 2.0;

      r.y *= camera.eyesep / 2.0;

      r.z *= camera.eyesep / 2.0;

      glMatrixMode(GL_PROJECTION);

      glLoadIdentity();

      left = - ratio * wd2 - 0.5 * camera.eyesep * ndfl;

      right =   ratio * wd2 - 0.5 * camera.eyesep * ndfl;

      top    =   wd2;

      bottom = - wd2;

      glFrustum(left,right,bottom,top,near,far);

      glMatrixMode(GL_MODELVIEW);

      glDrawBuffer(GL_BACK_RIGHT);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

      gluLookAt(camera.vp.x + r.x,camera.vp.y + r.y,camera.vp.z + r.z,

                camera.vp.x + r.x + camera.vd.x,

                camera.vp.y + r.y + camera.vd.y,

                camera.vp.z + r.z + camera.vd.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

      glMatrixMode(GL_PROJECTION);

      glLoadIdentity();

      left = - ratio * wd2 + 0.5 * camera.eyesep * ndfl;

      right =   ratio * wd2 + 0.5 * camera.eyesep * ndfl;

      top    =   wd2;

      bottom = - wd2;

      glFrustum(left,right,bottom,top,near,far);

      glMatrixMode(GL_MODELVIEW);

      glDrawBuffer(GL_BACK_LEFT);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

      gluLookAt(camera.vp.x - r.x,camera.vp.y - r.y,camera.vp.z - r.z,

                camera.vp.x - r.x + camera.vd.x,

                camera.vp.y - r.y + camera.vd.y,

                camera.vp.z - r.z + camera.vd.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

   } else {

      glMatrixMode(GL_PROJECTION);

      glLoadIdentity();

      left = - ratio * wd2;

      right =   ratio * wd2;

      top    =   wd2;

      bottom = - wd2;

      glFrustum(left,right,bottom,top,near,far);

      glMatrixMode(GL_MODELVIEW);

      glDrawBuffer(GL_BACK);

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glLoadIdentity();

      gluLookAt(camera.vp.x,camera.vp.y,camera.vp.z,

                camera.vp.x + camera.vd.x,

                camera.vp.y + camera.vd.y,

                camera.vp.z + camera.vd.z,

                camera.vu.x,camera.vu.y,camera.vu.z);

      MakeLighting();

      MakeGeometry();

   }

   /* glFlush(); This isn't necessary for double buffers */

   glutSwapBuffers();