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

效果如下图:

原文地址:https://www.cnblogs.com/pursuiting/p/6234751.html