視圖變換,是指變換照相機的位置,角度。
模型變換,是指變換被照物體的位置,角度。
這兩個變換,都會影響最終圖形中,物體的位置,角度。而這兩個變換,可以達到相同的效果。比如,你想要一個倒著的水杯圖形,可以把你自己倒立,這樣看到的水杯就是倒立的了。或者把水杯倒立,自己直立,也能看到倒立的水杯。
如圖所示,這兩種變換,可以看做達到目的的不同途徑。甚至可以同時使用視圖變換和模型變換,只要最終拿到了我們想要的圖像就可以了。至于使用的是視圖變換,還是模型變換,看我們理解問題的角度。
3.2.1 對變換進行思考
變換順序,對最終的結果影響很大。
看下面的例子:
圖中有兩個操作,旋轉和移動。一個是沿原點繞z軸逆時針旋轉45度,另一個是沿x軸向下平移。左圖中,是先旋轉,再移動,物體最終在x軸上。右圖中,是先移動,再旋轉,物體最終在x=y軸上。變換順序不同,導致物體最終位置不同,這就是變換順序的影響。
變換順序,在OpenGL中的具體實現。
在OpenGL中,所有的變換,都是通過矩陣來實現的。一個矩陣,表示一個或多個變換。模型視圖變換,是通過模型視圖矩陣來實現的。由于這個矩陣經常變換,需要進行管理,OpenGL中是通過矩陣堆棧來對矩陣進行管理的。
當前模型視圖矩陣如果用C來表示,在當前模型視圖基礎上,進行一個變換,這個變換使用的矩陣為M。那么一個頂點v的變換之后的坐標為CMv。也就是說,M變換先作用于頂點v,然后再是當前模型視圖矩陣C。
看下面的例子:
glMatrixMode(GL_MODELVIEW);
glLoadIndentity();
glMultMatrixf(N); //變換N
glMultMatrixf(M); //變換M glMultMatrixf(L); //變換L glBegin(GL_POINTS); glVertex3f(v); glEnd();
?
這段代碼中,模型視圖矩陣按順序分別包含了I, N, NM,最后是NML,其中I表示單位矩陣。經過變換的頂點是NMLv。因此,頂點變換就是N(M(Lv)),也就是說,v首先與L相乘,Lv再與M相乘,MLv再與N相乘,而不是按它們指定的順序出現的。
全局固定坐標系
圖3-4中,左圖中的先旋轉,再平移,代碼實現如下:
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glMultMatrixf(T); //平移
glMultMatrixf(R); //旋轉 draw_the_object();
只要記住一點,物體的變換順序,和矩陣的出現順序,正好相反。
局部移動坐標系統
這個通常用于模型的關節控制。比如機器人手臂。比如畫汽車輪子上的螺釘,這個螺釘的位置,相對于汽車輪子,這個輪子上建立的坐標系,叫局部移動坐標系。而輪子的位置,又是相對于汽車本身,最后,汽車本身,是在全局坐標系中指定。
3.2.2 模型變換
模型變換,主要涉及三個函數,移動、旋轉、縮放。有了這三個函數的組合,我們可以進行任意變換。
void glTranslate{fd}(TYPE x, TYPE y, TYPE z);
void glRotate{fd}(TYPE angle, TYPE x, TYPE y, TYPE z); void glScale{fd}(TYPE x, TYPE y, TYPE z);
這三個函數,之前接觸過。glTranslate,進行平移,平移的偏移量,由(x, y, z)指定。 glRotate,旋轉,以逆時針方向繞著從原點到點(x, y, z)的直線旋轉角度angle。glScale,按照一定比列進行縮放,比例在x軸,y軸,z軸方向上的量是(x, y, z)。 3.2.3 視圖變換
視圖變換,相關的三個函數是glTranslate,glRotate和gluLookAt函數。
這個glTranslate和glRotate在模型變換中,我們已經見過了。怎么模型變換和視圖變換,使用的是同樣的函數呢?因為變換是相對的。 比如,讓模型和照相機距離5個單位長度,假設模型和照相機放在一起。我們可以將物體向前移動5個單位長度,也可以將照相機向后移動5個單位長度。所以,視 圖變換,也是使用glTranslate和glRotate。只是這個參數的含義相反罷了。
比如
glTranslatef(0.0, 0.0, -5.0);
這個函數在場景中把物體沿z軸移動-5個單位,相當于把照相機沿z軸移動+5個單位。
使用工具函數gluLookAt()
void gluLookAt(GLdouble eyex, GLdouble eyey, GLdouble eyez,GLdouble centerx, GLdouble centery, GLdouble centerz,GLdouble upx, GLdouble upy, GLdouble upz);
定義一個視圖矩陣,并把它與當前矩陣進行右乘。目標觀察點eyex, eyey, eyez。centerx, centery和centerz參數指定了視線上的任意一點。upx,upy和upz參數表示哪個方向是朝上的(也就是說,在視景體中自底向上的方向)。 默認情況下,照相機位于原點,指向z軸的負方向,以y軸的正方向為朝上方向。相當于調用:
gluLookAt(0.0, 0.0, 0.0, 0.0, 0.0, -100.0, 0.0, 1.0, 0.0);
參考點的z值是-100.0, 但它也可以是任意的負值,因為它不會影響視線的方向。 下面是使用gluLookAt的一個例子。
gluLookAt(4.0, 2.0, 1.0, 2.0, 4.0, -3.0, 2.0, 2.0, -1.0);
這個函數,將攝像機移動到了(4.0, 2.0, 1.0)這個點,攝像機朝向(2.0, 4.0, -3.0)方向,攝像機向上的方向為(2.0, 2.0, -1.0)。
強烈推薦,理解這幾個函數(glTranslate, glRotate, gluLookAt),使用Nate Robin的教程。網上有下的。
創建自定義的工具函數
創建自定義的函數,其實就是在自己的函數中,調用glTranslate和glRotate這兩個函數。
要創建自定義的工具函數,主要是弄清楚兩個東西:一個是參數是相對于哪個坐標系的,第二個就是照相機的變換順序,與glTranslate,glRotate出現的順序相同。
比如編寫一個飛機模擬器,并且以飛機的駕駛員座位觀察點顯示飛機外面的景象。我們可以用一個圓點位于跑道上的坐標系統來描述整個場景,飛機相對于坐 標(x, y, z)。然后,假設飛機還有傾側角、螺旋角和航向改變角(這些都是飛機相對于它的重心的旋轉角度)。下面這個函數可以作用視圖變換函數使用。
void pilotView(GLdouble planex, GLdouble planey, GLdouble planez, GLdouble roll, GLdouble pitch, GLdouble heading)
{glRotated(roll, 0.0, 0.0, 1.0); glRotated(pitch, 0.0, 1.0, 0.0); glRotated(heading, 1.0, 0.0, 0.0); glTranslated(-planex, -planey, -planez); }
這個其實很好理解,因為這個roll, pitch, heading,都是相對于飛機的。而飛機,就是我們的相機。所以,先將飛機旋轉到一定角度,然后移動到點(planex, planey, planez)上面。因為這個planex, planey, planez是相對于機場跑道的坐標系,而我們移動的飛機相當于相機,所以都要取負號。