基于glut的OpenGL框架(一)

<style type="text/css"> <!-- @page {margin:2cm} pre.cjk {font-family:"DejaVu Sans Condensed",monospace} p {margin-bottom:0.21cm} --> </style>

基于glutOpenGL框架(一)

——面向对象框架的搭建

我是一名OpenGL的初学者,在看完《OpenGL超级宝典》的前几章后,开始迫不及待地动手验证一下书上程序的代码了。我发现书上的例子程序是用C语言描述的,虽然简单,但却无法在此基础上进行扩展,即使扩展了也仅仅是添加了几个函数并且调用之,没有什么层次性。所以我开始利用C++和已经形成了标准的glut框架编写自己的OpenGL框架。

首先是我的main.cpp文件:

// main.cpp
// 110852 最后编辑
#include "GLWidget.h"
// 宽屏的程序要求纵横比16:9,我们指定高,宽就出来了。
#define _WINDOW_HEIGHT_     360
#define _WINDOW_WIDTH_      _WINDOW_HEIGHT_ * 16 / 9
static 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 );
    // 如果有必要的话,让其更新
    glutPostRedisplay( );
}
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 );             // 重改变形状函数
    glutIdleFunc( Idle );                   // 空转时运行的函数
    pWidget->Init( _WINDOW_WIDTH_, _WINDOW_HEIGHT_ );
    glutMainLoop( );
    return 0;
}

我的目标是制作一个宽高比为169的程序,暂时不考虑全屏。为什么是169呢?因为大多数计算机显示屏都是169,宽屏的显示器越来越多,方屏的显示器越来越少,我们学校里面今年来也都换成了宽屏的显示屏,而且我工作的游戏公司在制作游戏时的标准也是按照169制作的。适配方屏屏幕时上下空余部分自动变成黑色。所以我们在定义窗口大小时,只需指定高,宽可以让预编译器自动计算出来。这里我默认了640×480的分辨率。

用面向对象的方法编写OpenGL程序自然要用到类。这里我使用了自定义的GLWidget类,稍后我会对这个类进行介绍。这里我创建了一个全局静态指针(静态是因为它仅在main.cpp文件中使用)。

接下来则是一些回调函数的定义了,Reshape()函数作用是对窗口大小更改是我们该怎样应对,我们将在GLWidget::Reshape()里面介绍。Render()函数作用是进行渲染,这里我们用不透明的黑色清除了屏幕,并且使用一个白色的矩形填充了以后需要渲染的区域。随后的绘制就在GLWidget::Render()里面完成了。Idle()函数我认为是一个非常重要的函数了。记得我在编写DirectX相关程序时,自己就要写消息循环体,其中就有如果窗口没有任何消息时,我们该做什么。我和很多书上的做法是一样的,强制让程序渲染。这里也是一样的,用glPostRedisplay()函数告诉OpenGL,下一个循环一定要调用Render()函数进行渲染。

为了防止错误地使用了空指针,我们使用assert()函数来假定不会访问出错。assert.h被包含在GLWidget.h中。

main.cpp函数中我们用new创建了一个对象。我们有一些步骤可以放在构造函数里面做(比如说验证版本的一致啊,更新啦……扯远了),随后是初始化glut的相关组件并且注册回调函数,在初始化GLWidget控件之后glutMainLoop()表示开始了游戏的循环。

循环之后就是return0;,随后程序结束。但是我想告诉大家,这里有一个隐含的内存泄漏,因为glutMainLoop()函数没有办法跳出。起初我认为是有办法退出glut的循环的,但是在我看了这篇文章:

OpenGL内存泄漏之主循环函数glutMainLoop()

之后,我发现没法跳出循环了!由于各个glut的实现不一样,在试了原作者的方法后,我发现没有找到相关的函数。所以我索性后面不写对pWidget空间的释放了,在需要退出程序的时候,我们这样调用:deletepWidget; exit( 0 );。这样也能不造成内存泄漏。

随后介绍的就是GLWidget类,它维持着我们需要处理OpenGL图形的相关代码。接下来就是GLWidget类的定义:

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

这里定义了构造函数、析构函数、初始化、释放空间、渲染和重新改变大小的函数。私有变量有初始化时候的宽和高,以及保存的宽高比。接下来我们看GLWidget.cpp文件。

// GLWidget.cpp 包含了控件的使用
// 11:14:27 最后编辑
#include "GLWidget.h"
GLWidget::GLWidget( void )
{
    // 构造函数的代码在这里
    m_Width     = 0.0;
    m_Height    = 0.0;
}
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 )
{
    // 渲染代码
}
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( );
}

构造函数和析构函数就介绍了。Init()函数保存了传入的宽和高,并且规定了清除屏幕时使用的颜色是不透明的黑色(R0G0B0A255)。这里介绍Reshape()函数。假设用户对窗口改变了形状,这是传入的widthheight就是新的窗口宽和高,我们用aspectRatio来保存宽高比,

随后我们就要进行判断了。如果当前的宽高比比我们期望的宽高比小,比如说这种情况:


我们这个时候让中间白色场景的高为smallHeight,两边黑框的高为heightBlank,计算之,并且设置视口(viewport,即我们能绘制的区域)。GlViewport函数的原型为:

glViewport( GLint x, GLint y, GLsizei width, GLsizei height );

第一、二个参数是屏幕的起始坐标,后面的两个参数是从这个坐标开始延伸的宽和高。注意这里遵循屏幕坐标系。所以我们填入了glViewport(0, heightBlank, GLint( width ), smallHeight );。如果前的宽高比比我们期望的宽高比大,如这样:


同理分析,这里就不介绍了。

然后设置投影矩阵为单位矩阵(重置投影矩阵),并且使用glOrtho()函数设置正交投影空间,glOrtho()函数的原型是这样的:

glOrtho( GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near_val, GLdouble far_val );

这里设定了左、右、下、上、近、远六个参数,就像一个长方体一样。如果要显得有三维的感觉,使用gluPerspective()函数设置透视投影空间,它看起来很像金字塔削去了尖头剩下的部分。这里不详细介绍gluPerspective()函数了,由于我们主要处理二维图形,因此near_valfar_val就简单地设为-10.010.0了。最后让模型矩阵为单位矩阵。

编译成功,链接,在g++下需要加上-lglut这个选项。如果你能顺利地运行,那么太好了,我的小程序能在你的机子上运行了。运行的时候是不是这个结果啊?下面是我在Ubuntu下的截图:


是一个空白,宽和高分别是640360。在随后的介绍中我们将会在这里绘制一些简单的图形。相信你一定觉得这个例子很简单!


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值