2013年11月22日 星期五

[openGL] 3D Viewing - transformation 實作

為了瞭解觀念,所以做的實作


[一] 在介紹之前應該要知道的事


World Coordinatetarget 客觀所在的位置
Viewing Coordinate人站在某個位置往某個方向看,所看到 target 所在的位置,算是相對於人所在的位置
Target目標物體,我覺得用一個點(point)來想比較好思考
View Plane簡單說就是人看到的畫面
Model-View Matrix用來將modeling轉換成view coordinate
Projection Matrix用來將view轉換成clip coordinate

[二] Transformation From World To View Coordinate


目的就是將物體從客觀的座標位置轉換成某人站在某處往某個方向看的一種相對座標,詳細原由就不講了,總之在轉換的時候,因為是從客觀的位置轉換到人站在某個位置看某個方向所看到的情形,所以需要:

void glhLookAtd2(float *eye, float *reference, float *viewUp)
人的客觀座標位置eye position
人看在哪個點上reference point
哪邊是上方viewUp

由這三組向量可以算出 transformation matrix的uvn Viewing-Coordinate Reference Frame,

(Nx, Ny, Nz) = (eye[0] - reference[0], eye[1] - reference[1], eye[0] - reference[0])
(Vx, Vy, Vz) 通常都是等於 (0, 1, 0),因為x是當作螢幕的橫軸,y是螢幕的縱軸,z則是距離螢幕的遠近,所以上方就是在y的正值

由以上兩組向量可以求得

(nx, ny, nz) = (Nx/|N|, Ny/|N|, Nz/|N|) -> normalize
(ux, uy, uz) = ((Vx, Vy, Vz) cross (nx, ny, nz)) / |V|
(vx, vy, vz) = (nx, ny, nz) cross (ux, uy, uz)

又由此可以得到如下 4x4 的transformation matrix

uxuyuz-x0*ux-y0*uy-z0*uz
vxvyvz-x0*vx-y0*vy-z0*vz
nxnynz-x0*nx-y0*ny-z0*nz
0001  

[三] Transformation From View Coordinate To Clip Coordinate

目的是為了讓原本計算之後,無法預期大小的座標透過矩陣運算 縮小到視窗範圍內,

glFrustum(float left, float right, float bottom, float top, float dnear, float dfar)
簡單說就是看到的範圍吧

用以上這些值,可以求的如下的 4x4 transformation matrix

znear = -dnear
zfar = -dfar
-2znear/(right-left)0(right+left)/(right-left)0
0-2znear/(top-bottom)(top+bottom)/(top-bottom)0
00(znear+zfar)/(znear-zfar)(-2znear*zfar)/(znear-zfar)
00-10  

但是即便如此仍然可能會超出範圍,而超出視窗範圍時,就應該用上裁切的概念,

如上圖,假設螢幕的範圍是黑色的框框,物體畫在螢幕上是紅色的三角形,此時有可能會出現 run-time error 原因是存取違規,access violate,原因可以去看另一篇「drawpixels問題紀錄」
這時候就要像上圖一樣,上面的水平線依照原本的斜率畫到視窗的邊界,下面的斜線也是一樣,裁切方式如下

bool setPixel (int x, int y, GLbyte R, GLbyte G, GLbyte B)
{
if (x < W && x > 0) {
if (y < H && y > 0) {
GLbyte *p = image;
p += ((y * W + x) * 3);
*p = R;
*(p + 1) = G;
*(p + 2) = B;
}
}
}
image 是我的 frame buffer (拿來存 pixel 顏色的資訊),W和H是螢幕的寬跟高

drawLine (int x0, int y0, int x, int y)
{
...
setPixel(...........);
...
}


[四] Viewport Transformation for screen coordinate


因為算出來的值有正有負,而理想狀況下,座標原點在screen正中間,所以做以下調整之後,就可以將物體放在視窗中間

x = (w / 2) * (x + 1)
y = (w / 2) * (y + 1)
z就可以不用算了

但是前面在做 clip coordinate 的時候,因為算出來 x, y, z 的範圍都有可能因為glFrustum代入的值的關係而不是在 -1 ~ 1之間,所以來做個 normalize

x' = x / (第四項)
y' = y / (第四項)

最後再求

x = (w / 2) * (x' + 1)
y = (w / 2) * (y' + 1)

[五] 簡單小範例

function帶入的parameter
glLookAt({100, 50, 50}, {50, 50, 0}, {0, 1, 0});
glFrustum(-40.0, 40.0, -60.0, 60.0, 25.0, 125.0);

一個面4個點的資訊
v1[4] = { 0.0, 0.0, 0.0, 1.0 } 
v2[4] = { 100.0, 0.0, 0.0, 1.0 }
v3[4] = { 100.0, 100.0, 0.0, 1.0 }
v4[4] = { 0.0, 100.0, 0.0, 1.0 }

紅色為4個點構成的面,綠點是人眼的位置,藍線是人看的方向,黑點是人看的位置


結果應該是要看到類似這樣的梯形,這樣才有像人看到的情況

modelview 的 matrix 應該是這樣  (假設為A)
0.7071070-0.707107-35.3553
010-50
0.70710700.707107-106.066
0001

projection 的 matrix 應該要是 (假設為B)
0.625000
00.41666700

0-1.5-62.5
00-10

以 v1 為例,點的matrix如下 (假設為 x)
0
0
0
1

而要把點的座標算出來就需要做 BAx,而v1算出來為
-22.0971
-20.8333
96.599
106.066

因為範圍不在-1~1之間所以normalize一下,把值除以第四項
-22.0971/106.066
-20.8333/106.066
96.599/106.066
106.066/106.066

再透過上面寫的viewport得到
237.5
241.074
45.5372
1

則最後只留前兩項,四個點分別算出來是
v1(237.5, 241.074)
v2(487.5, 123.223)
v3(487.5, 476.777)
v4(237.5, 358.926)


沒有留言:

張貼留言