為了瞭解觀念,所以做的實作
[一] 在介紹之前應該要知道的事
World Coordinate | target 客觀所在的位置 |
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
ux | uy | uz | -x0*ux-y0*uy-z0*uz |
vx | vy | vz | -x0*vx-y0*vy-z0*vz |
nx | ny | nz | -x0*nx-y0*ny-z0*nz |
0 | 0 | 0 | 1 |
[三] 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 |
0 | 0 | (znear+zfar)/(znear-zfar) | (-2znear*zfar)/(znear-zfar) |
0 | 0 | -1 | 0 |
但是即便如此仍然可能會超出範圍,而超出視窗範圍時,就應該用上裁切的概念,
如上圖,假設螢幕的範圍是黑色的框框,物體畫在螢幕上是紅色的三角形,此時有可能會出現 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.707107 | 0 | -0.707107 | -35.3553 |
0 | 1 | 0 | -50 |
0.707107 | 0 | 0.707107 | -106.066 |
0 | 0 | 0 | 1 |
projection 的 matrix 應該要是 (假設為B)
0.625 | 0 | 0 | 0 |
0 | 0.416667 | 0 | 0 |
| 0 | -1.5 | -62.5 |
0 | 0 | -1 | 0 |
以 v1 為例,點的matrix如下 (假設為 x)
而要把點的座標算出來就需要做 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得到
v1(237.5, 241.074)
v2(487.5, 123.223)
v3(487.5, 476.777)