OpenGL中圖形繪制后,往往需要一系列的變換來達到用戶的目的,而這種變換實現的原理是又通過矩陣進行操作的。opengl中的變換一般包括視圖變換、模型變換、投影變換等,在每次變換后,opengl將會呈現一種新的狀態(這也就是我們為什么會成其為狀態機)。
有時候在經過一些變換后我們想回到原來的狀態,就像我們談戀愛一樣,換來換去還是感覺初戀好,怎么辦?強大的opengl就幫我們提供了兩個函數:giPushMatrix()和glPopMatrix();
首先我們要知道,對於矩陣的操作都是對於矩陣棧的棧頂來操作的。當前矩陣即為矩陣棧的棧頂元素,而對當前矩陣進行平移、旋轉等的變換操作也同樣是對棧頂矩陣的修改。所以我們在變換之前調用giPushMatrix()的話,就會把當前狀態壓入第二層,不過此時棧頂的矩陣也與第二層的相同。
當經過一系列的變換后,棧頂矩陣被修改,此時調用glPopMatrix()時,棧頂矩陣被彈出,且又會恢復為原來的狀態。
函數的作用過程可以用下圖描述,更為直觀。
在opengl場景中一般存在多種矩陣變換操作,而控制這些操作的命令主要用到
glMatrixMode(GLenum mode);
作用:用於指定用哪個矩陣作為當前矩陣,mode用於指定哪一種矩陣棧是其后矩陣操作的目標。mode可取:
GL_MODELVIEW: 把其后的矩陣操作施加於造型視圖矩陣棧。(默認)
GL_PROJECTION: 把其后的矩陣操作施加於投影矩陣棧。
GL_TEXTURE: 把其后的矩陣操作施加於紋理矩陣棧。
注意上述三種模式分別對應了三種矩陣棧。
所以在場景中存在多種矩陣變換時,glPushMatrix()和glPopMatrix()一般情況下也要結合glMatrixMode(GLenum mode)運用,系統才知道具體操作的是哪個矩陣棧。
注意:
攝像機矩陣和模型矩陣用的是同一個矩陣,就是GL_MODELVIEW (model是模型搜索矩陣,view是攝像機矩陣,GL_MODELVIEW里保存的是這兩個矩陣的積)。所以選擇GL_MODELVIEW之后直接用glTranslate,glRotate之類的就行。
其實攝像機和模型矩陣本質上是一回事(這也是為什么OpenGL把這兩個矩陣放在一起保存的原因),因為比如把整個世界向y+方向移動10跟把攝像機向y-方向移動10是等價的。旋轉也是一樣。
雖然矩陣里可以保存任何變換,但按照OpenGL的概念,model和view矩陣里只能保存平移,旋轉和縮放;project矩陣里只能保存投影矩陣,viewport矩陣里只能保存二維平移和縮放。這樣來看把model和view放在一起是合理的。他們之間的區別純粹是人為的。
附上代碼例子:
1 #include <stdlib.h> 2 #include "include\glut.h" 3 4 static int year = 0, day = 0; 5 6 void init(void) 7 { 8 glClearColor (0.0, 0.0, 0.0, 0.0); 9 glShadeModel (GL_FLAT); 10 } 11 12 void display(void) 13 { 14 glClear (GL_COLOR_BUFFER_BIT); 15 glColor3f (1.0, 1.0, 1.0); 16 17 glPushMatrix(); 18 { 19 glutWireSphere(1.0, 20, 16); /* draw sun */ 20 glRotatef ((GLfloat) year, 0.0, 1.0, 0.0); 21 22 glTranslatef (2.0, 0.0, 0.0); //把坐標原點變換位置 23 24 glRotatef ((GLfloat) day, 0.0, 1.0, 0.0); 25 glutWireSphere(0.2, 10, 8); /* draw smaller planet */ 26 } 27 glPopMatrix(); 28 29 glutSwapBuffers(); 30 } 31 32 void reshape(int w, int h) 33 { 34 glViewport (0, 0, (GLsizei) w, (GLsizei) h); 35 glMatrixMode (GL_PROJECTION); 36 glLoadIdentity (); 37 gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0, 20.0); 38 39 glMatrixMode(GL_MODELVIEW); 40 glLoadIdentity(); 41 gluLookAt (0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); 42 } 43 44 void keyboard(unsigned char key, int x, int y) 45 { 46 switch (key) { 47 case 'd': 48 day = (day + 10) % 360; 49 glutPostRedisplay(); 50 break; 51 case 'D': 52 day = (day - 10) % 360; 53 glutPostRedisplay(); 54 break; 55 case 'y': 56 year = (year + 5) % 360; 57 glutPostRedisplay(); 58 break; 59 case 'Y': 60 year = (year - 5) % 360; 61 glutPostRedisplay(); 62 break; 63 case 27: 64 exit(0); 65 break; 66 default: 67 break; 68 } 69 } 70 71 int main(int argc, char** argv) 72 { 73 glutInit(&argc, argv); 74 glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB); 75 glutInitWindowSize (500, 500); 76 glutInitWindowPosition (100, 100); 77 glutCreateWindow (argv[0]); 78 79 init(); 80 glutDisplayFunc(display); 81 glutReshapeFunc(reshape); 82 glutKeyboardFunc(keyboard); 83 glutMainLoop(); 84 return 0; 85 }
運行結果: