2014-04-03

OpenGL 攝影機控制

在這篇說明一下攝影機的操控邏輯,
以及「其中一種」操控方式。
在OpenGL中有很多種可以操控攝影機的方式,
這邊只提及其中一種,為大助教提供的方法。

void gluLookAt(GLdouble eyeX,    GLdouble eyeY,    GLdouble eyeZ,
               GLdouble centerX, GLdouble centerY, GLdouble centerZ,
               GLdouble upX,     GLdouble upY,     GLdouble upZ);

eye[],攝影機的位置;
center[],攝影機看的地方;(以下出現的at[],都是與center[]指一樣意思)
up[],攝影機的上方向量。

因此,只要更改eye[]center[]
在重畫時就會更改攝影機的角度與位置。



1. 相機的旋轉

我們可以用球座標系的表示法,(參考:wiki 球座標系)
使用θ、φ來表示攝影機的方向;
當攝影機往上、往下看時,就改變θ的值,
往左、往右看時,就改變φ的值。




















紅色向量代表的是相機看的方向,(vec[] = at[] - eye[])
因此,只要把eye[]加上vec[],就能得到相機看的位置(at[])。

當θ或φ有變化時,vec[]的「方向」也會跟著改變,(向量的長度固定)
因此只要角度有變化時,就需要重新計算vec[],
然後把eye[]加上新的vec[],就得到新的at[]位置。

因為vec[]是用(θ,φ)來表示的,而eye[]是用(x,y,z)來表示,
因此eye[]與vec[]做運算時,需要把vec[]轉回用(x,y,z)來表示。
(轉換的公式在wiki裡有寫)

有了以上的觀念以後,就可以來寫程式碼了:
#include <cmath>

// 利用初始值的vec[],也就是用讀檔進來的值,計算(r,θ,φ)的初始值。
// 這個計算只需做一次,之後相機的旋轉,直接操作θ、φ即可。
x = at[X]-eye[X];
y = at[Y]-eye[Y];
z = at[Z]-eye[Z];
r = sqrt(x*x + y*y + z*z);
theta = acos(z / r);
phi = atan2(y, x);

// 把vec[]轉換回直角坐標系,
// 每當θ、φ有改變時,就需要重新計算一次。
vec[X] = r*sin(theta)*cos(phi);
vec[Y] = r*sin(theta)*sin(phi);
vec[Z] = r*cos(theta);

// 最後,就可以來定義gluLookAt()要如何寫了。
center[X..Z] = eye[X..Z] + vec[X..Z];
gluLookAt( eye[X],    eye[Y],    eye[Z],
           center[X], center[Y], center[Z],
           up[X],     up[Y],     up[Z]);

做個簡單的小整理:

我們首先要用讀檔進來的eye[]、at[]值,
計算出相機看的方向向量,
然後利用這個向量,計算出(r,θ,φ)的初始值。

這個動作只需要做一次,
之後我們就可以把相機看的方向向量用(r,θ,φ)表示,
每當相機有旋轉的動作時,就直接更改θ、φ。

由上述的vec[]公式可以看出,
每當θ、φ有改變、也就是相機有旋轉時,vec[]也會跟著改變,
只要把eye[]的位置加上vec[]向量,就能得到新的at[]位置,
也就達到了旋轉的效果。



2. 相機的平移

有了θ、φ,並計算出相機看的方向vec[]之後,
就可以利用vec[]與up[]的外積,算出相機的右方向量。
而只要把eye[]與at[]同時加上右方向量(right[]),相機就會往右平移;
同時加上前方向量(vec[]),就會向前平移。

外積的算法有很多,這邊用拆項的方式比較方便計算。
u<u1,u2,u3> x v<v1,v2,v3> = <u2v3-u3v2, u3v1-u1v3, u1v2-u2v1>

因此就可以用程式碼來算右方向量。
// vec[] x up[] 得到右方向量。
right[X] = vec[Y]*up[Z] - vec[Z]*up[Y];
right[Y] = vec[Z]*up[X] - vec[X]*up[Z];
right[Z] = vec[X]*up[Y] - vec[Y]*up[X];

// 相機向右移動
eye[X..Z] += right[X..Z];

// 相機向前移動
eye[X..Z] += vec[X..Z];

在上面的gluLookAt()中,center[] = eye[] + vec[],
因此只要eye[]或是vec[]有變化,center[]也會跟著變化,
不需要另外把center[]加上right[]或是vec[]。

而right[]跟vec[]息息相關,
因此只要vec[]有變化,也就是相機有做旋轉時,
就要重新計算一次right[]。



3. 附註

在計算φ時,使用的是atan2()函數,而不是用atan(),
因為計算出的結果跟(x,y,z)的象限有關。

vec[]其實就是at[]-eye[],只是用不同座標系統來表示。
因此照理來說,反推回來的vec[]應該要與at[]-eye[]的值一樣,
但因為象限正負號問題,反推後的結果正負號可能會不同。

要解決這個問題,
就必須使用atan2()而不是atan()來計算φ的值。

在英文版的wiki中有寫到需要使用atan2(),
中文版的沒提到這個。

引用wiki裡3.1節Cartesian coordinates裡面的一段話:
The inverse tangent denoted in φ = arctan(y/x) 
must be suitably defined, taking into account the correct quadrant of (x,y). 
See the article on atan2.

參考:
(1) Spherical coordinate system
(2) atan2

沒有留言:

張貼留言