在用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漫游的实现】
推荐阅读