天天看點

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”

三、原理:

我們要完成如下功能:

鏡頭上下轉(沿紅色線),左右轉(沿綠色線)以及左右傾(沿藍色線),如圖:

OpenGL漫遊的實作

還有前後左右上下平移。

一、     要實作這些,原理非常簡單,分四步:

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]

繼續閱讀