资源描述
基于OpenGL的真实感图形渲染
OpenGL是一个跨平台的图形库,可在Windows、Unix、Mac等平台上运行。本书主要介绍怎样在Windows平台上建立OpenGL程序,并以Visual C++ 6.0编程环境为例来说明整个编程环境的建立和设置。
2.5.1 VC环境下OpenGL编程
本节在VC6.0环境下介绍两种OpenGL应用程序结构:Win32 Console Application和Windows MFC。
1.Win32 Console Application
在该OpenGL应用程序结构中,我们使用GLUT来响应、处理用户的交互操作;尽管GLUT可能无法满足编写功能完整的OpenGL应用程序的要求,但作为学习OpenGL却是一个非常好的起点。
首先运行Visual C++ 6.0从“File”菜单中选择“New”菜单项,在弹出的对话框中选择”Projects”标签,如图2-28所示。然后单击“Win32 Console Application”选项,再给应用程序指定目录和名字,如工程名为“ConsoleExpl”。单击“OK”按钮,确认由Visual C++ 6.0创建一个新工程。接着,显示如图图2-29所示的对话框,用以选择应用程序的类型。选择产生一个空的应用程序,单击“Finish”按钮,这样一个新的工程框架建立完成。随后,从“File”菜单中选择“New”菜单项,在弹出的对话框中选择“New”标签,创建一个“C++ Source File”如名字为“main”,并添加到该工程中,如图2-30 所示。
图2-28 选择新工程类型 图2-29 选择应用程序类型
通过如下的方法加入OpenGL的库文件:从“Project”菜单中选择“Settings”菜单项,在弹出的对话框中单击“Link”标签,在“Object/library modules”文本框中加入“opengl32.lib glu32.lib glut32.lib glew32.lib”即可,如图2-31所示。
图2-30 创建源文件加入工程 图2-31 OpenGL库文件加入当前工程
完成上述步骤后,一个Windows平台下进行OpenGL应用程序开发的环境已形成,便可进入具体的编程阶段。这里,给出一个简单的的OpenGL示例程序(ConsoleExpl),代码如下,运行结果如图2-32所示。
// main.cpp
#include <GL/glut.h>
#include <stdlib.h>
static GLfloat spin = 0.0;
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();//保存模型视图矩阵
glRotatef(spin, 0.0, 0.0, 1.0); //模型旋转变换
glBegin(GL_POLYGON);
glColor3f(1.0, 0.0,0.0); //设置当前颜色为红色
glVertex2f(0.0, -25.0);
glColor3f(0.0, 1.0, 0.0); //设置当前颜色为绿色
glVertex2f(25.0, -12.5);
glColor3f(0.0, 0.0, 1.0);
glVertex2f(25.0, 12.5);
glColor3f(1.0, 1.0, 0.0);
glVertex2f(0, 25.0);
glColor3f(1.0, 0.0, 1.0);
glVertex2f(-25.0, 12.5);
glColor3f(0.0, 1.0, 1.0);
glVertex2f(-25.0, -12.5);
glEnd();
glPopMatrix(); //恢复模型视图矩阵
glutSwapBuffers(); //交换缓冲区
}
void spinDisplay(void)
{
spin = spin + 2.0;
if (spin > 360.0)
spin = spin - 360.0;
glutPostRedisplay();
}
void init(void) //初始化绘制环境
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_SMOOTH);
}
void reshape(int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h); //设置视口
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0); //设置正投影
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void mouse(int button, int state, int x, int y)
{
switch (button) {
case GLUT_LEFT_BUTTON:
if (state = = GLUT_DOWN)
glutIdleFunc(spinDisplay); //左键单击进行模型旋转
break;
case GLUT_MIDDLE_BUTTON:
case GLUT_RIGHT_BUTTON:
if (state = = GLUT_DOWN)
glutIdleFunc(NULL);
break;
default:
break;
}
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init ();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMainLoop();
return 0;
}
图2-32 ConsoleExpl程序的运行结果
对该程序进行如下的简要解释:
(1)窗口管理
GLUT通过如下几个函数执行初始化窗口所需要的任务:
glutInit(int *argc, char **argv)对GLUT进行初始化,并处理所有的命令行参数。glutInit()应该在调用其他任何GLUT函数之前调用。
glutInitDisplayMode(unsigned int mode) 指定了是使用RGBA模式还是颜色索引模式(这里使用RGBA);指定是使用单缓冲还是使用双缓冲窗口(这里使用后者);还可以使用这个函数表示希望窗口拥有相关联的深度、模板、多重采样和/或累积缓冲区。例如,如果需要一个双缓冲、RGBA颜色模式以及带有一个深度缓冲区的窗口,可以调用glutInitDisplayMode(GLUT_ DOUBLE |GLUT_RGBA | GLUT_DEPTH)。
glutInitWindowPosition(int x, int y) 指定了窗口左上角的屏幕位置(100,100)。
glutInitWindowSize(int width, int size) 指定了窗口的大小(250, 250)(以像素为单位)。
int glutCreateWindow(char *string)创建了一个支持OpenGL渲染环境的窗口。这个函数返回一个唯一的标识符,标识了这个窗口。注意:在调用glutMainLoop()函数之前,这个窗口并没有显示。
(2)显示回调函数
glutDisplayFunc(void (*func)(void))是极其重要的事件回调函数,每当GLUT确定一个窗口的内容需要重新显示时,通过glutDisplayFunc()注册的那个回调函数(该例为display)就会被执行。因此,应该把重绘场景所需要的所有代码都放在这个显示回调函数里。如果程序修改了窗口的内容,有时候可能需要调用glutPostRedisplay(),这个函数将会指示glutMainLoop()调用已注册的显示回调函数。
(3)运行程序
最后,必须调用glutMainLoop()来启动程序。所有已经创建的窗口将会在这时显示,对这些窗口的渲染也开始生效。事件处理循环开始启动,已注册的显示回调函数被触发。一旦进入循环,它就永远不会退出。
(4)处理输入事件
可以使用下面这些函数注册一些回调函数,当指定的事件发生时,这些函数便会被调用:
glutReshapeFunc(void(*func)int w, int h))表示当窗口的大小发生改变时应该采取什么行动(该例为reshape)。
glutKeyboardFunc(void(*func)(unsigned char key, int x, int y))和glutMouseFunc(void(*func)(int button, int state, int x, int y))将键盘上的一个键或鼠标上的一个按钮与一个函数相关联,当这个键或按钮被按下或释放时,这个函数就会调用(该例为mouse)。
(5)空闲处理
可以在glutIdleFunc(void(*func)(void))回调函数中指定一个函数,如果不存在其他尚未完成的事件(例如,当事件循环处于空闲的时候),就执行这个函数。这个回调函数接受一个函数指针作为它的唯一参数。如果向它传递NULL(0),就相当于禁用这个函数。
该例中,init()函数初始化绘制环境,其中glClearColor (0.0, 0.0, 0.0, 0.0)设置背景色为黑色、glShadeModel (GL_SMOOTH)设置着色模型为平滑方式。在reshape()函数中,首先设置了视口的大小,然后设置投影方式为正投影,并切换当前矩阵为模型视图矩阵。在display()中,首先将窗口清除为当前的清除颜色,然后进行模型的旋转变换,并绘制一个多边形,最后交换缓冲区。函数spinDisplay()的作用是增大旋转角度并刷新窗口;函数mouse()通过鼠标按钮确定多边形是否进行旋转的切换。
2.Windows MFC
为了编写功能更加完备的OpenGL应用程序,尤其是在友好的用户交互方面,可以采用Windows MFC应用程序框架;在该框架中,利用MFC消息映射机制进行各种响应的处理。
由于OpenGL是一个与平台无关的三维图形接口,操作系统必须提供像素格式管理和渲染环境管理。OpenGL的绘图方式与Windows一般绘图方式是不同的,Windows一般的应用程序使用设备描述表(Device Context, DC)进行图形的绘制输出,但OpenGL并不使用标准的设备描述表,而是使用渲染描述表(Rendering Context, RC)完成图形图像的映射,其核心是像素格式的设置。
(1)像素格式
在进行OpenGL的绘图操作时,实际上是在进行设备像素的操作。OpenGL将数据转化为像素操作写入帧缓存中,OpenGL需要知道Windows的像素格式,或者说需要与其一致起来。在初始化OpenGL时,初始化函数需要一种叫做PIXELFORMATDESCRIPTOR的结构,来完成对像素属性的设置,包括缓存设置、颜色模式、颜色位数、深度缓存位数等。OpenGL通过PIXELFORMATDESCRIPTOR结构指定自己的像素格式,结构如下:
typedef struct tag PIXELFORMATDESCRIPTOR {
WORD nSize; //结构大小
WORD nVersion; //结构版本
DWORD dwFlags; //告知OpenGL如何处理像素
BYTE iPixelType; //颜色模式
BYTE cColorBits; //颜色位数
BYTE cRedBits; //通常为0
BYTE cRedShift; //通常为0
BYTE cGreenBits; //通常为0
BYTE cGreenShift; //通常为0
BYTE cBlueBits; //通常为0
BYTE cBlueShift; //通常为0
BYTE cAlphaBits; //RGBA颜色缓存中Alpha的位数
BYTE cAlphaShift; //现不支持置为0
BYTE cAccumBits; //累积缓存的位数
BYTE cAccumRedBits; //基本不被采用置为0
BYTE cAccumGreenBits; //基本不被采用置为0
BYTE cAccumBlueBits; //基本不被采用置为0
BYTE cAccumAlphaBits; //基本不被采用置为0
BYTE cDepthBits; //深度缓存位数
BYTE cStencilBits; //模板缓存位数
BYTE cAuxBuffers; //辅助缓存位数
BYTE iLayerType; //层面类型
BYTE bReserved; //置0
DWORD dwLayerMask; //置0
DWORD dwVisibleMask; //置0
DWORD dwDamageMask; //置0
} PIXELFORMATDESCRIPTOR;
(2)渲染描述表
如Widows程序的DC一样,OpenGL渲染描述表保存着在窗口中用来渲染一个场景所需的信息。一个OpenGL应用程序必须有一个RC,并且在进行OpenGL绘制之前它应该是当前的。实际上,RC是OpenGL输出与Windows DC联系的机制,在RC存入信息后,OpenGL就可以在Windows系统中更新一个窗口的图形状态。
在创建渲染描述表之前必须创造像素格式,这样,RC才知道怎样选择像素属性。RC是线性安全的,也就是说多个线程可以同时使用一个渲染描述表。在某一时刻一个线程只使用一个RC,且每个OpenGL线程必须有一个当前的RC支持工作。一般情况下,RC的管理需要以下OpenGL函数:
wglCreateContext(HDC hDC):创建一个与给定的DC兼容的RC,调用成功返回RC句柄,不成功返回NULL。
wglMakeCurrent(HDC hDC,HGLRC hRC):RC当前化,调用成功返回GL_TRUE,否则返回GL_FALSE。如果第二个参数为NULL,则是使当前RC非当前化。
wglDeleteContext(HGLRC hRC):删除RC,成功返回GL_TRUE,否则返回GL_FALSE。
wglGetCurrentContext():获取当前RC句柄。
wglGetCurrentDC():获取绑定当前RC的DC句柄。
下面我们结合一个具体的OpenGL应用程序(工程名为MFCOpenGLExpl,其运行结果和ConsoleExpl一样),分步骤来介绍MFC框架下的OpenGL编程;MFCOpenGLExpl是一通用的基础框架程序,MFC框架下的OpenGL应用程序开发均可参考。描述中将涉及到Visual C++程序设计的基础知识,如数据成员变量的增加、成员函数的创建、消息响应等。
第一步:建立工程。首先运行Visual C++ 6.0从“File”菜单中选择“New”菜单项,在弹出的对话框中选择”Projects”标签,如图2-33所示。然后单击“MFC AppWizard(exe)”选项,再给应用程序指定目录和名字MFCOpenGLExpl,并单击“OK”按钮。然后,在弹出的对话框中选择“Single doucument”,并单击“Finish”按钮完成一新工程的创建。
图2-33 选择新工程类型
第二步:设置OpenGL库。首先包含OpenGL头文件,打开工程中StdAfx.h文件,在其中包含glew.h和glut.h两个文件,如图2-34所示。然后连接OpenGL库文件,从“Project”菜单中选择“Settings”菜单项,在弹出的对话框中单击“Link”标签,在“Object/library modules”文本框中加入“opengl32.lib glu32.lib glut32.lib glew32.lib”即可,如图2-35所示。
图2-34 包含OpenGL头文件
图2-35 连接OpenGL库文件
第三步:添加变量和函数。首先在CMFCOpenGLExplView.h文件中,添加公共数据成员变量m_hRC、 m_pDC、m_rotAngle和成员函数InitializeOpenGL()、SetupPixelFormat()和RenderScene(),如图2-36所示;并在CMFCOpenGLExplView.cpp文件的构造函数中初始化这3个变量,代码如下。
图2-36 添加变量和函数
CMFCOpenGLExplView::CMFCOpenGLExplView()
{
m_hRC = NULL;
m_pDC= NULL;
m_rotAngle = 0.0f;
}
利用“MFC ClassWizard”,使CMFCOpenGLExplView类响应WM_CREATE、WM_SIZE、WM_ERASEBKGND、WM_DESTROY、WM_LBUTTONDOWN、WM_RBUTTONDOWN、和WM_TIMER这7个消息,得到相应的7个消息响应函数,如图2-37所示。
第四步:设置像素格式。在CMFCOpenGLExplView.cpp文件中定义SetupPixelFormat()成员函数,代码如下。
BOOL CMFCOpenGLExplView::SetupPixelFormat() //设置像素格式
{
static PIXELFORMATDESCRIPTOR pfd = // 填充该结构体
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
24,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
32,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
// 选择匹配像素格式,并返回索引
int m_nPixelFormat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd);
if ( m_nPixelFormat == 0 )
return FALSE;
if ( ::SetPixelFormat(m_pDC->GetSafeHdc(), m_nPixelFormat, &pfd) == FALSE)
return FALSE;
return TRUE;
}
第五步:使用RC。首先在成员函数InitializeOpenGL()中创建RC,该函数在CMFCOpenGLExplView.cpp文件中定义如下。
BOOL CMFCOpenGLExplView::InitializeOpenGL()
{
m_pDC = new CClientDC(this);
if(m_pDC == NULL)
{
MessageBox("Error Obtaining DC");
return FALSE;
}
if(!SetupPixelFormat())
return FALSE;
m_hRC = wglCreateContext (m_pDC->GetSafeHdc ());//创建
if(m_hRC == 0)
{
MessageBox("Error Creating RC");
return FALSE;
}
if(wglMakeCurrent (m_pDC->GetSafeHdc (), m_hRC)==FALSE) //当前化RC
{
MessageBox("Error making RC Current");
return FALSE;
}
glShadeModel(GL_SMOOTH); //设置着色模型方式
glClearColor(0.0f,0.0f,0.0f,0.0f); //设置背景色
glClearDepth(1.0f); //设置深度缓存的清除值
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0); //设置正投影
return TRUE;
}
在OnCreate()函数中调用InitializeOpenGL()函数,使得上述的工作有效,代码如下。
int CMFCOpenGLExplView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
InitializeOpenGL();
return 0;
}
应用程序窗口销毁时,应删除RC,由OnDestroy()完成,代码如下。
void CMFCOpenGLExplView::OnDestroy()
{
CView::OnDestroy();
// 释放RC
if(::wglMakeCurrent (0,0) == FALSE)
MessageBox("Could not make RC non-current");
if(::wglDeleteContext (m_hRC)==FALSE)
MessageBox("Could not delete RC");
if(m_pDC)
delete m_pDC;
m_pDC = NULL;
}
第六步:绘制场景。首先在成员函数OnSize()中设置视口并切换到模型视图矩阵,该函数在CMFCOpenGLExplView.cpp文件中定义如下。
void CMFCOpenGLExplView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (cy= =0) // 防止被零除
cy = 1; // 将cy设为1
glViewport(0, 0, cx, cy); // 设置视口
glMatrixMode(GL_MODELVIEW); // 切换到模型视图矩阵
glLoadIdentity();
}
为了避免屏幕的闪动,修改OnEraseBkgnd()函数如下:
BOOL CMFCOpenGLExplView::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
完成上述各项工作后,便可在OnDraw()函数中调用成员函数RenderScene()来绘制场景(代码如下),读者会发现该函数体其实就是ConsoleExpl工程中display()的函数体。
void CMFCOpenGLExplView::OnDraw(CDC* pDC)
{
CMFCOpenGLExplDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
RenderScene();
}
void CMFCOpenGLExplView::RenderScene()
{
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); //清除缓冲区
glPushMatrix();//保存模型视图矩阵
glRotatef(m_rotAngle, 0.0, 0.0, 1.0); //模型旋转变换
glBegin(GL_POLYGON);
glColor3f(1.0, 0.0,0.0);
glVertex2f(0.0, -25.0);
glColor3f(0.0, 1.0, 0.0);
glVertex2f(25.0, -12.5);
glColor3f(0.0, 0.0, 1.0);
glVertex2f(25.0, 12.5);
glColor3f(1.0, 1.0, 0.0);
glVertex2f(0, 25.0);
glColor3f(1.0, 0.0, 1.0);
glVertex2f(-25.0, 12.5);
glColor3f(0.0, 1.0, 1.0);
glVertex2f(-25.0, -12.5);
glEnd();
glPopMatrix();//恢复模型视图矩阵
SwapBuffers(wglGetCurrentDC());//交换缓冲区
}
绘制结果如图2-37所示。
图2-37 MFCOpenGLExpl程序的运行结果
展开阅读全文