读书人

基于glut的OpenGL框架(2)

发布时间: 2012-11-17 11:14:16 作者: rapoo

基于glut的OpenGL框架(二)

基于glut的OpenGL框架(二)

——加入键盘控制

前面一篇文章我向大家介绍了我自己制作的基于glut的OpenGL框架。接下来我们要加入交互的内容,说到交互,其实就是键盘的控制了。我们会在这一次体会到面向对象机制给我们带来的巨大便利以及glut给我们带来的诸多方便。

组件化glut按键处理功能,这是一个好主意。在这个想法下,我将glut按键处理的功能写到一个类,叫作KeyEvent。下面是我们这个类的定义:

#ifndef KEYEVENT_H#define KEYEVENT_H#define _MAX_KEY_NUM_   256class KeyEvent{public:    KeyEvent( void );    void KeyDown( int key );    void KeyUp( int key );protected:    char m_KeyState[_MAX_KEY_NUM_];};#endif // KEYEVENT_H

这个类非常简单,包含了一个构造函数、按键按下时的函数以及按键弹上时的函数。私有成员为256个按键。定义_MAX_KEY_NUM_为256是因为考虑键盘一般有108个按键,而一个字节最多可以保存28即256中状态。所以就定义了256。以后为了适应不同的情况,可以将这个宏的值进行改变。

接下来我要向大家展示一个程序,并且希望大家能够编译运行再测试一下这个程序,看它的按键响应效果。(来自《OpenGL超级宝典》)

// Points.c// OpenGL SuperBible// Demonstrates OpenGL Primative GL_POINTS// Program by Richard S. Wright Jr.#include <GL/glut.h>#include <math.h>// Define a constant for the value of PI#define GL_PI 3.1415f// Rotation amountsstatic GLfloat xRot = 0.0f;static GLfloat yRot = 0.0f;// Called to draw scenevoid RenderScene(void){    GLfloat x,y,z,angle; // Storeage for coordinates and angles    // Clear the window with current clearing color    glClear(GL_COLOR_BUFFER_BIT);    // Save matrix state and do the rotation    glPushMatrix();    glRotatef(xRot, 1.0f, 0.0f, 0.0f);    glRotatef(yRot, 0.0f, 1.0f, 0.0f);    // Call only once for all remaining points    glBegin(GL_POINTS);    z = -50.0f;    for(angle = 0.0f; angle <= (2.0f*GL_PI)*3.0f; angle += 0.1f)    {        x = 50.0f*sin(angle);        y = 50.0f*cos(angle);        // Specify the point and move the Z value up a little        glVertex3f(x, y, z);        z += 0.5f;    }    // Done drawing points    glEnd();    // Restore transformations    glPopMatrix();    // Flush drawing commands    glutSwapBuffers();}// This function does any needed initialization on the rendering// context.void SetupRC(){    // Black background    glClearColor(0.0f, 0.0f, 0.0f, 1.0f );    // Set drawing color to green    glColor3f(0.0f, 1.0f, 0.0f);}void SpecialKeys(int key, int x, int y){    if(key == GLUT_KEY_UP)        xRot-= 5.0f;    if(key == GLUT_KEY_DOWN)        xRot += 5.0f;    if(key == GLUT_KEY_LEFT)        yRot -= 5.0f;    if(key == GLUT_KEY_RIGHT)        yRot += 5.0f;    if(key > 356.0f)        xRot = 0.0f;    if(key < -1.0f)        xRot = 355.0f;    if(key > 356.0f)        yRot = 0.0f;    if(key < -1.0f)        yRot = 355.0f;    // Refresh the Window    glutPostRedisplay();}void ChangeSize(int w, int h){    GLfloat nRange = 100.0f;    // Prevent a divide by zero    if(h == 0) h = 1;    // Set Viewport to window dimensions    glViewport(0, 0, w, h);    // Reset projection matrix stack    glMatrixMode(GL_PROJECTION);    glLoadIdentity();    // Establish clipping volume (left, right, bottom, top, near, far)    if (w <= h)        glOrtho (-nRange, nRange, -nRange*h/w, nRange*h/w, -nRange, nRange);    else        glOrtho (-nRange*w/h, nRange*w/h, -nRange, nRange, -nRange, nRange);    // Reset Model view matrix stack    glMatrixMode(GL_MODELVIEW);    glLoadIdentity();}int main(int argc, char* argv[]){    glutInit(&argc, argv);    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);    glutCreateWindow("Points Example");    glutReshapeFunc(ChangeSize);    glutSpecialFunc(SpecialKeys);    glutDisplayFunc(RenderScene);    SetupRC();    glutMainLoop();    return 0;}

运行一下,你会看到是一个螺线形(圆形),截图如下:


基于glut的OpenGL框架(2)

按上下左右可以移动螺线形,但是的细心的同学可能发现了,如果你按紧左键,它会连续地运动,但是其中有一个小小的停顿。这是由于这种机制下的按键是按照按下按键的ASCII码来响应的。不信大家可以打开gedit或者notepad,按紧a,你会发现第一个a和第二个a出现中间有一段停顿,而后面的a则较连贯地显示出来。这样的效果是不符合大多数游戏的交互体验的,而正是这样,我才开始了新的摸索,好在glut还提供了这样一个函数:glutKeyboardUpFunc,它可以对按键弹上进行回调。glutKeyboardUpFunc()函数的声明是这样的:

FGAPI void FGAPIENTRY glutKeyboardUpFunc( void (* callback)( unsigned char, int, int ) );


有了这个函数,我们就可以知道玩家按下一个按键有多久,可以实现一些游戏当中才能出现的按键效果,比如组合键、蓄气、二倍速跑、二段跳等。了解了这么多,我开始介绍KeyEvent类的实现了。

// KeyEvent.cpp 键盘事件的实现// 19时35分01秒 最后编辑#include <cstring>#include "KeyEvent.h"KeyEvent::KeyEvent( void ){    // 初始化这些按键    using namespace std;    memset( m_KeyState, 0, _MAX_KEY_NUM_ );}void KeyEvent::KeyDown( int key ){    m_KeyState[key] = 1;}void KeyEvent::KeyUp( int key ){    m_KeyState[key] = 0;}

在实现中,我们对108个按键的状态进行标识,以便使用的时候读取。KeyDown()函数对按下的按键置1,KeyUp()函数对弹出的按键置0。

现在将我们的按键功能集成到GLWidget类中吧。当然是使用继承啦。

#ifndef GLWIDGET_H#define GLWIDGET_H#include <assert.h>#include <GL/glut.h>#include "KeyEvent.h"class GLWidget: public KeyEvent{public:    GLWidget( void );    ~GLWidget( void );    void Init( int width, int height );    void Release( void );    void Render( void );    void ProcessKey( void );                            // 按键处理    void Reshape( int width, int height );              // 重新改变窗口大小private:    float m_X2;    GLdouble m_Width, m_Height;    GLdouble m_AspectRatio;};#endif // GLWIDGET_H

以上是GLWidget类的定义,我们看到,该类继承自KeyEvent类,表示我们108个按键的状态都可以在GLWidget类的内部使用。此外定义了一个成员函数ProcessKey(),它专门用来处理按键的响应。

以下是GLWidget.cpp的实现:

// GLWidget.cpp 包含了控件的使用// 11:14:27 最后编辑#include "GLWidget.h"GLWidget::GLWidget( void ){    // 构造函数的代码在这里    m_Width     = 0.0;    m_Height    = 0.0;    m_X2 = 160.0f;}GLWidget::~GLWidget( void ){    Release( );}void GLWidget::Init( int width, int height ){    // 保存初始化时窗口的宽和高    m_Width = GLdouble( width );    m_Height = GLdouble( height );    m_AspectRatio = m_Width / m_Height;    // 初始化代码    glClearColor( 0.0, 0.0, 0.0, 1.0 );}void GLWidget::Render( void ){    // 渲染代码    glColor3ub( 255, 0, 0 );    glRectf( 0.0f, 0.0f, m_X2, 180.0f );    glRectf( 160.0f, 260.0f, 180.0f, 280.0f );}void GLWidget::Release( void ){    // 释放空间代码}void GLWidget::Reshape( int width, int height ){    // 改变大小时程序如何应对?    GLdouble aspectRatio = GLdouble( width ) / GLdouble( height );    // 设置视口    if ( aspectRatio < m_AspectRatio )    {        GLint smallHeight = GLint( GLdouble( width ) / m_AspectRatio );        GLint heightBlank = ( GLint( height ) - smallHeight ) / 2;        glViewport( 0, heightBlank, GLint( width ), smallHeight );    }    else    {        GLint smallWidth = GLint( GLdouble( height ) * m_AspectRatio );        GLint widthBlank = ( GLint( width ) - smallWidth ) / 2;        glViewport( widthBlank, 0, smallWidth, GLint( height ) );    }    glMatrixMode( GL_PROJECTION );    glLoadIdentity( );    // 设置裁剪区域(左右下上近远)    glOrtho( 0.0, m_Width, 0.0, m_Height, -10.0, 10.0 );    // 为模型视图载入标准矩阵    glMatrixMode( GL_MODELVIEW );    glLoadIdentity( );}void GLWidget::ProcessKey( void ){    // 注意:这里大写的B和小写的b是不一样的。如果你开启了caps lock键,那么按下b键就会有效,否则无效。    if ( m_KeyState['b'] )    {        m_X2 += 0.5f;    }    if ( m_KeyState['x'] )    {        delete this;        exit( 0 );    }}

前面的函数我在第一节和大家都介绍了,下面我介绍一下ProcessKey()函数。ProcessKey()函数体内包含了两条if语句,表示对按键b和x的处理。这里注意,B和b的意义不一样,要响应B键,要在CapsLock键按下才有效。此外,当我们按下x键后,将会退出程序。不必担心deletethis语句,因为在这条语句后,我们不会访问this下的任何数据成员,我们会直接调用exit(0 )退出。

最后,让我们再看看main.cpp是什么样子吧。

// main.cpp// 11时08分52秒 最后编辑#include "GLWidget.h"// 宽屏的程序要求纵横比16:9,我们指定高,宽就出来了。#define _WINDOW_HEIGHT_     360#define _WINDOW_WIDTH_      _WINDOW_HEIGHT_ * 16 / 9static GLWidget* pWidget = 0;void Reshape( int x, int y ){    assert( pWidget != 0 );    pWidget->Reshape( x, y );}void Render( void ){    glClear( GL_COLOR_BUFFER_BIT );           // 用黑色清屏    glColor3ub( 255, 255, 255 );    glRecti( 0, 0, _WINDOW_WIDTH_, _WINDOW_HEIGHT_ );// 绘制白色的矩形背景    // 执行widget里的绘图函数    assert( pWidget != 0 );    pWidget->Render( );    // 交换缓存    glutSwapBuffers( );}void Idle( void )       // 空转时候运行的函数{    assert( pWidget != 0 );    // 处理按键    pWidget->ProcessKey( );    // 如果有必要的话,让其更新    glutPostRedisplay( );}void KeyDown( unsigned char key, int, int ){    assert( pWidget != 0 );    pWidget->KeyDown( key );}void KeyUp( unsigned char key, int, int ){    assert( pWidget != 0 );    pWidget->KeyUp( key );}int main( int argc, char** argv ){    // 初始化控件类    pWidget = new GLWidget;    glutInit( &argc, argv );    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );    glutInitWindowSize( _WINDOW_WIDTH_, _WINDOW_HEIGHT_ );    glutCreateWindow( "Simple Object" );    glutDisplayFunc( Render );              // 渲染函数    glutReshapeFunc( Reshape );             // 重改变形状函数    //glutSpecialFunc( Special );             // 特殊函数    glutKeyboardFunc( KeyDown );            // 键盘按下函数    glutKeyboardUpFunc( KeyUp );            // 键盘按上函数    glutIdleFunc( Idle );                   // 空转时运行的函数    pWidget->Init( _WINDOW_WIDTH_, _WINDOW_HEIGHT_ );    glutMainLoop( );    return 0;}


实现连续响应按键的关键一步,就是将响应按键的内容放入Idle()函数中,而Idle()函数在程序空转的时候执行按键的响应和画面的渲染。此外,在按键按下的时候,我们通过调用GLWidget::KeyDown()和GLWidget::KeyUp()函数记录下按键的状态,从而保证按键状态是最新的。

好了,激动人心的一刻到来了,我们要运行我们的源程序,看看它对于响应按键的强大吧。

基于glut的OpenGL框架(2)

我们按下b键,可以看到下面红色矩形逐渐变宽,这种变化是实时的。而按下x键则退出程序。

(程序源代码下载地址在这里)

读书人网 >编程

热点推荐