精华区 [关闭][返回]

当前位置:网易精华区>>讨论区精华>>电脑技术>>三维设计>>名词解释>>创建OpenGL图形类实现Visual C++中三维图形绘制

主题:创建OpenGL图形类实现Visual C++中三维图形绘制
发信人: xzwind_2003(WIND)
整理人: avexdesign(2003-11-11 19:17:54), 站内信件


摘  要 本文简要介绍了OpenGL的基本发展状况及工作机制,重点运用面向对象思想论述了创建可重用图形类CopenGL在Visual C++三维图形绘制中的目的意义、操作、具体实施等诸多事项。

关键词 OpenGL  面向对象  重用  着色描述表  设备描述表

 1、引言 

随着计算机图形学的发展和不断完善,三维图形的应用也越来越广泛,三维图形应用软件也得到相应的发展。作为目前较为领先的主流3D软件,OpenGL是一种比较完善的三维开放图形标准,它作为一种硬件图形的软件接口,显著的优点是作为一个独立的工作平台,独立于硬件设备、窗口系统和操作系统,用它编写的软件可以在UNIX、Windows95 OSR2、Window 98/NT等系统间实现移植。

但是,OpenGL仅是一个包含120多个图形函数组成的图形库,它缺乏面向对象能力,不符合当前流行的软件设计思想,况且它与操作系统之间的连接烦琐,每次创建OpenGL应用程序时都需要重新书写连接代码,而无法共享这部分公用代码,另外在Visual C++环境中编程时,它把对像素结构描述、着色环境创建等代码都添加到视类中处理,不符合软件编程习惯,不利于了解程序编制的流程、思路和思想。因此,按照面向对象思想创建一个OpenGL图形类,封装或重载OpenGL函数或代码,可以很好地解决上面的问题。

2、OpenGL简介 

       OpenGL是近几年发展起来的一个性能卓越的三维图形平台。它的前身是由SGI公司为其图形工作站开发的IRIS GL,后来SGI和微软共同开发了Windows NT下的新版本,即OpenGL1. 1,并把它集成到Windows95 OSR2中。现在,OpenGL以动态链接库形式挂接到Windows NT 3. 51、Windows NT 4. 0和Windows OSR2、Windows 98等环境中,并把OpenGL图形库封装在Visual C++2. 0及其以上版本中,开发者可以在多种硬件平台及操作系统下方便地利用这个图形库,创建出接近光线跟踪的高质量静止或动画的三维彩色图像,而且要比光线跟踪算法快一个数量级。OpenGL的基本工作机制流程图如图1所示。

图1  OpenGL基本工作流程图
 
 
  

为了利用Visual C++的强大功能来实现对OpenGL三维图形的绘制,Windows提供了OpenGL32. DLL和GLU32. DLL动态链接库,Visual C++包含了GL库(opengl32. Lib)、辅助库(glaux. lib)和实用库(glu32. lib),并且OpenGL在网络上工作时,显示图形的计算机(客户机)可以不是运行图形程序的计算机(服务器),客户机与服务器可以是不同类型的机器,只要两者服从相同的协议。另外,OpenGL1.1版本中加强并引入了一些新功能,如:在增强元文件中包含OpenGL调用,改进打印机支持,提高顶点数组的新特性,提高顶点位置、法线、颜色及色彩指数、纹理坐标、多边形边缘标识的传输速度,引入新的纹理特性,等等。这些都为在微机上实现高品质、交互式三维图像开发提供了良好的便利条件。 

3、创建OpenGL可重用图形类 

       创建COpenGL可重用图形类的一个显著特性是通过对象的封装性而隐藏复杂性来简化程序设计。另外通过封装部分标准OpenGL函数代码实现参数化抽象,进行函数重载,使在同一个作用域内的若干个参数特征不同的函数使用相同的函数名称,而实现函数的算法和运算符的语义因参数特征或被操作数类型的各异而不同,这样就可以增强程序的可读性、灵活性和简便性。

    COpenGL只是一个基类,它使用抽象的方法解决一般性、普遍性的问题。为把原有的一般性问题具体化或加以拓展,可利用继承性派生其子类,在派生类中通过虚函数实现其多态性,同时可加以拓展,增加其属性和函数,增强功能。这样只需增加少量代码就可以开发出解决特定应用需要的解,从而大大减少了程序的冗余信息,并且可维护性好。因此,建立可重用图形类是一件一劳永逸的事情。

COpenGL的具体代码如下:

//OpenGL.h:创建的COpenGL可重用图形类头文件

#ifndef _GL_H

#define _GL_H

#include "gl/gl.h"

#include "gl/glu.h"

#include “gl/glaux.h”

class COpenGL 

{

public:

       COpenGL();                   //构造函数

       virtual ~COpenGL();            //析构函数

protected:

       HGLRC m_hrc;                //创建的着色描述表

       BOOL m_bDoubleBuffer;             //标识是否是双缓存

       CDC* m_pdc;                             //指向和m_hrc相连的CDC的指针

       BOOL m_bDrawToBitmap;          //标识是否支持DIB位图

public:

       BOOL Create(CWnd* pWnd,

int   iPixelType=PFD_TYPE_RGBA,//RGBA模式

DWORD dwFlags=PFD_DOUBLEBUFFER| //支持双缓存

PFD_SUPPORT_OPENGL| //支持OpenGL

PFD_DRAW_TO_WINDOW); //支持窗口

       BOOL Create(CDIB* pDIB,

int iPixelType=PFD_TYPE_RGBA,  //RGBA模式

DWORD dwFlags=PFD_SUPPORT_ORENGL|  //支持OpenGL

PFD_SUPPORT_GDI|   //支持GDI

PFD_DRAW_TO_BITMAP);  //支持位图

       CDC* GetDC(); //获得DC

public:

       void Init(void);

       void Destroy(void);

       void MakeCurrent(void);

BOOL OnSize(int cx,int cy);

       virtual BOOL OnCreate(CWnd* pWnd,PIXELFORMATDESCRIPTOR* pfd){

TRACE0(“创建OpenGL图形类\r\n”); return FALSE;}

public:

       void glVertex(GLdouble x, GLdouble y);

       void glVertex(GLfloat x, GLfloat y);

       void glVertex(GLint x, GLint y);

       void glVertex(GLshort x, GLshort y);

       void glVertex(GLdouble x, GLdouble y, GLdouble z);

       ……

       void glVertex(GLdouble x, GLdouble y, GLdouble z,GLdouble w);

       ……

       ……

};

#endif _GL_H

1>初始化并设置当前着色描述表 

    通过成员函数Create定义像素格式(PIXELFORMAT)并创建着色描述表(Rendering Contexts,即RC)。

    由于OpenGL窗口要求有自己的像素格式,即只有从OpenGL窗口客户区域获得的设备描述表(Device Contexts,即DC)才允许在窗口内绘图。函数ChoosePixelFormat使用有DC支持的与所给的像素格式描述(PIXELFORMATDESCRIPTOR)最相匹配的像素格式;函数SetPixelFormat将DC中像素格式设置为有参数iPixelFormat所指定的像素格式。

  在Visual C++中,绘图需要DC,而在OpenGL编程环境中,需要使用RC。RC并不等同于DC,它需在由产生RC的DC句柄指定的设备上绘图。绘图时,要先初始化定义DC的像素格式,然后用wglCreateContext函数创建一个RC,并用wglMakeCurrent函数把它作为当前线程的RC,最后调用OpenGL函数在指定的设备上绘图。绘图结束后,将它释放。

  其具体程序实现如下:

BOOL COpenGL::Create(CWnd* pWnd,int iPixelType,DWORD dwFlags)

{

       static PIXELFORMATDESCRIPTOR pfd={

              sizeof(PIXELFORMATDESCRIPTOR), //pfd的大小

              1,                                                     //版本号

dwFlags,                                           //像素缓冲区属性

iPixelType,                                        //像素格式类型

              24,                                                    //24位颜色深度

              0,0,0,0,0,0,                                        //忽略颜色位

              0,                                                     //无alpha缓存

              0,                                                     //忽略转换位

              0,                                                     //无累计缓存

              0,0,0,0,                                             //忽略计位

              32,                                                    //32位深度缓存

              0,                                                     //无模板缓存

0,                                                     //无辅助缓存

PFD_MAIN_PLANE,                         //层类型

0,                                                     //保留

0,0,0,                                                //忽略层屏蔽

       };

       OnCreate(pWnd,&pfd);

       m_pdc=new CClientDC(pWnd);                 //构造CDC对象

       int nPixelFormat=ChoosePixelFormat(m_pdc->m_hDC,&pfd); //选择像素格式

       if (nPixelFormat==0){

              MessageBox ("选择像素格式失败!像素格式为:%d\r\n",nPixelFormat ");

              return FALSE;

       }

       if (!SetPixelFormat(m_pdc->m_hDC,nPixelFormat,&pfd)){ //设置像素格式

              MessageBox ("设置像素失败! ");

              return FALSE;

       }

       m_hrc=wglCreateContext(m_pdc->m_hDC);             //创建RC

       if (!m_hrc){

              MessageBox ("产生设备描述表失败!");

              return FALSE;

       }

       return TRUE;

}

void COpenGL::MakeCurrent()

{

       ASSERT(m_hrc);

       if (m_hrc!=wglGetCurrentContext()){

              if (!wglMakeCurrent(m_pdc->m_hDC,m_hrc)){  //使成为当前调用线程的RC

                     MessageBox("使成为当前RC失败!");

                     return ;

              }

       }

}

       依据同样原理可用Create()函数对支持GDI的DIB进行初始化,并用GetDC()函数获得CDC句柄,在这里不具体详述。

2>初始化OpenGL场景 

  通过成员函数Init()初始化OpenGL场景,设置其执行功能的特性。具体程序实现如下:

void COpenGL::Init(void)

{

       MakeCurrent();

       glClearDepth(1.0f);                  //定义深度缓冲区的清除值

       glEnable(GL_DEPTH_TEST);               //允许进行深度比较,并更改深度缓冲区

       glEnable(GL_COLOR_MATERIAL);       //允许用一个或多个材质参数跟踪当前颜色

       glEnable(GL_LIGHT3);                         //可使用3个光源求解光照方程

       ……

}

3>处理视口变化及设置图形显示模式 

  通过视口变换在实际屏幕窗口中定义一个观察或显示虚拟场景的视口,由于Windows窗口大小发生变化时,OpenGL不会自动调整视口,须用OnSize()函数进行处理。另外,为了使三维物体变换为二维图形显示出来,必须设置投影变换图形显示模式。最常用、最基本的投影模式为:正射投影(Orthographic Projection)和透视投影(Perspective Projection)。

  其程序实现如下:

BOOL COpenGL::OnSize(int cx,int cy)

{

       if ((cx<=0)||(cy<=0)) return FALSE;

MakeCurrent();

glViewport(0,0,cx,cy); //定义视口

glMatrixMode(GL_PROJECTION); //使投影矩阵堆栈成为当前堆栈

glLoadIdentity(); //设为单位矩阵

gluOrtho2D(0,cx,0,cy); //定义二维正射投影矩阵

glMatrixMode(GL_MODELVIEW); //使模型取景矩阵堆栈成为当前堆栈

return(TRUE);

}

4>函数重载 

  函数重载是用一个同名函数将所有相关函数封装起来,而具体实现是用内联函数简单地调用相应的OpenGL 函数。例如把glVertex2{dfis}()、glVertex3{dfis}()、glVertex4{dfis}()和glVertex{234}{dfis}v()等函数封装起来,而用不同参数的glVertex()函数来实现其功能。

  具体实现以glVertex2i()为例:

void COpenGL::glVertex(GLint x, GLint y)

{

       glVertex2i(x,y);

}

5>善后处理 

  当处理完毕后,必须释放着色描述表并删除与之相连的DC对象。程序实现如下:

void COpenGL::Destroy(void)

{

       if (m_hrc){

              if (m_hrc==::wglGetCurrentContext())

                     ::wglMakeCurrent(NULL,NULL);

              ::wglDeleteContext(m_hrc);   //释放当前RC

              m_hrc=NULL;

       }

       if (m_pdc){

              delete m_pdc;                      //释放DC句柄

              m_pdc=NULL;

       }

}

4、图形绘制 

       现在,可以在Visual C++环境中利用向导建立工程了!完毕后,把COpenGL类添加到工程中,并将头文件OpenGL.h包含到相应的文件中(一般在视类的头文件中),添加数据成员:

COpenGL  m_pGL;

CDIB  *m_pDIB;

这样,其余的工作就可以结合普通类和OpenGL的操作特点进行相应处理了,可支持DIB位图的色彩斑斓、五彩缤纷的三维图形世界就在你眼前了,加油!

5、结语 

       本文是运用面向对象思想对传统OpenGL绘图编程习惯的一种改进,所提供类的功能尽管不是很完善,但其可扩充性较强,可按要求对类本身或其派生类进行改进而实现编程目的。本文所涉及的程序全部在Visual C++ 5.0中调试通过,所绘制的三维图形运行效果良好。

  

参考文献 

1、贾志刚.精通OpenGL.电子工业出版社.1998.8

2、曾 志等.Win32高级图形编程技术.电子科技大学出版社.1998.7



----
希望大家可以帮我解决这个问题!
多谢!!

[关闭][返回]