OpenGL漫游的实现

在用opengl学习和开发3d软件的时候,要是做出的模型能够随意地通过鼠标拖动转换视角,不用重复地调整参数重新编译,对程序开发来说会很省事。

去年做了一个opengl的鼠标交互式的程序http://blog.pfan.cn/jiagleo/42301.html,可以随意通过鼠标拖动旋转物体,切换视角,不过那个程序只能让你从周围观察原点。下面我再做一个漫游的程序,使你能随意改变观察点的位置,并且能够调整方向。我将更进一步把它集成一个工具库,用起来就像gluLookAt一样方便。

一、要看懂本程序,我假定网友们已经懂得了基本的C/C++知识,并已经掌握opengl的基本绘图框架。
二、首先还是先声明一下工作环境,我们使用的是C++,用glut函数库,这样就可以与平台无关了,用VC或Dev,还是Win或Linux等等都可以。

程序使用了GLUT库,需要下载库文件,执行时需要相应的动态链接库。在Windows平台下的下载地址:http://www.opengl.org/resources/libraries/glut/glut_downloads.php。
Windows环境下安装GLUT的步骤:
将下载的压缩包解开,将得到5个文件
(1)glut.h 放在“include/GL”;
(2)glut.lib和glut32.lib放在“/lib”;
(3)glut.dll和glut32.dll放到“C:/Windows/System32”
三、原理:

我们要完成如下功能:
镜头上下转(沿红色线),左右转(沿绿色线)以及左右倾(沿蓝色线),如图:




还有前后左右上下平移。

一、要实现这些,原理非常简单,分四步:
1.我们先需要声明一个矩阵;
2.变换的时候把矩阵加载到当前视口矩阵里;
3.调用opengl内置的坐标变换函数,然后取出矩阵;
4.用的时候,直接加载自定义的矩阵即可。

分别创建一个头文件和源文件:ramble.hramble.cpp

在ramble.cpp文件里:
首先声明变量:

GLfloat mRamble[16]; // 视图矩阵
程序开始的时候,需要初始化一下:
void RambleInitial() { glGetFloatv(GL_MODELVIEW_MATRIX, mRamble); // 这个函数可以读出OpenGL的视图矩阵 }

下面就是我们需要完成的主要的函数,把它们放到头文件里:
void RambleTurnLeft(); void RambleTurnRight(); void RambleTurnUp(); void RambleTurnDown(); void RambleSlantLeft(); void RambleSlantRight(); void RambleMoveForward(); void RambleMoveBack(); void RambleMoveLeft(); void RambleMoveRight(); void RambleMoveUp(); void RambleMoveDown();

然后,在源文件里实现:
只举两个例子:
void RambleTurnLeft(GLfloat angle) { glMatrixMode(GL_MODELVIEW); // 确保是在操纵视图矩阵 glLoadMatrixf(mRamble); // 把我们自己的矩阵加载进去 glRotated( angle, 0.0,-1.0, 0.0); // 左转angle角度 glGetFloatv(GL_MODELVIEW_MATRIX, mRamble); // 保存 } void RambleMoveForward(GLfloat m) { glMatrixMode(GL_MODELVIEW); // 确保是在操纵视图矩阵 glLoadMatrixf(mRamble); glTranslated( 0.0, 0.0, m); glGetFloatv(GL_MODELVIEW_MATRIX, mRamble); }
其他的类似,把参数改一下就可以了。
为了更简洁地调用这些函数,而不用每次都传入一个参数,重载这些函数:
void RambleTurnLeft(); void RambleTurnRight(); void RambleTurnUp(); void RambleTurnDown(); void RambleSlantLeft(); void RambleSlantRight(); void RambleMoveForward(); void RambleMoveBack(); void RambleMoveLeft(); void RambleMoveRight(); void RambleMoveUp(); void RambleMoveDown();

为此,我们还必须在头文件里声明两个变量,用于管理默认的变换幅度参数:
GLfloat mpstep = 0.25; //速度 GLfloat anglepturn = DEGtoRAD(45.0); // 转速

举其中一个例子:


void RambleTurnLeft() { RambleTurnLeft(anglepturn); }
其他的照搬就可以了


但是,这离我们的要求还有点距离,我们要把这些东西封装成一个工具库,也就是说,我们要做到,用户一拿到这两个文件就可以直接使用,而无需管内部过程。
那么我们需要下面函数,用来响应相应的消息:
inline void RambleVK_A(){RambleTurnLeft(); } inline void RambleVK_D(){RambleTurnRight(); } inline void RambleVK_W(){RambleTurnUp(); } inline void RambleVK_S(){RambleTurnDown(); } inline void RambleVK_R(){RambleMoveUp(); } inline void RambleVK_F(){RambleMoveDown(); } inline void RambleVK_Q(){RambleSlantLeft(); } inline void RambleVK_E(){RambleSlantRight(); } inline void RambleVK_LEFT(){RambleMoveLeft(); } inline void RambleVK_RIGHT(){RambleMoveRight(); } inline void RambleVK_UP(){RambleMoveForward(); } inline void RambleVK_DOWN(){RambleMoveBack(); }

你可以看到,其实不要这些也可以,但这是一种非常好的思想习惯,你不可能每次写个程序,都把一大堆让别人觉得莫名奇妙的函数往主程序里放,这样会给人阅读造成困难。并且当你以后修改的时候也会很方便。

最后是怎么在主程序里用了。很简单:
1.在opengl初始化完成后调用RambleInitial();
2.在相应的消息相应函数里加入对应的调用函数,就是上面的12个函数;
3.在绘图函数的开头使用Ramble();

源代码:
main.cpp:
/* * GLUT Shapes Demo * * Written by Nigel Stewart November 2003 * * This program is test harness for the sphere, cone * and torus shapes in GLUT. * * Spinning wireframe and smooth shaded shapes are * displayed until the ESC or q key is pressed. The * number of geometry stacks and slices can be adjusted * using the + and - keys. */ #ifdef __APPLE__ #include #else #include #endif #include "ramble.h" #include/* GLUT callback Handlers */ int w = 640; int h = 480; static void resize(int width, int height) { const float ar = (float) width / (float) height; glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0,ar,0.1,20.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity() ; w = width; h = height; } static void display(void) { const double t = glutGet(GLUT_ELAPSED_TIME) / 1000.0; const double a = t*90.0; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); Ramble(); glTranslated(0.0,0.0,-4.0); glutSolidTeapot(1.0); glutSwapBuffers(); } static void key(unsigned char key, int x, int y) { switch (key) { case 27 : exit(0); break; case 'q': case 'Q': RambleVK_Q(); break; case 'e': case 'E': RambleVK_E(); break; case 'a': case 'A': RambleVK_A(); break; case 'd': case 'D': RambleVK_D(); break; case 'w': case 'W': RambleVK_W(); break; case 's': case 'S': RambleVK_S(); break; case 'r': case 'R': RambleVK_R(); break; case 'f': case 'F': RambleVK_F(); break; } glutPostRedisplay(); } static void spacialkey(int key, int x, int y) { switch (key) { case GLUT_KEY_LEFT: RambleMoveLeft(); break; case GLUT_KEY_RIGHT: RambleMoveRight(); break; case GLUT_KEY_UP: RambleMoveForward(); break; case GLUT_KEY_DOWN: RambleMoveBack(); break; default: break; } glutPostRedisplay(); } void Mouse(int button, int state, int x, int y) /*当鼠标按下或拿起时会回调该函数*/ { if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) { RambleLButtonDown( x, y); } if(button == GLUT_LEFT_BUTTON && state == GLUT_UP) { RambleLButtonUp( x, y); } } void OnMouseMove(int x, int y) /*当鼠标移动时会回调该函数*/ { RambleMouseMove( x, y); } static void idle(void) { glutPostRedisplay(); } const GLfloat light_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f }; const GLfloat light_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f }; const GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; const GLfloat light_position[] = { 2.0f, 5.0f, 5.0f, 0.0f }; const GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f }; const GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f }; const GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; const GLfloat high_shininess[] = { 100.0f }; /* Program entry point */ int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitWindowSize(w,h); glutInitWindowPosition(10,10); glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutCreateWindow("GLUT Shapes"); glutReshapeFunc(resize); glutDisplayFunc(display); glutKeyboardFunc(key); glutSpecialFunc(spacialkey); glutMouseFunc(Mouse); glutMotionFunc(OnMouseMove); /*设置各种消息处理函数*/ glutIdleFunc(idle); RambleInitial(); RambleGetShape(&w,&h); glClearColor(1,1,1,1); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_LIGHT0); glEnable(GL_NORMALIZE); glEnable(GL_COLOR_MATERIAL); glEnable(GL_LIGHTING); glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient); glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse); glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient); glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, high_shininess); glutMainLoop(); return EXIT_SUCCESS; }
ramble.h:
#ifndef RAMBLE_H_INCLUDED #define RAMBLE_H_INCLUDED #include #include void RambleInitial(); void RambleGetShape(int *w, int *h); void Ramble(); void RambleReset(); void RambleMouseMove(int x,int y); void RambleLButtonDown(int x,int y); void RambleLButtonUp(int x,int y); void RambleSetSpeed(GLfloat mPStep); void RambleAddSpeed(GLfloat mPStep); void RambleSetSensitivity(GLfloat anglePTurn); void RambleAddSensitivity(GLfloat anglePTurn); void RambleTurnLeft(GLfloat angle); void RambleTurnRight(GLfloat angle); void RambleTurnUp(GLfloat angle); void RambleTurnDown(GLfloat angle); void RambleSlantLeft(GLfloat angle); void RambleSlantRight(GLfloat angle); void RambleMoveForward(GLfloat m); void RambleMoveBack(GLfloat m); void RambleMoveLeft(GLfloat m); void RambleMoveRight(GLfloat m); void RambleMoveUp(GLfloat m); void RambleMoveDown(GLfloat m); // for short call void RambleTurnLeft(); void RambleTurnRight(); void RambleTurnUp(); void RambleTurnDown(); void RambleSlantLeft(); void RambleSlantRight(); void RambleMoveForward(); void RambleMoveBack(); void RambleMoveLeft(); void RambleMoveRight(); void RambleMoveUp(); void RambleMoveDown(); // for message calls inline void RambleVK_A(){RambleTurnLeft(); } inline void RambleVK_D(){RambleTurnRight(); } inline void RambleVK_W(){RambleTurnUp(); } inline void RambleVK_S(){RambleTurnDown(); } inline void RambleVK_R(){RambleMoveUp(); } inline void RambleVK_F(){RambleMoveDown(); } inline void RambleVK_Q(){RambleSlantLeft(); } inline void RambleVK_E(){RambleSlantRight(); } inline void RambleVK_LEFT(){RambleMoveLeft(); } inline void RambleVK_RIGHT(){RambleMoveRight(); } inline void RambleVK_UP(){RambleMoveForward(); } inline void RambleVK_DOWN(){RambleMoveBack(); } #endif // RAMBLE_H_INCLUDED
ramble.cpp:
#include "ramble.h" #include #include #include #pragma comment(lib,"OpenGL32.lib") #pragma comment(lib,"Glu32.lib") #define PI 3.14159265 #define DEGtoRAD(angle) (angle * PI / 180.0) #define NEAREST 1.0f #define FAREST 40.0f #define MAXSPEED 100.0 #define MINSPEED 0.001f #define MAXSENSITIVITY 45.0 #define MINSENSITIVITY 0.01 int *pwidth; // pointer to global variable:window width int *pheight; // pointer to global variable:widow height GLfloat mRamble[16]; // View mode matrix GLfloat mpstep = 0.25; // move speed GLfloat anglepturn = DEGtoRAD(45.0); // turning sensitive (measured in rad) void RambleInitial() { glGetFloatv(GL_MODELVIEW_MATRIX, mRamble); // This function is to get the view matrix } // Get window size void RambleGetShape(int *w, int *h) { pwidth = w; pheight = h; } // when paint, put this function first to use the rambal void Ramble() { glLoadMatrixf(mRamble); // load view matrix to change the view sight } void RambleReset() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glGetFloatv(GL_MODELVIEW_MATRIX, mRamble); } void RambleMouseMove(int x,int y) { } void RambleLButtonDown(int x,int y) { if( x < *pwidth/3 ) RambleTurnLeft(); if( x > *pwidth*2/3 ) RambleTurnRight(); if( y < *pheight/3 ) RambleTurnUp(); if( y > *pheight*2/3 ) RambleTurnDown(); } void RambleLButtonUp(int x,int y) { } void RambleSetSpeed(GLfloat mPStep) { if( mPStep>MINSPEED && mPStepMINSENSITIVITY && anglePTurn
运行程序后,使用wsad实现前后左右转动,qe实现左右倾,方向键控制前后左右运动,rf上下运动。
如果你离远点很远的话,视图投影会有些不正常,这和AutoDesk 3DS MAX的渲染效果图里的漫游出现的情况有点类似,我猜他们会不会是按照这种方法设计的?
每次你做视图变换的时候,OpenGL都会重新一些其它的参数,因此,这种方法有可能很不安全,而且会浪费一些运算时间。下次再做个程序来改进吧。


代码纯属原创,转载请注明,有意见请发email到jianglve@live.cn

【OpenGL漫游的实现】

    推荐阅读