AD

Stereoscopic 3D in OpenGL

近年來Stereoscopic 3D在面板跟多媒體業界(的廠商,不包含使用者XD)間似乎很熱, 而在電腦影像處理中是如何處理多視角的3D影像呢,此篇用OpenGL來實際實作一個簡單的例子....




首先, 在開始前有些基本OpenGL功能必須要清楚, 像是一些基本的幾何知識,以及glFrustum的使用等等...


1.glFrustum

OpenGL API中的glFrustum函式主要負責投影矩陣的處理, 可由圖一看出Camera在3D空間中的投影成像狀況,可以看出是投影的成像是透過一個六面體 , glFrustum負責處理投影成像的矩陣運算,另外,OpenGL程式設計常使用到的gluPerspective是glFrustum的一種常用例子,其六面體是對稱的且前後投影面是平行的(如圖一), 沒有特別需求的話一般可以使用gluPerspective處理即可...



圖一,glFrustum Perspective View

詳細函式可以參考

http://www.opengl.org/sdk/docs/man/xhtml/glFrustum.xml 

2.Stereo View的計算

詳細可以參考網址:
http://www.binocularity.org/page26.php
http://www.orthostereo.com/geometryopengl.html

 左右眼在空間中的視角上視圖如圖二,實際單眼的視角在空間中會如圖一那樣,也就是說, 只要處理眼睛的移動以及投影的計算, 就可以產生出左右眼的影像.


圖二, 左右眼視角上視圖


實際實做需要注意的重點有幾個地方

.Frustum投影的計算

.平移3D環境的Camera

.重新計算Frustum投影


3.程式實做

從上面參考網址的程式碼來改, 基礎部分省略, 以下列出幾個重點部分

//這個是degrees to radians
#define DTR 0.0174532925

struct camera
{
 GLdouble leftfrustum;
 GLdouble rightfrustum;
 GLdouble bottomfrustum;
 GLdouble topfrustum;
 GLfloat modeltranslation;
} leftCam, rightCam;

double aspect = 1.0;
double nearZ = 0.1;                 //near clipping plane
//畫布(display plane)的z位置, Frustum效果平移的主要基準frame
double screenZ = 600.0;     
//眼(鏡頭)相距距離, 處理時由原點各左右移IOD/2處理左右眼影像, 值需要依狀況做調整, 
//另外, 有paper說最佳值為width的5%或2.5%, 不過沒試過我也不知道效果
double IOD = 5;   

以上參數有了, 再來是計算Frustum的參數, 注意雖然不做也沒關係, 不過依照比例下去計算各個投影參數, 出來的形狀比較不會變形太多, frustumshift也依照比例去算

void setFrustum()
{
 double top = nearZ*tan(DTR*fovy/2);                    //sets top of frustum based on fovy and near clipping plane
 double right = aspect*top;                             //sets right of frustum based on aspect ratio
 double frustumshift = (IOD/2)*nearZ/screenZ;

 leftCam.topfrustum = top;
 leftCam.bottomfrustum = -top;
 leftCam.leftfrustum = -right + frustumshift;
 leftCam.rightfrustum = right + frustumshift;
 leftCam.modeltranslation = IOD/2;

 rightCam.topfrustum = top;
 rightCam.bottomfrustum = -top;
 rightCam.leftfrustum = -right - frustumshift;
 rightCam.rightfrustum = right - frustumshift;
 rightCam.modeltranslation = -IOD/2;
}


.處理左右影像繪圖

OpenGL基礎部分省略, 雖然畫幾個簡單幾何圖形表示就可以了, 不過感覺好像z值不夠變化, 所以找個DepthMap影像來畫一畫...


 圖三, 材質

圖四, DepthMap

(圖的來源應該是某個學術網站, 臨時忘了網址, 想起來之後會補上)


.簡單依照DepthMap畫出3D模型

畫完了之後可以開始準備處理左右眼影像了...


圖五, 前視圖

圖六, 上視圖

.左右眼影像繪圖處理


GLvoid drawFrustumView(GLvoid)
{
 glDrawBuffer(GL_BACK);                                   //draw into both back buffers
 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);      //clear color and depth buffers

 if(nChangeSideView%2 == 0)
 {
  glDrawBuffer(GL_BACK_LEFT);                              //draw into back left buffer
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();                                        //reset projection matrix
  glFrustum(leftCam.leftfrustum, leftCam.rightfrustum,     //set left view frustum
   leftCam.bottomfrustum, leftCam.topfrustum,
   nearZ, farZ);
  glTranslatef(leftCam.modeltranslation, 0.0, 0.0);        //translate to cancel parallax
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 0,  0, 0, -1*screenZ, 0, 1, 0);
  glPushMatrix();
  {   
   glTranslated(xTranslation,yTranslation,-1*screenZ);//translate to screenplane
   RenderHeightMap(g_HeightMap);
  }
  glPopMatrix();
 }
 else if(nChangeSideView%2 == 1)
 {
  glDrawBuffer(GL_BACK_RIGHT);                             //draw into back right buffer
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();                                        //reset projection matrix
  glFrustum(rightCam.leftfrustum, rightCam.rightfrustum,   //set left view frustum
     rightCam.bottomfrustum, rightCam.topfrustum,
     nearZ, farZ);
  glTranslatef(rightCam.modeltranslation, 0.0, 0.0);       //translate to cancel parallax
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  gluLookAt(0, 0, 0,  0, 0, -1*screenZ, 0, 1, 0);
  glPushMatrix();
  {   glTranslated(xTranslation,yTranslation,-1*screenZ);
   RenderHeightMap(g_HeightMap);
  }
  glPopMatrix();
 } 
}


.實作結果



圖七, 左眼影像



圖八, 右眼影像


以上為用OpenGL實做左右眼影像, 如果要用DirectX處理的話, 不很確定作法, 網路大概找了一下, 有可能是

D3DXMATRIX* WINAPI D3DXMatrixLookAtLH ( D3DXMATRIX *pOut, CONST D3DXVECTOR3 *pEye, CONST D3DXVECTOR3 *pAt, CONST D3DXVECTOR3 *pUp )

 // Set up our view matrix. A view matrix can be defined given an eye point,
    // a point to lookat, and a direction for which way is up. Here, we set the
    // eye five units back along the z-axis and up three units, look at the
    // origin, and define "up" to be in the y-direction.
    D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
    D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );


4.後續

如果要利用左右影像做3D影像, 紅藍記得是alpha兩邊都設0.5, 顏色處理一下, 找個想要讓眼睛焦點對上的地方當對位點對好即可,如果是要處理交錯, 就比較麻煩了, X86上可以用render to texture然後從gpu取貼圖到pc做處理, 或者是比較有效率的直接在shader做處理, 後者在OpenGL ES也可以實作, 前者會比較受限於OpenGL ES實作上有困難(至少我做的時候是那樣....哈).

沒有留言:

張貼留言