实验一基于opengl的二维图形编程
实验目的:
通过配置opengl编程环境,绘制基本图形和二维变换,掌握opengl的函数调用,深入理解opengl的框架和二维观察流水线的流程。
实验内容:
1. opengl编程环境组建(基于VC6.0或VC2008)
2. 基本图形绘制
3. 图形的二维变换
4. 二维观察流水线
5. 分形(选做,了解)
实验设备:
PC,windows OS,VC++6.0/VC++2008环境, opengl函数包
预备知识:
1. opengl框架及函数库
2. VC++编程知识
3. 计算机图形学二维变换矩阵运算
4. 二维图形观察流水线过程
实验步骤:
1. opengl编程环境组建
参见PPT课件,配置VC++6.0与VC++2008,主要步骤为:
针对VC6.0:
下载opengl开发库文件夹
复制glut32.dll和glut.dll到…\windows\system32
复制glut.h到...\Microsoft Visual Studio\VC98\Include\GL
复制glut32.lib和glut.lib到…\Microsoft Visual Studio\VC98\Lib
新建工程后,进入Project菜单,选Settings项,弹出 Settings 对话框,选Link项,在 Libraries 栏目中加入OpenGL库:opengl32.lib glu32.lib glaux.lib
针对VC2008:
下载并安装opengl2.exe,生成GLSDK,包含include、lib、example等多个子文件夹,然后下载glut文件夹,包含include、lib两个子文件夹;
将GLSDK的include、lib文件夹路径设为环境变量;
打开vc2008IDE,在工具,选项里选择 项目和解决方案/文件目录,分别在include、lib两个下拉菜单中添加(1)中的两个include、两个lib路径进来即可。
2.opengl窗口编程
运行一个Windows环境下的一个基本OpenGL程序,直接打开2008version或60version文件夹内的工程,它将显示一个空的OpenGL窗口,可以在定制窗口大小和全屏模式下切换(按F1),按ESC退出,该程序为以后的应用程序提供了实验平台,并预留了绘图接口,具体代码文档见附件。
3.基本图像绘制
阅读教材P67-70,了解绘制函数,根据附件2提供的源码baseshape.cpp,将该文件内的场景绘制函数Drawsence()替代步骤2中的同名函数,分析程序架构和关键代码,修改图形绘制命令和参数,显示出点、线、矩形、三角形、多边形等,可设置不同线宽,保存代码和屏幕显示结果。
4. 二维变换
阅读附件3提供源码,将该文件内的场景绘制函数Drawsence()替代步骤3中的同名函数,分析程序代码并查看结果,通过调用glTranslate*, glRotate*,glscale*等二维变换函数实现平移、旋转、缩放等变换,要求通过参数操作和矩阵操作两种方式执行,分别给出源代码。
5. 二维观察流水线
掌握二维观察流水线各环节知识,弄懂裁剪窗口、视口、世界坐标系、观察坐标系、设备坐标系的概念。阅读附件viewport.cpp提供的源代码和课本P253-259,将该文件内的场景绘制函数Drawsence()替代步骤3中的同名函数,理解gluOrtho2D、glviewport函数的调用,参考glut窗口操作函数,实现同一窗口中的视口变换和多视口显示,结果如图所示,
6. Sierpinski模型绘制(选做,了解)
在完成前面工作的基础上,引入分形的递归算法,通过绘制小的三角形动作,生成Sierpinski镂垫
实验报告
用自己的话给出上述各步骤的原理理解,代码分析和实验结果,其中步骤3中至少绘制三个图形,步骤4中至少完成两个变换操作,步骤5两个结果都需要实现,杜绝雷同结果。
将报告文档、源码工程存放一个文件夹下,打包压缩,压缩名以“学号+姓名+GI+第1次实验”格式命名,实验报告内容见教辅系统提供样本,提交至教辅系统 。
附件1: opengl窗口编程代码,该附件以较大篇幅详细分析了VC++面向对象编程的窗口知识和windows应用的基本原理,重在理解,为后续绘图程序的添加做好铺垫
代码的前4行包括了每个库文件的头文件。如下所示:
#include <windows.h> // Windows的头文件
#include <gl\gl.h> //包含最新的gl.h
#include <gl\glu.h> //包含最新的glu.h库
#include <gl\glaux.h> //X –windows系统应用库
接下来设置计划在程序中使用的所有变量。本例程将创建一个空的OpenGL窗口,暂时还无需设置大堆的变量。余下需要设置的变量不多,但十分重要,以后所写的每一个OpenGL程序中都要用到它们。
第一行设置的变量是Rendering Context(着色描述表)。每一个OpenGL都被连接到一个着色描述表上。着色描述表将所有的OpenGL调用命令连接到Device Context(设备描述表)上。我将OpenGL的着色描述表定义为 hRC 。要让程序能够绘制窗口的话,还需要创建一个设备描述表,也就是第二行的内容。Windows的设备描述表被定义为 hDC 。DC将窗口连接到GDI(Graphics Device Interface图形设备接口)。而RC将OpenGL连接到DC。第三行的变量 hWnd 将保存由Windows给窗口指派的句柄。最后,第四行为程序创建了一个Instance(实例)。
HGLRC hRC=NULL; //窗口着色描述表句柄
HDC hDC=NULL; // OpenGL渲染描述表句柄
HWND hWnd=NULL; // 保存窗口句柄
HINSTANCE hInstance; // 保存程序的实例
下面的第一行设置一个用来监控键盘动作的数组。有许多方法可以监控键盘的动作,但这里的方法很可靠,并且可以处理多个键同时按下的情况。
active 变量用来告知程序窗口是否处于最小化的状态。如果窗口已经最小化的话,我们可以做从暂停代码执行到退出程序的任何事情。我喜欢暂停程序。这样可以使得程序不用在后台保持运行。
fullscreen 变量的作用相当明显。如果我们的程序在全屏状态下运行, fullscreen 的值为TRUE,否则为FALSE。这个全局变量的设置十分重要,它让每个过程都知道程序是否运行在全屏状态下。
bool keys[256]; // 保存键盘按键的数组
bool active=TRUE; // 窗口的活动标志,缺省为TRUE
bool fullscreen=TRUE; // 全屏标志缺省,缺省设定成全屏模式
现在我们需要先定义WndProc()。必须这么做的原因是CreateGLWindow()有对WndProc()的引用,但WndProc()在CreateGLWindow()之后才出现。在C语言中,如果想要访问一个当前程序段之后的过程和程序段的话,必须在程序开始处先申明所要访问的程序段。所以下面的一行代码先行定义了WndProc(),使得CreateGLWindow()能够引用WndProc()。
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); // WndProc的声明
下面的代码的作用是重新设置OpenGL场景的大小,而不管窗口的大小是否已经改变(假定用户没有使用全屏模式)。甚至无法改变窗口的大小时(例如用户在全屏模式下),它至少仍将运行一次--在程序开始时设置用户的透视图。OpenGL场景的尺寸将被设置成它显示时所在窗口的大小。
GLvoid ReSizeGLScene(GLsizei width, GLsizei height) // 重置OpenGL窗口大小
{
if (height==0) // 防止被零除
{
height=1; // 将Height设为1
}
glViewport(0, 0, width, height); // 重置当前的视口
下面几行为透视图设置屏幕。意味着越远的东西看起来越小。这么做创建了一个现实外观的场景。此处透视按照基于窗口宽度和高度的45度视角来计算。0.1f,100.0f是我们在场景中所能绘制深度的起点和终点。
glMatrixMode(GL_PROJECTION)指明接下来的两行代码将影响projection matrix(投影矩阵)。投影矩阵负责为我们的场景增加透视。 glLoadIdentity()近似于重置。它将所选的矩阵状态恢复成其原始状态。调用 glLoadIdentity()之后我们为场景设置透视图。
glMatrixMode(GL_MODELVIEW)指明任何新的变换将会影响 modelview matrix(模型观察矩阵)。模型观察矩阵中存放了我们的物体讯息。最后我们重置模型观察矩阵。如果您还不能理解这些术语的含义,请别着急。在以后的教程里,我会向大家解释。只要知道如果您想获得一个精彩的透视场景的话,必须这么做。
glMatrixMode(GL_PROJECTION); // 选择投影矩阵
glLoadIdentity(); // 重置投影矩阵
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f); //设置视口的大小
glMatrixMode(GL_MODELVIEW); // 选择模型观察矩阵
glLoadIdentity(); // 重置模型观察矩阵
}
接下的代码段中,我们将对OpenGL进行所有的设置。将设置清除屏幕所用的颜色,打开深度缓存,启用smooth shading(阴影平滑)等等,这直到OpenGL窗口创建之后才会被调用。此过程将有返回值,但此处还用不着担心这个返回值。
int InitGL(GLvoid) // 此处开始对OpenGL进行所有设置
{
下一行启用smooth shading(阴影平滑)。阴影平滑通过多边形精细的混合色彩,并对外部光进行平滑。我将在另一个教程中更详细的解释阴影平滑。
glShadeModel(GL_SMOOTH); // 启用阴影平滑
下一行设置清除屏幕时所用的颜色。可参考色彩的工作原理,色彩值的范围从0.0f到1.0f。0.0f代表最黑的情况,1.0f就是最亮的情况。glClearColor 后的第一个参数是Red Intensity(红色分量),第二个是绿色,第三个是蓝色。最大值也是1.0f,代表特定颜色分量的最亮情况。最后一个参数是Alpha值。当它用来清除屏幕的时候,我们不用关心第四个数字。现在让它为0.0f。我会用另一个教程来解释这个参数。
通过混合三种原色(红、绿、蓝),可以得到不同的色彩。因此可知,当使用glClearColor (0.0f,0.0f,1.0f,0.0f),将用亮蓝色来清除屏幕,如果用 glClearColor(0.5f,0.0f,0.0f,0.0f)的话,将使用中红色来清除屏幕。不是最亮(1.0f),也不是最暗 (0.0f)。要得到白色背景,应该将所有的颜色设成最亮(1.0f)。要黑色背景的话,将所有的颜色设为最暗(0.0f)。
glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景
接下来的三行必须做的是关于depth buffer(深度缓存)的。将深度缓存设想为屏幕后面的层。深度缓存不断的对物体进入屏幕内部有多深进行跟踪。我们本节的程序其实没有真正使用深度缓存,但几乎所有在屏幕上显示3D场景OpenGL程序都使用深度缓存。它的排序决定那个物体先画。这样您就不会将一个圆形后面的正方形画到圆形上来。深度缓存是OpenGL十分重要的部分。
glClearDepth(1.0f); // 设置深度缓存
glEnable(GL_DEPTH_TEST); // 启用深度测试
glDepthFunc(GL_LEQUAL); // 所作深度测试的类型
接着告诉OpenGL用户希望进行最好的透视修正。这会十分轻微的影响性能,但使得透视图看起来好一点。
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 告诉系统对透视进行修正
最后,返回TRUE。如果用户希望检查初始化是否OK,可以查看返回的 TRUE或FALSE的值。如果有错误发生的话,用户可以加上自己的处理代码并返回FALSE。
return TRUE; // 初始化 OK
}
下一段包括了所有的绘图代码。任何所需要在屏幕上显示的东东都将在此段代码中出现。以后的每次实验的工作都是在程序框架的此处增加新的代码。如果对OpenGL已经有所了解的话,用户可以在 glLoadIdentity()调用之后,返回TRUE值之前,试着添加一些OpenGL代码来创建基本的形。这里所作的全部就是将屏幕清除成前面所决定的颜色,清除深度缓存并且重置场景。
int DrawGLScene(GLvoid) // 从这里开始进行所有的绘制
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
glLoadIdentity(); // 重置当前的模型观察矩阵
return TRUE; // 一切 OK
}
下一段代码只在程序退出之前调用。KillGLWindow() 的作用是依次释放着色描述表,设备描述表和窗口句柄。这里已经加入了许多错误检查。如果程序无法销毁窗口的任意部分,都会弹出带相应错误消息的讯息窗口,告诉用户什么出错了。使用户在代码中查错变得更容易些。
GLvoid KillGLWindow(GLvoid) // 正常销毁窗口
{
KillGLWindow()中所作的第一件事是检查窗口显示是否处于全屏模式。如果是,要切换回桌面。本应在禁用全屏模式前先销毁窗口,但在某些显卡上这么做可能会使得桌面崩溃。所以我们还是先禁用全屏模式。这将防止桌面出现崩溃,并在Nvidia和3dfx显卡上都工作的很好!
if (fullscreen) //处于全屏模式吗?
{
使用ChangeDisplaySettings(NULL,0)回到原始桌面。将NULL作为第一个参数,0作为第二个参数传递强制Windows使用当前存放在注册表中的值(缺省的分辨率、色彩深度、刷新频率,等等)来有效的恢复原始桌面。切换回桌面后,还要使得鼠标指针重新可见。
ChangeDisplaySettings(NULL,0); // 是的话,切换回桌面
ShowCursor(TRUE); // 显示鼠标指针
}
接下来的代码查看是否拥有着色描述表(hRC)。如果没有,程序将跳转至后面的代码查看是否拥有设备描述表。
if (hRC) // 已经拥有OpenGL渲染描述表吗?
{
如果存在着色描述表的话,下面的代码将查看我们能否释放它(将 hRC从hDC分开)。这里请注意使用的查错方法。基本上只是让程序尝试释放着色描述表(通过调用wglMakeCurrent(NULL,NULL),然后再查看释放是否成功。巧妙的将数行代码结合到了一行。
if (!wglMakeCurrent(NULL,NULL)) //能否释放DC和RC描述表?
{
如果不能释放DC和RC描述表的话,MessageBox()将弹出错误消息,告知用户DC和RC无法被释放。NULL意味着消息窗口没有父窗口。其右的文字将在消息窗口上出现。"SHUTDOWN ERROR"出现在窗口的标题栏上。MB_OK的意思消息窗口上带有一个写着OK字样的按钮。
MB_ICONINFORMATION将在消息窗口中显示一个带圈的小写的i(看上去更正式一些)。
MessageBox(NULL,"释放DC或RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
}
下一步试着删除着色描述表。如果不成功的话弹出错误消息。
if (!wglDeleteContext(hRC)) //能否删除RC?
{
如果无法删除着色描述表的话,将弹出错误消息告知用户RC未能成功删除。然后hRC被设为NULL。
MessageBox(NULL,"释放RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
}
hRC=NULL; // 将RC设为 NULL
}
现在查看是否存在设备描述表,如果有尝试释放它。如果不能释放设备描述表将弹出错误消息,然后hDC设为NULL。
if (hDC && !ReleaseDC(hWnd,hDC)) //能否释放 DC?
{
MessageBox(NULL,"释放DC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
hDC=NULL; // 将 DC 设为 NULL
}
现在来查看是否存在窗口句柄,调用 DestroyWindow( hWnd )来尝试销毁窗口。如果不能的话弹出错误窗口,然后hWnd被设为NULL。
if (hWnd && !DestroyWindow(hWnd)) // 能否销毁窗口?
{
MessageBox(NULL,"释放窗口句柄失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
hWnd=NULL; // 将 hWnd 设为 NULL
}
最后要做的事是注销窗口类。这允许正常销毁窗口,接着在打开其他窗口时,不会收到诸如"Windows Class already registered"(窗口类已注册)的错误消息。
if (!UnregisterClass("OpenG",hInstance)) // 能否注销类?
{
MessageBox(NULL,"不能注销窗口类。","关闭错误",MB_OK | MB_ICONINFORMATION);
hInstance=NULL; // 将 hInstance 设为 NULL
}
}
接下来的代码段创建OpenGL窗口。对编程初学者而言,经常有诸如此类的问题:怎样创建窗口而不使用全屏幕?怎样改变窗口的标题栏?怎样改变窗口的分辨率或pixel format(象素格式)?以下的代码完成了所有这一切!此过程返回布尔变量(TRUE 或 FALSE)。还带有5个参数:窗口的标题栏,窗口的宽度,窗口的高度,色彩位数(16/24/32),和全屏标志(TRUE --全屏模式, FALSE--窗口模式 )。返回的布尔值告诉用户窗口是否成功创建。
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
{
当要求Windows为寻找相匹配的象素格式时,Windows寻找结束后将模式值保存在变量PixelFormat中。
GLuint PixelFormat; // 保存查找匹配的结果
wc用来保存窗口类的结构。窗口类结构中保存着窗口信息。通过改变类的不同字段可以改变窗口的外观和行为。每个窗口都属于一个窗口类。当用户创建窗口时,必须为窗口注册类。
WNDCLASS wc; // 窗口类结构
dwExStyle和dwStyle存放扩展和通常的窗口风格信息。使用变量来存放风格的目的是为了能够根据需要创建的窗口类型(是全屏幕下的弹出窗口还是窗口模式下的带边框的普通窗口)来改变窗口的风格。
DWORD dwExStyle; // 扩展窗口风格
DWORD dwStyle; // 窗口风格
下面的5行代码取得矩形的左上角和右下角的坐标值。后面将使用这些值来调整我们的窗口使得其上的绘图区的大小恰好是用户所需的分辨率的值。通常,如果直接创建一个640x480的窗口,窗口的边框会占掉一些分辨率的值。
RECT WindowRect; // 取得矩形的左上角和右下角的坐标值
WindowRect.left=(long)0; // 将Left设为 0
WindowRect.right=(long)width; // 将Right设为要求的宽度
WindowRect.top=(long)0; // 将Top设为 0
WindowRect.bottom=(long)height; // 将Bottom 设为要求的高度
下一行代码我们让全局变量fullscreen等于fullscreenflag。如果希望在全屏幕下运行而将fullscreenflag设为TRUE,但没有让变量fullscreen等于fullscreenflag的话,fullscreen变量将保持为FALSE。当我们在全屏幕模式下销毁窗口的时候,变量fullscreen的值却不是正确的TRUE值,计算机将误以为已经处于桌面模式而无法切换回桌面。就是一句话,fullscreen的值必须永远等于fullscreenflag的值,否则就会有问题。
fullscreen=fullscreenflag; // 设置全局全屏标志
下一部分的代码中,先取得窗口的实例,然后定义窗口类。
CS_HREDRAW 和 CS_VREDRAW 的意思是无论何时,只要窗口发生变化时就强制重画。CS_OWNDC为窗口创建一个私有的DC。这意味着DC不能在程序间共享。WndProc是程序的消息处理过程。由于没有使用额外的窗口数据,后两个字段设为零。然后设置实例。接着将hIcon设为NULL,因为不想给窗口来个图标。鼠标指针设为标准的箭头。背景色无所谓(在GL中设置)。这里不用窗口菜单,所以将其设为NULL。类的名字可以设为想要的任何名字。出于简单,将使用"OpenG"。
hInstance = GetModuleHandle(NULL); // 取得我们窗口的实例
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 移动时重画,并为窗口取得DC
wc.lpfnWndProc = (WNDPROC) WndProc; // WndProc处理消息
wc.cbClsExtra = 0; // 无额外窗口数据
wc.cbWndExtra = 0; // 无额外窗口数据
wc.hInstance = hInstance; // 设置实例
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 装入缺省图标
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 装入鼠标指针
wc.hbrBackground = NULL; // GL不需要背景
wc.lpszMenuName = NULL; // 不需要菜单
wc.lpszClassName = "OpenG"; // 设定类名字
现在注册类名字。如果有错误发生,弹出错误消息窗口。按下上面的OK按钮后,程序退出
if (!RegisterClass(&wc)) // 尝试注册窗口类
{
MessageBox(NULL,"注册窗口失败","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 退出并返回FALSE
}
查看程序应该在全屏模式还是窗口模式下运行。如果应该是全屏模式的话,将尝试设置全屏模式。
if (fullscreen) // 要尝试全屏模式吗?
{
下一部分的代码看来很多人都会有问题要问关于——切换到全屏模式。在切换到全屏模式时,有几件十分重要的事必须牢记。必须确保全屏模式下所用的宽度和高度等同于窗口模式下的宽度和高度。最最重要的是要在创建窗口之前设置全屏模式。这里的代码中,无需再担心宽度和高度,它们已被设置成与显示模式所对应的大小。
DEVMODE dmScreenSettings; // 设备模式
memset(&dmScreenSettings,0,sizeof(dmScreenSettings)); //确保内存清空为零
dmScreenSettings.dmSize=sizeof(dmScreenSettings);// Devmode 结构的大小
dmScreenSettings.dmPelsWidth = width; // 所选屏幕宽度
dmScreenSettings.dmPelsHeight = height; // 所选屏幕高度
dmScreenSettings.dmBitsPerPel = bits; // 每象素所选的色彩深度
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
上面的代码中,分配了用于存储视频设置的空间。设定了屏幕的宽,高,色彩深度。下面的代码尝试设置全屏模式。在dmScreenSettings中保存了所有的宽,高,色彩深度讯息。下一行使用ChangeDisplaySettings来尝试切换成与dmScreenSettings所匹配模式。使用参数CDS_FULLSCREEN来切换显示模式,因为这样做不仅移去了屏幕底部的状态条,而且它在来回切换时,没有移动或改变已经在桌面上的窗口。
// 尝试设置显示模式并返回结果。注: CDS_FULLSCREEN 移去了状态条。
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
{
如果模式未能设置成功,我们将进入以下的代码。如果不能匹配全屏模式,弹出消息窗口,提供两个选项:在窗口模式下运行或退出。
// 若模式失败,提供两个选项:退出或在窗口内运行。
if (MessageBox(NULL,"全屏模式在当前显卡上设置失败!\n使用窗口模式?","NeHe G",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
{
如果用户选择窗口模式,变量fullscreen 的值变为FALSE,程序继续运行。
fullscreen=FALSE; // 选择窗口模式(Fullscreen=FALSE)
}
else
{
如果用户选择退出,弹出消息窗口告知用户程序将结束。并返回FALSE告诉程序窗口未能成功创建。程序退出。
// 弹出一个对话框,告诉用户程序结束
MessageBox(NULL,"程序将被关闭","错误",MB_OK|MB_ICONSTOP);
return FALSE; // 退出并返回 FALSE
}
}
}
由于全屏模式可能失败,用户可能决定在窗口下运行,需要在设置屏幕/窗口之前,再次检查fullscreen的值是TRUE或FALSE
if (fullscreen) // 仍处于全屏模式吗?
{
如果仍处于全屏模式,设置扩展窗体风格为WS_EX_APPWINDOW,这将强制窗体可见时处于最前面。再将窗体的风格设为WS_POPUP。这个类型的窗体没有边框,使我们的全屏模式得以完美显示。最后禁用鼠标指针。当程序不是交互式的时候,在全屏模式下禁用鼠标指针通常是个好主意。
dwExStyle=WS_EX_APPWINDOW; // 扩展窗体风格
dwStyle=WS_POPUP; // 窗体风格
ShowCursor(FALSE); // 隐藏鼠标指针
}
else
{
如果使用窗口而不是全屏模式,在扩展窗体风格中增加了 WS_EX_WINDOWEDGE,增强窗体的3D感观。窗体风格改用 WS_OVERLAPPEDWINDOW,创建一个带标题栏、可变大小的边框、菜单和最大化/最小化按钮的窗体。
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // 扩展窗体风格
dwStyle=WS_OVERLAPPEDWINDOW; // 窗体风格
}
下一行代码根据创建的窗体类型调整窗口。调整的目的是使得窗口大小正好等于我们要求的分辨率。通常边框会占用窗口的一部分。使用AdjustWindowRectEx 后,我们的OpenGL场景就不会被边框盖住。实际上窗口变得更大以便绘制边框。全屏模式下,此命令无效。
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
// 调整窗口达到真正要求的大小
下一段代码开始创建窗口并检查窗口是否成功创建,将传递CreateWindowEx()所需的所有参数。如扩展风格、类名字(注册窗口类时所用的名字相同)、窗口标题、窗体风格、窗体的左上角坐标(0,0 是个安全的选择)、窗体的宽和高。我们没有父窗口,也不想要菜单,这些参数被设为NULL。还传递了窗口的实例,最后一个参数被设为NULL。
注意在窗体风格中包括了 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN。要让OpenGL正常运行,这两个属性是必须的。他们阻止别的窗体在我们的窗体内/上绘图。
if (!(hWnd=CreateWindowEx( dwExStyle, // 扩展窗体风格
"OpenG", // 类名字
title, // 窗口标题
WS_CLIPSIBLINGS | // 必须的窗体风格属性
WS_CLIPCHILDREN | // 必须的窗体风格属性
dwStyle, // 选择的窗体属性
0, 0, // 窗口位置
WindowRect.right-WindowRect.left, // 计算调整好的窗口宽度
WindowRect.bottom-WindowRect.top, // 计算调整好的窗口高度
NULL, // 无父窗口
NULL, // 无菜单
hInstance, // 实例
NULL))) // 不向WM_CREATE传递任何东东
下来我们检查看窗口是否正常创建。如果成功, hWnd保存窗口的句柄。如果失败,弹出消息窗口,并退出程序。
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能创建一个窗口设备描述表","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
下面的代码描述象素格式。选择了通过RGBA(红、绿、蓝、alpha通道)支持OpenGL和双缓存的格式。试图找到匹配选定的色彩深度(16位、24位、32位)的象素格式。最后设置16位Z-缓存。其余的参数要么未使用要么不重要(stencil buffer模板缓存和accumulation buffer聚集缓存除外)。
static PIXELFORMATDESCRIPTOR pfd=
// /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式
{
sizeof(PIXELFORMATDESCRIPTOR), // 上述格式描述符的大小
1, // 版本号
PFD_DRAW_TO_WINDOW | // 格式支持窗口
PFD_SUPPORT_OPENGL | // 格式必须支持OpenGL
PFD_DOUBLEBUFFER, // 必须支持双缓冲
PFD_TYPE_RGBA, // 申请 RGBA 格式
bits, // 选定色彩深度
0, 0, 0, 0, 0, 0, // 忽略的色彩位
0, // 无Alpha缓存
0, // 忽略Shift Bit
0, // 无累加缓存
0, 0, 0, 0, // 忽略聚集位
16, // 16位 Z-缓存 (深度缓存)
0, // 无蒙板缓存
0, // 无辅助缓存
PFD_MAIN_PLANE, // 主绘图层
0, // Reserved
0, 0, 0 // 忽略层遮罩
};
如果前面创建窗口时没有错误发生,接着尝试取得OpenGL设备描述表。若无法取得DC,弹出错误消息程序退出(返回FALSE)。
if (!(hDC=GetDC(hWnd))) // 取得设备描述表了么?
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能创建一种相匹配的像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
设法为OpenGL窗口取得设备描述表后,尝试找到对应与此前我们选定的象素格式的象素格式。如果Windows不能找到的话,弹出错误消息,并退出程序(返回FALSE)。
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd))) // Windows 找到相应的象素格式了吗?
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
Windows 找到相应的象素格式后,尝试设置象素格式。如果无法设置,弹出错误消息,并退出程序(返回FALSE)。
if(!SetPixelFormat(hDC,PixelFormat,&pfd)) // 能够设置象素格式么?
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
正常设置象素格式后,尝试取得着色描述表。如果不能取得着色描述表的话,弹出错误消息,并退出程序(返回FALSE)。
if (!(hRC=wglCreateContext(hDC))) // 能否取得着色描述表?
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能创建OpenGL渲染描述表","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
如果到现在仍未出现错误的话,我们已经设法取得了设备描述表和着色描述表。接着要做的是激活着色描述表。如果无法激活,弹出错误消息,并退出程序(返回FALSE)。
if(!wglMakeCurrent(hDC,hRC)) // 尝试激活着色描述表
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"不能激活当前的OpenGL渲然描述表","错误",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
一切顺利的话,OpenGL窗口已经创建完成,接着可以显示它啦。将它设为前端窗口(给它更高的优先级),并将焦点移至此窗口。然后调用ReSizeGLScene 将屏幕的宽度和高度设置给透视OpenGL屏幕。
ShowWindow(hWnd,SW_SHOW); // 显示窗口
SetForegroundWindow(hWnd); // 略略提高优先级
SetFocus(hWnd); // 设置键盘的焦点至此窗口
ReSizeGLScene(width, height); // 设置透视 GL 屏幕
跳转至 InitGL(),这里可以设置光照、纹理、等等任何需要设置的东东。可以在 InitGL()内部自行定义错误检查,并返回 TRUE (一切正常)或FALSE (有什么不对)。例如,如果在InitGL()内装载纹理并出现错误,可能希望程序停止。如果返回 FALSE的话,下面的代码会弹出错误消息,并退出程序。
if (!InitGL()) // 初始化新建的GL窗口
{
KillGLWindow(); // 重置显示区
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // 返回 FALSE
}
到这里可以安全的推定创建窗口已经成功了。向WinMain()返回TRUE,告知WinMain()没有错误,以防止程序退出。
return TRUE; // 成功
}
下面的代码处理所有的窗口消息。当注册好窗口类之后,程序跳转到这部分代码处理窗口消息。
LRESULT CALLBACK WndProc( HWND hWnd, // 窗口的句柄
UINT uMsg, // 窗口的消息
WPARAM wParam, // 附加的消息内容
LPARAM lParam) // 附加的消息内容
{
下面的代码比对uMsg的值,然后转入case处理,uMsg 中保存了我们要处理的消息名字。
switch (uMsg) // 检查Windows消息
{
如果uMsg等于WM_ACTIVE,查看窗口是否仍然处于激活状态。如果窗口已被最小化,将变量active设为FALSE。如果窗口已被激活,变量active的值为TRUE。
case WM_ACTIVATE: // 监视窗口激活消息
{
if (!HIWORD(wParam)) // 检查最小化状态
{
active=TRUE; // 程序处于激活状态
}
else
{
active=FALSE; // 程序不再激活
}
return 0; // 返回消息循环
}
如果消息是WM_SYSCOMMAND(系统命令),再次比对wParam。如果wParam 是 SC_SCREENSAVE 或 SC_MONITORPOWER的话,不是有屏幕保护要运行,就是显示器想进入节电模式。返回0可以阻止这两件事发生。
case WM_SYSCOMMAND: // 系统中断命令
{
switch (wParam) // 检查系统调用
{
case SC_SCREENSAVE: // 屏保要运行?
case SC_MONITORPOWER: // 显示器要进入节电模式?
return 0; // 阻止发生
}
break; // 退出
}
如果 uMsg是WM_CLOSE,窗口将被关闭。发出退出消息,主循环将被中断。变量done被设为TRUE,WinMain()的主循环中止,程序关闭。
case WM_CLOSE: // 收到Close消息?
{
PostQuitMessage(0); // 发出退出消息
return 0; // 返回
}
如果键盘有键按下,通过读取wParam的信息可以找出键值。将键盘数组keys[ ]相应的数组组成员的值设为TRUE。这样以后就可以查找key[ ]来得知什么键被按下。允许同时按下多个键。
case WM_KEYDOWN: // 有键按下么?
{
keys[wParam] = TRUE; // 如果是,设为TRUE
return 0; // 返回
}
同样,如果键盘有键释放,通过读取wParam的信息可以找出键值。然后将键盘数组keys[ ]相应的数组组成员的值设为FALSE。这样查找key[ ]来得知什么键被按下,什么键被释放了。键盘上的每个键都可以用0-255之间的一个数来代表。举例来说,当按下40所代表的键时,keys[40]的值将被设为TRUE。放开的话,它就被设为FALSE。这也是key数组的原理。
case WM_KEYUP: // 有键放开么?
{
keys[wParam] = FALSE; // 如果是,设为FALSE
return 0; // 返回
}
当调整窗口时,uMsg 最后等于消息WM_SIZE。读取lParam的LOWORD 和HIWORD可以得到窗口新的宽度和高度。将它们传递给ReSizeGLScene(),OpenGL场景将调整为新的宽度和高度。
case WM_SIZE: // 调整OpenGL窗口大小
{
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
// LoWord=Width,HiWord=Height
return 0; // 返回
}
}
其余无关的消息被传递给DefWindowProc,让Windows自行处理。
// 向 DefWindowProc传递所有未处理的消息。
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
下面是Windows程序的入口。将会调用窗口创建例程,处理窗口消息,并监视人机交互。
int WINAPI WinMain( HINSTANCE hInstance, // 当前窗口实例
HINSTANCE hPrevInstance, // 前一个窗口实例
LPSTR lpCmdLine, // 命令行参数
int nCmdShow) // 窗口显示状态
{
设置两个变量。msg 用来检查是否有消息等待处理。done的初始值设为FALSE。这意味着程序仍未完成运行。只要程序done保持FALSE,程序继续运行。一旦done的值改变为TRUE,程序退出。
MSG msg; // Windowsx消息结构
BOOL done=FALSE; // 用来退出循环的Bool 变量
这段代码完全可选。程序弹出一个消息窗口,询问用户是否希望在全屏模式下运行。如果用户单击NO按钮,fullscreen变量从缺省的TRUE改变为FALSE,程序也改在窗口模式下运行。
// 提示用户选择运行模式
if (MessageBox(NULL,"你想在全屏模式下运行么?", "设置全屏模式",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE; // FALSE为窗口模式
}
接着创建OpenGL窗口。CreateGLWindow函数的参数依次为标题、宽度、高度、色彩深度,以及全屏标志。就这么简单!这段代码很简洁。如果未能创建成功,函数返回FALSE。程序立即退出。
// 创建OpenGL窗口
if (!CreateGLWindow("OpenGL程序框架",640,480,16,fullscreen))
{
return 0; // 失败退出
}
下面是循环的开始。只要done保持FALSE,循环一直进行。
while(!done) // 保持循环直到 done=TRUE
{
现在要做的第一件事是检查是否有消息在等待。使用PeekMessage()可以在不锁住我们的程序的前提下对消息进行检查。许多程序使用GetMessage(),也可以很好的工作。但使用GetMessage(),程序在收到paint消息或其他别的什么窗口消息之前不会做任何事。
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE)) // 有消息在等待吗?
{
下面的代码查看是否出现退出消息。如果当前的消息是由PostQuitMessage(0)引起的WM_QUIT,done变量被设为TRUE,程序将退出。
if (msg.message==WM_QUIT) // 收到退出消息?
{
done=TRUE; // 是,则done=TRUE
}
else // 不是,处理窗口消息
{
如果不是退出消息,我们翻译消息,然后发送消息,使得WndProc() 或 Windows能够处理他们。
TranslateMessage(&msg); // 翻译消息
DispatchMessage(&msg); // 发送消息
}
}
else // 如果没有消息
{
如果没有消息,绘制我们的OpenGL场景。代码的第一行查看窗口是否激活。如果按下ESC键,done变量被设为TRUE,程序将会退出。
// 绘制场景。监视ESC键和来自DrawGLScene()的退出消息
if (active) // 程序激活的么?
{
if (keys[VK_ESCAPE]) // ESC 按下了么?
{
done=TRUE; // ESC 发出退出信号
}
else // 不是退出的时候,刷新屏幕
{
如果程序是激活的且ESC没有按下,我们绘制场景并交换缓存(使用双缓存可以实现无闪烁的动画)。我们实际上在另一个看不见的"屏幕"上绘图。当我们交换缓存后,我们当前的屏幕被隐藏,现在看到的是刚才看不到的屏幕。这也是我们看不到场景绘制过程的原因。场景只是即时显示。
DrawGLScene(); // 绘制场景
SwapBuffers(hDC); // 交换缓存 (双缓存)
}
}
下面的一点代码允许用户按下F1键在全屏模式和窗口模式间切换。
if (keys[VK_F1]) // F1键按下了么?
{
keys[VK_F1]=FALSE; //若是使对应的Key数组中的值为 FALSE
KillGLWindow(); // 销毁当前的窗口
fullscreen=!fullscreen; // 切换 全屏 / 窗口 模式
// 重建 OpenGL 窗口
if (!CreateGLWindow("NeHe's OpenGL 程序框架",640,480,16,fullscreen))
{
return 0; // 如果窗口未能创建,程序退出
}
}
}
}
如果done变量不再是FALSE,程序退出。正常销毁OpenGL窗口,将所有的内存释放,退出程序。
// 关闭程序
KillGLWindow(); // 销毁窗口
return (msg.wParam); // 退出程序
}
附件2:baseshape.cpp
int DrawGLScene(GLvoid) //
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存
glLoadIdentity(); // 重置当前的模型观察矩阵
glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 单位,并移入屏幕 6.0
glBegin(GL_TRIANGLES); // 绘制三角形
glVertex3f( 0.0f, 1.0f, 0.0f); // 上顶点
glVertex3f(-1.0f,-1.0f, 0.0f); // 左下
glVertex3f( 1.0f,-1.0f, 0.0f); // 右下
glEnd(); // 三角形绘制结束
glTranslatef(3.0f,0.0f,0.0f); // 右移3单位
glBegin(GL_QUADS); // 绘制正方形
glVertex3f(-1.0f, 1.0f, 0.0f); // 左上
glVertex3f( 1.0f, 1.0f, 0.0f); // 右上
glVertex3f( 1.0f,-1.0f, 0.0f); // 左下
glVertex3f(-1.0f,-1.0f, 0.0f); // 右下
glEnd(); // 正方形绘制结束
return TRUE; // 继续运行
}
附件3:rotate.cpp
GLfloat rtri; // 用于三角形旋转的角度,需设为全局变量
GLfloat rquad; // 用于四边形旋转的角度,需设为全局变量
int DrawGLScene(GLvoid) // Here's Where We Do All The Drawing
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕及深度缓存
glLoadIdentity(); // 重置模型观察矩阵
glTranslatef(-1.5f,0.0f,-6.0f); // 左移 1.5 单位,并移入屏幕 6.0
glRotatef(rtri,0.0f,1.0f,0.0f); // 绕Y轴旋转三角形
glBegin(GL_TRIANGLES); // 绘制三角形
//glColor3f(1.0f,0.0f,0.0f); // 设置当前色为红色
glVertex3f( 0.0f, 1.0f, 0.0f); // 上顶点
//glColor3f(0.0f,1.0f,0.0f); // 设置当前色为绿色
glVertex3f(-1.0f,-1.0f, 0.0f); // 左下
//glColor3f(0.0f,0.0f,1.0f); // 设置当前色为蓝色
glVertex3f( 1.0f,-1.0f, 0.0f); // 右下
glEnd(); // 三角形绘制结束
glLoadIdentity(); // 重置模型观察矩阵
glTranslatef(1.5f,0.0f,-6.0f); // 右移1.5单位,并移入屏幕 6.0
glRotatef(rquad,1.0f,0.0f,0.0f); // 绕X轴旋转四边形
//glColor3f(0.5f,0.5f,1.0f); // 一次性将当前色设置为蓝色
glBegin(GL_QUADS); // 绘制正方形
glVertex3f(-1.0f, 1.0f, 0.0f); // 左上
glVertex3f( 1.0f, 1.0f, 0.0f); // 右上
glVertex3f( 1.0f,-1.0f, 0.0f); // 左下
glVertex3f(-1.0f,-1.0f, 0.0f); // 右下
glEnd(); // 正方形绘制结束
rtri+=0.2f; // 增加三角形的旋转变量
rquad-=0.15f; // 减少四边形的旋转变量
return TRUE; // 继续运行
}
贵州大学实验报告学院计算计科学与信息学院专业班级贵州大学实验报告学院计算计科学与信息学院专业数字媒体技术班级数媒091贵州大学实验…
计算机图形学基础实验6OpenGL中的变换一实验目的及要求1理解OpenGL中的各种变换的实现原理2掌握OpenGL中模型视图矩阵…
计算机图形学实验指导书计算机科学与信息工程学院目录实验一OpenGL程序设计3实验二二维基本图元的生成7实验三二维图元的填充13实…
Opengl项目一一题目分析利用OpenGL绘制一个简单的场景包含球正方形网格数据交互操作平移缩放旋转可以使用不同的灯光项目1作业…
计算机图形学基础实验4OpenGL中基本图形的绘制一实验目的及要求1掌握OpenGL中点的绘制方法2掌握OpenGL中直线的绘制方…
贵州大学实验报告学院计算计科学与信息学院专业班级贵州大学实验报告学院计算计科学与信息学院专业数字媒体技术班级数媒091贵州大学实验…
计算机图形学基础实验6OpenGL中的变换一实验目的及要求1理解OpenGL中的各种变换的实现原理2掌握OpenGL中模型视图矩阵…
计算机图形学实验指导书计算机科学与信息工程学院目录实验一OpenGL程序设计3实验二二维基本图元的生成7实验三二维图元的填充13实…
Opengl项目一一题目分析利用OpenGL绘制一个简单的场景包含球正方形网格数据交互操作平移缩放旋转可以使用不同的灯光项目1作业…