在用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.h ramble.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 <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include "ramble.h"
#include <stdlib.h>
/* 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 <GL/gl.h>
#include <GL/glu.h>
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 <math.h>
#include <GL/gl.h>
#include <GL/glu.h>
#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 && mPStep<MAXSPEED) mpstep = mPStep;
}
void RambleAddSpeed(GLfloat mPStep)
{
RambleSetSpeed(mPStep + mpstep);
}
void RambleSetSensitivity(GLfloat anglePTurn)
{
if( anglePTurn>MINSENSITIVITY && anglePTurn<MAXSENSITIVITY) anglepturn = anglePTurn;
}
void RambleAddSensitivity(GLfloat anglePTurn)
{
RambleSetSensitivity(anglepturn + anglePTurn);
}
// Turning functions
void RambleTurnLeft(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);// to ensure that you are operating view matrix
glLoadMatrixf(mRamble);// load our own matrix
glRotated( angle, 0.0,-1.0, 0.0);// turn left
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);// get out our matrix to save it.
}
void RambleTurnRight(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glRotated( angle, 0.0, 1.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleTurnUp(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glRotated( angle,-1.0, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleTurnDown(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glRotated( angle, 1.0, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleSlantLeft(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glRotated( angle, 0.0, 0.0,-1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleSlantRight(GLfloat angle)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glRotated( angle, 0.0, 0.0, 1.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
// Moving functions
void RambleMoveForward(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated( 0.0, 0.0, m);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleMoveBack(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated( 0.0, 0.0,-m);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleMoveLeft(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated( m, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleMoveRight(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated(-m, 0.0, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleMoveUp(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated( 0.0,-m, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
void RambleMoveDown(GLfloat m)
{
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(mRamble);
glTranslated( 0.0, m, 0.0);
glGetFloatv(GL_MODELVIEW_MATRIX, mRamble);
}
// for short call
void RambleTurnLeft()
{
RambleTurnLeft(anglepturn);
}
void RambleTurnRight()
{
RambleTurnRight(anglepturn);
}
void RambleTurnUp()
{
RambleTurnUp(anglepturn);
}
void RambleTurnDown()
{
RambleTurnDown(anglepturn);
}
void RambleSlantLeft()
{
RambleSlantLeft(anglepturn);
}
void RambleSlantRight()
{
RambleSlantRight(anglepturn);
}
void RambleMoveForward()
{
RambleMoveForward(mpstep);
}
void RambleMoveBack()
{
RambleMoveBack(mpstep);
}
void RambleMoveLeft()
{
RambleMoveLeft(mpstep);
}
void RambleMoveRight()
{
RambleMoveRight(mpstep);
}
void RambleMoveUp()
{
RambleMoveUp(mpstep);
}
void RambleMoveDown()
{
RambleMoveDown(mpstep);
}
運作程式後,使用wsad實作前後左右轉動,qe實作左右傾,方向鍵控制前後左右運動,rf上下運動。
如果你離遠點很遠的話,視圖投影會有些不正常,這和AutoDesk 3DS MAX的渲染效果圖裡的漫遊出現的情況有點類似,我猜他們會不會是按照這種方法設計的?
每次你做視圖變換的時候,OpenGL都會重新一些其它的參數,是以,這種方法有可能很不安全,而且會浪費一些運算時間。下次再做個程式來改進吧。
代碼純屬原創,轉載請注明,有意見請發email到[email protected]