OpenGL實現3D漫游的理解


這篇文章主要參考以下兩篇博客:

推導相機變換矩陣

OpenGL系列教程之五:OpenGL矩陣類

上面的第一篇是理論的講解,第二篇有實例代碼,我在后面會給出自己寫的主函數,依賴的類可以從第二篇參考中下載。

我這篇文主要談我個人對OpenGL中實現3D漫游的思路的理解。經過這些天的學習,主要是研究別人寫的代碼和網上的的博客,我初步理解了OpenGL中對於多方位觀察物體的實現策略。其實,對於3維坐標變換,每個人都可以有自己的理解方式,有的人喜歡研究一堆矩陣,有些人喜歡從空間幾何的角度去理解。

一  要實現3D漫游,第一步就要確定投影方向

OpenGL首先定義了一個世界坐標系(xyz),還有一個UVN坐標系,兩個都是右手系。觀察者總是沿着N軸負方向去觀察xyz,而物體也總是放在xyz中的。在OpenGL中,xyz其實是一個與物體保持相對位置不動的坐標系,而UVN坐標系其實是一個與觀察點位置保持相對不動的坐標系。那么怎樣才能全方位觀察物體呢?無外乎兩個思路:1,讓xyz繞着UVN轉;2,讓UVN繞着xyz轉。從OpenGL提供的接口函數及其實現的效果來看,其采用的是前者。

OpenGL中,屏幕中所呈現的視平面其實是UOV平面,屏幕上方為V軸正方向,屏幕右方為U軸正方向,屏幕由里到外為N軸正方向。因此視線方向總是沿N軸負方向。要想從某個特殊的角度去觀察xyz坐標系(物體),只需要確定一個變換矩陣,將xyz先繞着UVN旋轉,然后再沿着NO的方向平移,就能一覽無余了。其實又可以這樣理解,最初xyz與UVN其實是重合的,然后讓xyz先乘一個旋轉矩陣,再乘一個平移矩陣,這樣xyz就以一個特定的姿態出現在視野中了,准確的說是,物體在UOV平面產生了一個特定的投影。

下面是一些變換矩陣:

1.平移矩陣:

其逆矩陣從平移過程去思考的話,顯然是反向平移對應的矩陣,因此其逆矩陣只是在x,y,z前面加個負號。

2.繞x,y,z軸旋轉的矩陣:

三個旋轉矩陣Rx,Ry,Rz,它們的列項向量都是兩兩互相垂直,並且都是單位向量,所以Rx,Ry,Rz都是正交矩陣,它們的逆就是其自身的轉置。並且可以證明:有限個正交矩陣的乘積仍為正交矩陣。

3.繞空間某一向量(x, y, z)旋轉的矩陣。注意向量是有方向的,旋轉規則仍是右手螺旋定則:

二  投影方向確定了,下一步就是確定投影面的位置,並且還需要在投影面中選取一個區域,該區域就是最終顯示在屏幕上的圖像

下面這張圖我認為很清晰地展示了投影面的確定過程

上圖采用的是透視投影,OpenGL中還有一種平行投影,分別由函數

 
 
 
         void gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar )
 
 
 
         void glOrtho( GLdouble left, GLdouble right,
                                 GLdouble bottom, GLdouble top,
                                 GLdouble near_val, GLdouble far_val )

確定投影的參數。gluPerspective參數中的fovy就是圖中的視角,以度為單位,zNear為近平面距離,zFar為遠平面距離。

選定投影區域后,會將該區域的坐標標准化到[-1, 1]之間,再通過函數void glViewport( GLint x, GLint y, GLsizei width, GLsizei height )

將標准化的坐標映射到圖像坐標,映射公式如下

圖中xnd,ynd是歸一化后的坐標,xw,yw是對應的圖像坐標,以像素為單位,當xnd=-1,ynd=-1時,映射的是圖像左下角的坐標,即以像素為單位時,圖像坐下角坐標為(0, 0)。下面是一段實現3D漫游的代碼,按x鍵讓茶壺繞U軸旋轉,按y鍵讓茶壺繞V軸旋轉,也可以用鼠標控制茶壺旋轉。代碼中依賴的矩陣類可從參考博客中獲得。

  1 #include <GL/glut.h>
  2 #include "Matrices.h"
  3 #include "Vectors.h"
  4 
  5 // GLUT CALLBACK functions
  6 void displayCB();
  7 void reshapeCB(int w, int h);
  8 void timerCB(int millisec);
  9 void keyboardCB(unsigned char key, int x, int y);
 10 void mouseCB(int button, int stat, int x, int y);
 11 void mouseMotionCB(int x, int y);
 12 void initGL();
 13 void initLights();
 14 void drawAxis(float size = 0.6f);
 15 
 16 const int   SCREEN_WIDTH    = 600;
 17 const int   SCREEN_HEIGHT   = 600;
 18 const float CAMERA_DISTANCE = 1.5f;
 19 const int   TEXT_WIDTH      = 8;
 20 const int   TEXT_HEIGHT     = 13;
 21 const float DEG2RAD         = 3.141593f / 180;
 22 
 23 // global variables
 24 int screenWidth;
 25 int screenHeight;
 26 bool mouseLeftDown;
 27 bool mouseRightDown;
 28 float mouseX, mouseY;
 29 float cameraAngleX;
 30 float cameraAngleY;
 31 float cameraDistance;
 32 Matrix4 matrixView;
 33 Matrix4 matrixModel;
 34 Matrix4 matrixModelView;
 35 Matrix4 matrixProjection;
 36 
 37 int main(int argc, char **argv)
 38 {
 39     // init global vars
 40     screenWidth = SCREEN_WIDTH;
 41     screenHeight = SCREEN_HEIGHT;
 42     mouseLeftDown = mouseRightDown = false;
 43     mouseX = mouseY = 0;
 44     cameraAngleX = cameraAngleY = 0;
 45     cameraDistance = CAMERA_DISTANCE;
 46 
 47     glutInit(&argc, argv);
 48     glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );                 // display mode
 49     glutInitWindowSize(screenWidth, screenHeight);                             // window size
 50     glutInitWindowPosition(100, 100);                                          // window location
 51     glutCreateWindow(argv[0]);                                                 // param is the title of window
 52 
 53     glEnable(GL_DEPTH_TEST);
 54     glutReshapeFunc(reshapeCB);
 55     glutDisplayFunc(displayCB);
 56     glutTimerFunc(33, timerCB, 33);                                            // redraw only every given millisec
 57 
 58     glutKeyboardFunc(keyboardCB);
 59     glutMouseFunc(mouseCB);
 60     glutMotionFunc(mouseMotionCB);
 61 
 62     glutMainLoop();
 63 
 64     return 0;
 65 }
 66 
 67 void drawAxis(float size)
 68 {
 69     glDepthFunc(GL_ALWAYS);     // to avoid visual artifacts with grid lines
 70 
 71     // draw axis
 72     glLineWidth(3);
 73     glBegin(GL_LINES);
 74         glColor3f(1, 0, 0);
 75         glVertex3f(0, 0, 0);
 76         glVertex3f(size, 0, 0);
 77         glColor3f(0, 1, 0);
 78         glVertex3f(0, 0, 0);
 79         glVertex3f(0, size, 0);
 80         glColor3f(0, 0, 1);
 81         glVertex3f(0, 0, 0);
 82         glVertex3f(0, 0, size);
 83     glEnd();
 84     glLineWidth(1);
 85 
 86     // draw arrows(actually big square dots)
 87     glPointSize(5);
 88     glBegin(GL_POINTS);
 89         glColor3f(1, 0, 0);
 90         glVertex3f(size, 0, 0);
 91         glColor3f(0, 1, 0);
 92         glVertex3f(0, size, 0);
 93         glColor3f(0, 0, 1);
 94         glVertex3f(0, 0, size);
 95     glEnd();
 96     glPointSize(1);
 97 
 98     // restore default settings
 99     glDepthFunc(GL_LEQUAL);
100 }
101 
102 void displayCB()
103 {
104     // clear buffer
105     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
106 
107     // transform camera
108     matrixView.translate(0, 0, cameraDistance);
109     matrixView.rotate(cameraAngleX, 1, 0, 0);     //cameraAngleX
110     matrixView.rotate(cameraAngleY, 0, 1, 0);     //cameraAngleY
111     matrixView.translate(0, 0, -cameraDistance);
112     cameraAngleX = 0;
113     cameraAngleY = 0;
114     glLoadMatrixf(matrixView.get());
115 
116     glColor3f(1.0f, 0.3f, 1.0f);
117     glutWireTeapot(0.6f);
118     drawAxis();
119 
120     glutSwapBuffers();
121 }
122 
123 void reshapeCB(int w, int h)
124 {
125     // set viewport to be the entire window
126     glViewport(0, 0, (GLsizei)w, (GLsizei)h);
127 
128     glMatrixMode(GL_PROJECTION);
129     gluPerspective(80.0f, (float)(w)/h, 0.5f, 10.0f); // FOV, AspectRatio, NearClip, FarClip
130 
131     // switch to modelview matrix in order to set scene
132     glMatrixMode(GL_MODELVIEW);
133     matrixView.identity();
134     matrixView.translate(0, 0, -cameraDistance);
135     glLoadMatrixf(matrixView.get());
136 }
137 
138 void timerCB(int millisec)
139 {
140     glutTimerFunc(millisec, timerCB, millisec);
141     glutPostRedisplay();
142 }
143 
144 void keyboardCB(unsigned char key, int x, int y)
145 {
146     switch(key)
147     {
148     case 'x':
149         cameraAngleX = 3;
150         break;
151     case 'X':
152         cameraAngleX = -3;
153         break;
154     case 'y':
155         cameraAngleY = 3;
156         break;
157     case 'Y':
158         cameraAngleY = -3;
159         break;
160     case 27:              // ESCAPE
161         exit(0);
162         break;
163     default:
164         break;
165     }
166 }
167 
168 void mouseCB(int button, int state, int x, int y)
169 {
170     mouseX = x;
171     mouseY = y;
172 
173     if(button == GLUT_LEFT_BUTTON)
174     {
175         if(state == GLUT_DOWN)
176         {
177             mouseLeftDown = true;
178         }
179         else if(state == GLUT_UP)
180             mouseLeftDown = false;
181     }
182     else if(button == GLUT_RIGHT_BUTTON)
183     {
184         if(state == GLUT_DOWN)
185         {
186             mouseRightDown = true;
187         }
188         else if(state == GLUT_UP)
189             mouseRightDown = false;
190     }
191 }
192 
193 void mouseMotionCB(int x, int y)
194 {
195     if(mouseLeftDown)
196     {
197         cameraAngleY = (x - mouseX);
198         cameraAngleX = (y - mouseY);
199         mouseX = x;
200         mouseY = y;
201     }
202     if(mouseRightDown)
203     {
204         matrixView.translate(0, 0, cameraDistance);
205         cameraDistance += (y - mouseY) * 0.01f;
206         mouseY = y;
207         matrixView.translate(0, 0, -cameraDistance);
208     }
209 }
View Code

效果如下圖:

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM