在哪里启动openglwin7的硬件加速在哪里OpenGL全写Op

欢迎阅读我的OpenGL教程.我是一个热爱OpenGL的普通码农!我第一次听到OpenGL是在3Dfx刚发布他们给Voodoo I显卡的OpenGL硬件加速驱动的时候.我马上意识到我必须学习OpenGL.不幸的是,当时在网上很难找到关于OpenGL的书和资料.我花了数小时来编写可运行的代码,并且花了更多时间去发邮件和在IRC上求教别人.但是我发现懂OpenGL的人会当自己是神,并且完全没兴趣分享他们的技术.他们真的很烦!
我创建此站是为了给有兴趣学OpenGL的人提供帮助.每个章节我都会尽我所能的去解释尽量多的细节,例如每行代码都写有注释.我尽量保持代码简明(不涉及到MFC)!就算是VC++和OpenGL的新手,也可以通俗理解示例代码.本站只是众多OpenGL教程站中的一个,如果你是骨灰级OpenGL程序员,本站对你来说太过简单,但如果你是初学者,我觉得本站对你很有帮助.
本教程在2000的1月的时候重写了一次.本教程会教你如何创建一个OpenGL窗体.该窗体可以是带边框的窗体或者全屏,或者任何你想要的大小,分辨率和色深.代码的可扩展性很高,也可以用在你自己的OpenGL项目中.整个教程都会基于这一节的代码!所以我把它写成可扩展和实用性强的.所有错误都会被报告.代码应该是没内存泄漏,也比较容易读懂和修改.感谢Fredic Echols提交的修改代码!
我会从代码开始讲解.你要做的第一件事是在VC++下创建项目.如果你不懂怎么创建,你应该先学习VC++.提供下载的代码是VC++ 6.0代码.而有些VC++版本会需要把bool转换为大写,true和false也转换为大写.为了解决上述更改,我已经把代码修改成可以在VC++ 4.0和5.0下编译.
等你在VC++创建一个新的Win32应用(非控制台应用)之后,你会要链接到OpenGL库.在VC++中是到项目-&设置,然后右键点击LINK 选项卡.在"Object/Library Modules"的第一行(在kernel32.lib前面)添加OpenGL32.lib GLu32.lib和GLaux.lib.然后按确定,然后你就能开始写OpenGL窗体程序了.
注意1: 很多编译器没有定义CDS_FULLSCREEN. 如果你收到一条错误提示是关于CDS_FULLSCREEN的话,你就要添加以下代码到你程序的头部: #define CDS_FULLSCREEN 4.
注意2: 写本教程的第一版时,GLAUX是可行的.之后GLAUX就停止更新了.本站的很多教程仍然使用旧的GLAUX代码.如果你的编译器不支持GLAUX,你不能用的话,可以用主页(左边菜单)提供的GLAUX替换代码.
头4行代码包含了我们用到的各个库的头文件.
#include &windows.h&
// Header File For Windows
#include &gl\gl.h&
// Header File For The OpenGL32 Library
#include &gl\glu.h&
// Header File For The GLu32 Library
#include &gl\glaux.h&
// Header File For The GLaux Library
接下来你要设置在程序中用到的所有变量.该程序会创建空OpenGL窗体,所以我们暂时不需要定义太多变量.我们定义尽量少的变量是非常重要的,因为往后的示例都以本节的代码为基础扩展.
第一行定义了一个渲染上下文.所有的OpenGL程序都被链接到渲染上下文.渲染上下文的作用是把OpenGL调用链接到设备上下文.这里的OpenGL渲染上下文定义名叫hRC.要把程序绘制到窗体的话就需要设备上下文,第二行代码就是干这事.该Windows设备上下文命名为hDC.DC把窗体连接到GDI(图形设备接口).而RC连接OpenGL到DC.
在第三行,变量hWnd会保存Windows分配给我们窗体的句柄,最后,第四行代码为我们的程序创建一个实例(表现).
// Permanent Rendering Context
// Private GDI Device Context
hWnd=NULL;
// Holds Our Window Handle
// Holds The Instance Of The Application
下面第一行代码是创建一个用于监控按下的键的数组.有很多途径可以观察按键事件,但是下面这种是我惯用的.这种方法比较可靠,而且可以同时控制多个键按下的事件.
active变量是用来储存窗体是否最小化到任务栏的状态.如果窗体被最小化的话我们可以暂停退出程序来做任何事.我喜欢暂停程序,这样的话最小化时后台不会持续运作.
fullscreen变量非常明显了.如果我们程序运行在全屏模式下,fullscreen的值会是TRUE,如果运行在窗体模式下,fullscreen的值是FALSE.要注意的是,该变量要定义为全局,这样的话所有函数都知道程序是否运行在全屏模式下.
keys[256];
// Array Used For The Keyboard Routine
active=TRUE;
// Window Active Flag Set To TRUE By Default
fullscreen=TRUE;
// Fullscreen Flag Set To Fullscreen Mode By Default
现在我们要定义WndProc函数.原因是CreateGLWindow函数会调用WndProc函数但是WndProc函数的实现在CreateGLWindow函数后面.在C语言中,如果要在一个函数里面调用一个实现代码在其后面的函数的话,必须在该函数之前先声明要调用函数的原型.所以这里先声明WndProc函数,这样CreateGLWindow函数就能调用它了.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// Declaration For WndProc
下面函数的代码片段是用来在窗体大小变更的时候更改OpenGL场景大小的(假定你是在窗体模式下).即使你不能变更窗体大小(例如在全屏模式下),该程序也至少会在程序初次运行时被调用一次,用于创建我们的视图.OpenGL场景大小的变更是基于当前显示窗体的宽高.
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
// Resize And Initialize The GL Window
if (height==0)
// Prevent A Divide By Zero By
// Making Height Equal One
glViewport(0, 0, width, height);
// Reset The Current Viewport
下面几行代码是为屏幕创建视图.意味着物体按大小来区分距离远近.这样可以创建一个现实的观看场景.该视觉是用一个45度角基于窗体的宽高计算所得的.0.1f和100.0f的意思是我们能绘制到屏幕的深度的起始点和结束点.
glMatrixMode(GL_PROJECTION)表示接下来的两行代码是切换到投影矩阵进行处理.投影矩阵是负责添加视觉到我们的场景的.
glLoadIdentity是类似重置的作用.它把当前切换到的矩阵恢复到原始状态.在调用完glLoadIdentity函数之后,我们就开始创建我们场景视图.
glMatrixMode(GL_MODELVIEW)表示任何新的转换都会影响到模型视图矩阵.模型视图矩阵就是我们存放物体信息的容器.最后我们重置模型视图矩阵.暂时不用深究该技术细节,我将会在后面的章节讲解.你目前只需要知道它必须要写来实现视觉场景.
glMatrixMode(GL_PROJECTION);
// Select The Projection Matrix
glLoadIdentity();
// Reset The Projection Matrix
// Calculate The Aspect Ratio Of The Window
gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
glMatrixMode(GL_MODELVIEW);
// Select The Modelview Matrix
glLoadIdentity();
// Reset The Modelview Matrix
下面代码会创建OpenGL的环境.我们会设定屏幕背景的颜色,开启深度缓存,开启平滑渐变,等等.该程序会在OpenGL窗体创建的时候被调用.该函数有返回值,但是当前的入门示例并没有那么复杂,所以该返回值可以先不管.
int InitGL(GLvoid)
// All Setup For OpenGL Goes Here
下面这行开启平滑渐变.平滑渐变通过多边形很好的混合颜色和平滑理顺光源.我将会在其它教程解释平滑渐变的细节.
glShadeModel(GL_SMOOTH);
// Enables Smooth Shading
下面这行是设置当清空屏幕时的屏幕颜色.如果你不了解颜色怎么用数值表示,我很快就会在后面解释.颜色值的范围是从0.0f到1.0f. 其中0.0f表示最黑(暗),而1.0f是表示最白(亮).glClearColor函数的第一个参数是红色的强度,第二个参数是绿色而第三个是蓝色.这三个值越接近1.0f,对应颜色的光度就越大.最后一个值是透明值.现在只是清空屏幕的时候,我们不需要理会第4个值.就留空在默认值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);
// Black Background
下面三行代码是处理深度缓存的.可以把深度缓存想象成屏幕的层次.深度缓存保持跟踪物体在屏幕下的深度.本节的程序暂时还未用到深度缓冲,但所有OpenGL程序在屏幕绘制3D图形时都会用到深度缓存.它用来区分开哪个对象先绘制,例如在圆形后面绘制的正方形不会处于圆形的顶部.深度缓存是OpenGL非常重要的部分.
glClearDepth(1.0f);
// Depth Buffer Setup
glEnable(GL_DEPTH_TEST);
// Enables Depth Testing
glDepthFunc(GL_LEQUAL);
// The Type Of Depth Test To Do
接着我们要告诉OpenGL我们需要把视角修正设置为最优.这个特性只会消耗极少量的资源,但会让视角画面看起来好点.
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// Really Nice Perspective Calculations
最后我们返回TRUE.如果我们想看下初始化是否成功,可以检查返回值是TRUE还是FALSE.如果有错误你可以添加代码到返回FALSE的状态.但现在暂时先不用管这个值.
return TRUE;
// Initialization Went OK
这个函数是专门写绘制代码的地方.所有打算显示到屏幕的物体都是在这里编码.往后的各章节教程多数在这个函数里面加代码.如果你已经学完OpenGL,你就可以在glLoadIdentity函数的return TRUE语句之前创建基础形状.如果你是初学OpenGL,可以接着看后面的教程.当前我们会先做的是用之前的选定的颜色来填满屏幕,清空深度缓存和重置场景.我们暂时先不会绘制任何物体.
返回TRUE是表示程序没问题.如果你希望程序遇到一些状况后退出,可以把返回FALSE添加到异常处理中.这样程序就会退出.
int DrawGLScene(GLvoid)
// Here's Where We Do All The Drawing
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Clear The Screen And The Depth Buffer
glLoadIdentity();
// Reset The Current Modelview Matrix
return TRUE;
// Everything Went OK
下面函数是在程序退出前调用的.KillGLWindow函数是释放渲染上下文,设备上下文和终止窗体句柄.我会添加一堆错误检测.如果程序不能销毁窗体的任何部件,就会弹出错误消息窗口,来告知你关闭失败.这样可以更容易定位你代码中的问题.
GLvoid KillGLWindow(GLvoid)
// Properly Kill The Window
我们在KillGLWindow函数中做的第一件事是检查我们是否在全屏模式下.如果是在全屏模式下,我们会返回到桌面.我们可以在全屏模式关闭之前销毁窗体,但是这样做的话有些显卡会报错.所以我们还是先关闭全屏模式.这样可以防止桌面报错,并在Nvidia和3dfx显卡都运作正常!
if (fullscreen)
// Are We In Fullscreen Mode?
我们通过ChangeDisplaySettings(NULL,0)语句返回到原来的桌面.传参NULL和0来通知Windows用回Windows注册表中保存的状态值(默认分辨率,位深度,刷新频率等等)来回复到原来的桌面.当我们跳回桌面后就可以恢复显示鼠标了.
ChangeDisplaySettings(NULL,0);
// If So Switch Back To The Desktop
ShowCursor(TRUE);
// Show Mouse Pointer
下面的代码是检查我们是否有渲染上下文.如果没有创建,会跳到更后面的代码段检查是否有设备上下文.
// Do We Have A Rendering Context?
如果已经创建渲染上下文,以下代码会检查我们是否可以释放它(从设备上下文中分离出渲染上下文).留意到我们在检查错误.我一直在告诉程序尝试释放它(用下面的语句),然后检查是否释放成功.更便捷的是把操作语句都放进检查语句中.
if (!wglMakeCurrent(NULL,NULL))
// Are We Able To Release The DC And RC Contexts?
如果我们不能释放设备上下文和渲染上下文,MessageBox函数会弹出错误提示消息.NULL参数的意思是消息窗体没有父窗体.NULL右边的参数是显示在消息窗体的文本."SHUTDOWN ERROR"是现在消息窗体的顶部的文本(标题).MB_OK表示按钮的类型.MB_ICONINFORMATION会在文本旁边显示一个稍微突出的感叹图案.
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
接着我们尝试删除渲染上下文.如果删除失败会弹出错误消息.
if (!wglDeleteContext(hRC))
// Are We Able To Delete The RC?
如果删除渲染上下文失败,就弹窗提示.然后渲染上下文的变量hRC会被只空值(NULL).
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
// Set RC To NULL
现在我们来检查程序是否有设备上下文,而如果有,就释放它.如果我们释放失败,也弹窗提示并把设备上下文变量置空值(NULL).
if (hDC && !ReleaseDC(hWnd,hDC))
// Are We Able To Release The DC
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
// Set DC To NULL
现在我们检查是否已有窗体句柄,如果有的话我们会尝试用DestroyWindow(hWnd)语句来销毁该句柄.如果我们销毁窗体失败,也会弹窗提示并把窗体句柄的变量置空值(NULL).
if (hWnd && !DestroyWindow(hWnd))
// Are We Able To Destroy The Window?
MessageBox(NULL,"Could Not Release hWnd.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hWnd=NULL;
// Set hWnd To NULL
最后要做的是反注册窗体类.这个允许我们完全的杀死窗体,然后在不会提示"重复注册窗体类"的情况下重新打开一个窗体.
if (!UnregisterClass("OpenGL",hInstance))
// Are We Able To Unregister Class
MessageBox(NULL,"Could Not Unregister Class.","SHUTDOWN ERROR",MB_OK | MB_ICONINFORMATION);
hInstance=NULL;
// Set hInstance To NULL
下面我们开始创建OpenGL窗体.我花了一段时间考虑我是该以简洁的代码创建一个固定的全屏窗体,还是用简便的方案但以复杂代码定义我们的窗体.最后我选择了后者.我一直在问以下问题: 我们怎么用窗体来代替全屏? 我怎么更改窗体标题? 我们怎么更改分辨率和窗体的像素格式? 下面的代码解答了上面几条问题! 所以用后者比较容易学习,也让写OpenGL程序更简单!
如你所见,函数返回布尔值,有5个参数: 窗体标题,窗体宽度,窗体高度,颜色位数(16/24/32),和最后的全屏标记,TRUE是全屏,FALSE是窗体.我们返回一个布尔值会告诉我们窗体是否创建成功.
BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag)
当我们向Windows设置一个与我们要求匹配的像素格式时,Windows会从我们这个设定值PixelFormat变量中找.
// Holds The Results After Searching For A Match
变量wc是用来保存我们的窗体类结构的.窗体类结构体是保存关于我们窗体的信息的.通过更改该类中的不同成员,可以更改窗体的外观和交互效果.每个窗体都属于一个单独的窗体类.在你创建窗体前,你必须先为窗体注册一个类.
// Windows Class Structure
dwExStyle和dwStyle是分别保存扩展和普通窗体样式信息的.我用了多个变量来保存样式,这样我可以根据我需要创建的窗体类型来控制(全屏的话是弹窗,窗体模式的话是对话框).
// Window Extended Style
// Window Style
下面5行代码是定位方形的左上角和右下角的值.我们会用这些值来调整我们的窗体,这样绘制出来的分辨率就会精准了.一般情况下,我们绘制640x480分辨率的窗体时,边框会占用一些像素.
RECT WindowR
// Grabs Rectangle Upper Left / Lower Right Values
WindowRect.left=(long)0;
// Set Left Value To 0
WindowRect.right=(long)
// Set Right Value To Requested Width
WindowRect.top=(long)0;
// Set Top Value To 0
WindowRect.bottom=(long)
// Set Bottom Value To Requested Height
下面这行是把局部变量fullscreenflag的值赋给全局变量.
fullscreen=
// Set The Global Fullscreen Flag
下面代码中,我们为窗体创建一个实例,然后声明窗体类.
CS_HREDRAW和CS_VREDRAW样式会强迫窗体在更改大小的时候重绘.CS_OWNDC为窗体创建一个私有的设备上下文.意味着在程序内部的各个窗体不共享上下文.WndProc变量是程序用来监视消息的函数指针.没有额外的窗体数据,所以我们把额外属性置0.然后我们设置实例.接着设置hIcon属性为空,因为我们暂时不需要窗体图标,顺便把鼠标指针的图标也设置成默认的箭头.背景颜色没关系(因为我们会在OpenGL中另外设置).该窗体中我们不需要菜单,所以设置为空,剩下的窗体类名可以随便给.这里我随便给个"OpenGL"而已.
= GetModuleHandle(NULL);
// Grab An Instance For Our Window
= CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
// Redraw On Move, And Own DC For Window
wc.lpfnWndProc
= (WNDPROC) WndP
// WndProc Handles Messages
wc.cbClsExtra
// No Extra Window Data
wc.cbWndExtra
// No Extra Window Data
wc.hInstance
// Set The Instance
= LoadIcon(NULL, IDI_WINLOGO);
// Load The Default Icon
wc.hCursor
= LoadCursor(NULL, IDC_ARROW);
// Load The Arrow Pointer
wc.hbrBackground
// No Background Required For GL
wc.lpszMenuName
// We Don't Want A Menu
wc.lpszClassName
= "OpenGL";
// Set The Class Name
现在我们来注册类.如果中间有任何异常,就会有错误消息弹出.点确定就会退出程序.
if (!RegisterClass(&wc))
// Attempt To Register The Window Class
MessageBox(NULL,"Failed To Register The Window Class.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Exit And Return FALSE
现在来检查是否全屏.如果用户选择了全屏,我们就进入全屏.
if (fullscreen)
// Attempt Fullscreen Mode?
下面这几行代码有的人会看得云里雾里的,其实是转换到全屏.转换到全屏时有几个重要点要注意的.确保宽高是你想要的,更重要的是,创建窗体前要先设置好全屏模式.这里是把之前设定好的变量赋给窗体而已.
DEVMODE dmScreenS
// Device Mode
memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
// Makes Sure Memory's Cleared
dmScreenSettings.dmSize=sizeof(dmScreenSettings);
// Size Of The Devmode Structure
dmScreenSettings.dmPelsWidth
// Selected Screen Width
dmScreenSettings.dmPelsHeight
// Selected Screen Height
dmScreenSettings.dmBitsPerPel
// Selected Bits Per Pixel
dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
下面我们清空空间来保存视频设置.我们设置需要转换到的宽,高和位.我们在dmScreenSetting中保存所有宽高位的信息.在ChangeDisplaySetting后面尝试转换到储存在dmScreenSetting中的模式.我在转换模式时用CDS_FULLSCREEN变量,因为这样可以去掉屏幕底部的启动栏,加上它在全屏和窗体间切换的时候不会移动和更换你窗体的大小.
// Try To Set Selected Mode And Get Results.
NOTE: CDS_FULLSCREEN Gets Rid Of Start Bar.
if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
如果上面的代码不能切换模式,下面的代码就会执行.如果想要的全屏模式不存在,会有弹窗提示两个选项.. 可以选择运行在窗体模式还是直接退出.
// If The Mode Fails, Offer Two Options.
Quit Or Run In A Window.
if (MessageBox(NULL,"The Requested Fullscreen Mode Is Not Supported By\nYour Video Card. Use Windowed Mode Instead?","NeHe GL",MB_YESNO|MB_ICONEXCLAMATION)==IDYES)
如果用户选择了窗体模式,全屏的状态变量会赋FALSE值,然后程序继续运行.
fullscreen=FALSE;
// Select Windowed Mode (Fullscreen=FALSE)
如果用户选择关闭,会先弹窗提示一下.然后会返回FALSE来表示窗体创建不成功.然后程序就会关闭.
// Pop Up A Message Box Letting User Know The Program Is Closing.
MessageBox(NULL,"Program Will Now Close.","ERROR",MB_OK|MB_ICONSTOP);
return FALSE;
// Exit And Return FALSE
因为上面这段全屏失败并切换到窗体模式的原因,我们要在创建屏幕/窗体类型之前重新检查全屏状态值是TRUE还是FALSE.
if (fullscreen)
// Are We Still In Fullscreen Mode?
如果是仍然处于全屏模式下,就设置额外样式为WS_EX_APPWINDOW,就是一旦窗体可见就把顶级窗体强迫下放到任务栏.而窗体样式就设定为WS_POP.这个窗体类型是没有边框,这样使它能适应全屏模式.
最后,我们会禁用鼠标指针.如果你的程序非交互式的话,全屏模式下禁用鼠标通常是好的.不过视乎你决定.
dwExStyle=WS_EX_APPWINDOW;
// Window Extended Style
dwStyle=WS_POPUP;
// Windows Style
ShowCursor(FALSE);
// Hide Mouse Pointer
如果用窗体代替全屏模式,我们会添加WS_EX_WINDOWEDGE到扩展样式中.这样可以让窗体看上去更三维.样式上我们会用WS_OVERLAPPEDWINDOW代替WS_POPUP.WS_OVERLAPPEDWINDOW会创建一个有标题栏,可以更改边框,有窗体菜单和有最小化最大化按钮的窗体.
dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Window Extended Style
dwStyle=WS_OVERLAPPEDWINDOW;
// Windows Style
下面代码是用来调节我们创建的窗体的样式的.调节后会使窗体精确的确定到我们设定的分辨率.边框会重叠为窗体的部件.用AdjustWindowRectEx命令来确定OpenGL场景没有被边框覆盖,相反,窗体会被扩大到预留空间绘制边框.在全屏模式下,该命令不会影响.
AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle);
// Adjust Window To True Requested Size
在下面代码段中,我们会创建窗体并检查是否正确.我们传递所有用到的参数进CreateWindowEx()函数.我们选择要用的扩展样式.类名(就是上面注册窗体类时用的名).窗体标题.窗体样式.窗体左上角位置(0,0是最保险的).窗体的宽高.我们暂时不需要父窗体和菜单,所以我们设置成NULL.传入窗体实例并把最后一个参数置空置.
注意,我们要跟随之前定好的窗体样式,包含&WS_CLIPSIBLINGS和WS_CLIPCHILDREN.&WS_CLIPSIBLINGS和WS_CLIPCHILDREN要同时包含来确保OpenGL正常运作.这两个样式防止其它窗体在我们的OpenGL窗体上面或内部绘制图形.
if (!(hWnd=CreateWindowEx(
dwExStyle,
// Extended Style For The Window
// Class Name
// Window Title
WS_CLIPSIBLINGS |
// Required Window Style
WS_CLIPCHILDREN |
// Required Window Style
// Selected Window Style
// Window Position
WindowRect.right-WindowRect.left,
// Calculate Adjusted Window Width
WindowRect.bottom-WindowRect.top,
// Calculate Adjusted Window Height
// No Parent Window
// No Menu
hInstance,
// Instance
// Don't Pass Anything To WM_CREATE
然后我们检查窗体是否创建正常了.如果创建完毕,hWnd会持有窗体句柄.如果不正常,下面代码会弹窗提示错误消息,程序也会跟着退出.
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Window Creation Error.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
下面的代码段描述了像素格式.我们挑选了一个支持OpenGL和双缓存的格式,和RGBA一样(红绿蓝,透明管道).我们尝试找一种像素格式匹配我们选定的颜色位数(16bit,24bit,32bit).最后我们创建一个16位的Z-Buffer.剩下的参数要不没用到,要不就是不重要(先别管模板缓存和堆积缓存).
PIXELFORMATDESCRIPTOR pfd=
// pfd Tells Windows How We Want Things To Be
sizeof(PIXELFORMATDESCRIPTOR),
// Size Of This Pixel Format Descriptor
// Version Number
PFD_DRAW_TO_WINDOW |
// Format Must Support Window
PFD_SUPPORT_OPENGL |
// Format Must Support OpenGL
PFD_DOUBLEBUFFER,
// Must Support Double Buffering
PFD_TYPE_RGBA,
// Request An RGBA Format
// Select Our Color Depth
0, 0, 0, 0, 0, 0,
// Color Bits Ignored
// No Alpha Buffer
// Shift Bit Ignored
// No Accumulation Buffer
0, 0, 0, 0,
// Accumulation Bits Ignored
// 16Bit Z-Buffer (Depth Buffer)
// No Stencil Buffer
// No Auxiliary Buffer
PFD_MAIN_PLANE,
// Main Drawing Layer
// Reserved
// Layer Masks Ignored
如果创建窗体没报错的话,我们就会获取一个OpenGL设备上下文.如果获取设备上下文失败,就会弹窗提示消息,程序也会退出.
if (!(hDC=GetDC(hWnd)))
// Did We Get A Device Context?
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Can't Create A GL Device Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
获取到设备上下文后,我们会尝试找一种适合之前描述要求的像素格式.如果窗体找不到匹配的像素格式,会弹窗报错并退出程序.
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
// Did Windows Find A Matching Pixel Format?
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Can't Find A Suitable PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
如果窗体找到匹配的像素格式,我们就会尝试设置像素格式.如果设置不成功,就会弹窗提示错误消息,程序会退出.
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
// Are We Able To Set The Pixel Format?
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Can't Set The PixelFormat.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
如果像素格式设置成功,我们就会尝试获取渲染上下文.如果获取失败就会弹窗报错并退出程序.
if (!(hRC=wglCreateContext(hDC)))
// Are We Able To Get A Rendering Context?
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Can't Create A GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
如果以上皆通过,我们就会创建设备上下文和渲染上下文,剩下要做的就是激活渲染上下文.如果激活不成功,就弹窗报错并退出程序.
if(!wglMakeCurrent(hDC,hRC))
// Try To Activate The Rendering Context
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Can't Activate The GL Rendering Context.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
如果一切顺利,OpenGL窗体创建成功后,就是显示窗体了,把它设置为前端窗体(给它更多的优先级),然后设置聚焦到该窗体.然后调用ReSizeGLScene函数,传递宽高来设定我们需要的OpenGL屏幕.
ShowWindow(hWnd,SW_SHOW);
// Show The Window
SetForegroundWindow(hWnd);
// Slightly Higher Priority
SetFocus(hWnd);
// Sets Keyboard Focus To The Window
ReSizeGLScene(width, height);
// Set Up Our Perspective GL Screen
最后我们调用InitGL()函数,我们自定义用来创建光源,纹理和其它需要创建的属性.你也可以添加自己的错误校验到InitGL函数,然后返回TRUE或FALSE.例如,如果你正在载入纹理的时候遇到错误可以停止程序.如果你返回FALSE,就会弹窗报错并退出程序.
if (!InitGL())
// Initialize Our Newly Created GL Window
KillGLWindow();
// Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE;
// Return FALSE
如果上面的都通过了,就可以确认窗体创建成功了.我们返回TRUE给WinMain()函数告知没出错.这样程序才会继续执行下去.
return TRUE;
// Success
该函数是处理所有窗体消息的地方.当我们注册窗体类时,就会绑定该函数来处理窗体消息.
LRESULT CALLBACK WndProc(
// Handle For This Window
// Message For This Window
// Additional Message Information
// Additional Message Information
这个代码是把消息值当成状态来判断.uMsg会对应到我们要处理的消息名.
switch (uMsg)
// Check For Windows Messages
如果uMsg变量的值是WM_ACTIVATE,我们就会检查窗体是否仍然在激活状态.如果窗体被最小化该值会是FALSE.如果窗体处于激活状态,该值会是TRUE.
case WM_ACTIVATE:
// Watch For Window Activate Message
if (!HIWORD(wParam))
// Check Minimization State
active=TRUE;
// Program Is Active
active=FALSE;
// Program Is No Longer Active
// Return To The Message Loop
如果是uMsg的值是WM_SYSCOMMAND(系统命令),我们会对比wParam的值.如果wParam是SC_SCREENSAVE或SC_MONITORPOWER,就代表屏幕保护程序将会启动或者屏幕进入省电模式.我们会返回0以阻止这两种状况发生.
case WM_SYSCOMMAND:
// Intercept System Commands
switch (wParam)
// Check System Calls
case SC_SCREENSAVE:
// Screensaver Trying To Start?
case SC_MONITORPOWER:
// Monitor Trying To Enter Powersave?
// Prevent From Happening
如果uMsg的值是WM_CLOSE,窗体会被关闭.我们会发出一个退出消息,这样主线程中的循环会被中断.done变量会被设置为TRUE,WinMain函数中的主线程循环会停止,程序会退出.
case WM_CLOSE:
// Did We Receive A Close Message?
PostQuitMessage(0);
// Send A Quit Message
// Jump Back
如果有键被按下,我们可以通过判断wParam来确定.然后我们把keys数组中对应的值设置成TRUE.之后可以通过读取该数组来找出哪些键被按下了.这样就可以允许判断多键同时按下事件了.
case WM_KEYDOWN:
// Is A Key Being Held Down?
keys[wParam] = TRUE;
// If So, Mark It As TRUE
// Jump Back
如果键被松开,我们可以用wParam查键数组获得.然后就把数组中查得的值置FALSE.这样我读到那个数位就能知道键是按着的还是松开的.键盘上的每个键都可以用0-255之间的数值表示.例如,当我按下一个键时,返回了一个40的值,键数组的第40位的值会变成TRUE.到我松开后,它就会变回FALSE.这就是我们用数组位保存按键状态的方式.
case WM_KEYUP:
// Has A Key Been Released?
keys[wParam] = FALSE;
// If So, Mark It As FALSE
// Jump Back
当我们改变窗体大小时,就会触发事件并返回消息,uMsg的值会变成WM_SIZE.我们可以通过读取LOWORD和HIWORD的值来获取窗体大小变更后的宽高值.然后把新的宽高值传递进ReSizeGLScene()函数.OpenGL场景就会相应的变更到新的宽高.
case WM_SIZE:
// Resize The OpenGL Window
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam));
// LoWord=Width, HiWord=Height
// Jump Back
我们暂时不关心的消息可以直接传递到DefWindowProc函数,这样Windows自然会处理它们.
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
这是我们Windows程序的入口点.这里是调用常规函数,处理窗体消息和监测用户交互操作的.
int WINAPI WinMain( HINSTANCE
hInstance,
// Instance
hPrevInstance,
// Previous Instance
lpCmdLine,
// Command Line Parameters
// Window Show State
这里设置两个变量.变量msg用来检测当前等待处理的消息.变量out初始值是FALSE.它是用来标记当前还在运行状态.只要它的值仍然是FALSE,程序就会继续运行.如果从FALSE变成TRUE,程序就会退出.
// Windows Message Structure
done=FALSE;
// Bool Variable To Exit Loop
这段代码是可有可无的.它弹窗询问是否需要运行在全屏模式.如果用户点击NO按钮,变量fullscreen的值就会从TRUE变成FALSE,程序也会运行在窗体模式.
// Ask The User Which Screen Mode They Prefer
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
fullscreen=FALSE;
// Windowed Mode
这里是创建OpenGL窗体的地方.我们传入标题,宽高和色深,还有全屏选择给CreateGLWindow函数.这样就行了!我比较这种简洁的代码.如果窗体因为某些原因失败,这里会返回FALSE,然后程序会终止.
// Create Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
// Quit If Window Was Not Created
这里是循环的起始.只要done变量不为FALSE就会一直循环.
while(!done)
// Loop That Runs Until done=TRUE
循环内部首先要做的是检查是否有等待处理的窗体消息.通过PeekMessage函数,我们可以在不暂停程序的情况下检查消息.有很多程序使用GetMessage()函数代替.它也是一样的作用,但是用GetMessage函数的话,你的程序就会什么都不做,直到收到绘制消息或者其它窗体消息.
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
// Is There A Message Waiting?
下面这段代码是检查是否有退出消息发布.如果当前循环收到来自PostQuitMessage(0)函数产生的WM_QUIT消息,变量done就会被设置为TRUE,并促使程序结束.
if (msg.message==WM_QUIT)
// Have We Received A Quit Message?
done=TRUE;
// If So done=TRUE
// If Not, Deal With Window Messages
如果消息不是退出消息,我们就把它转换并派发,这样WndProc()函数或者Windows就可以处理它了.
TranslateMessage(&msg);
// Translate The Message
DispatchMessage(&msg);
// Dispatch The Message
// If There Are No Messages
如果暂时没有消息,我们会绘制OpenGL场景.第一行代码是检查当前窗体是否在激活状态.如果ESC键被按下,变量done就会被设置为TRUE,促使程序结束.
// Draw The Scene.
Watch For ESC Key And Quit Messages From DrawGLScene()
if (active)
// Program Active?
if (keys[VK_ESCAPE])
// Was ESC Pressed?
done=TRUE;
// ESC Signalled A Quit
// Not Time To Quit, Update Screen
如果程序当前是激活状态,并且ESC键没被按下,我们就提交场景并切换缓存(利用双缓存可以得到平滑无闪烁的动画).利用双缓存,我们可以在隐藏屏幕(后台)绘制所有物体而前端不会见到.当我们切换缓存时,当前屏幕会变成隐藏屏幕,而隐藏的屏幕会变成可视.这样的话我们就会看到场景逐渐绘制出来.因为它是实时出现的.
DrawGLScene();
// Draw The Scene
SwapBuffers(hDC);
// Swap Buffers (Double Buffering)
下面的代码是新加入的(2005年1月).它可以让我们通过按F1在全屏模式和窗体模式之间切换.
if (keys[VK_F1])
// Is F1 Being Pressed?
keys[VK_F1]=FALSE;
// If So Make Key FALSE
KillGLWindow();
// Kill Our Current Window
fullscreen=!
// Toggle Fullscreen / Windowed Mode
// Recreate Our OpenGL Window
if (!CreateGLWindow("NeHe's OpenGL Framework",640,480,16,fullscreen))
// Quit If Window Was Not Created
如果变量done的值变为TRUE,程序就会结束.我们要在关闭OpenGL窗体之前释放资源,然后退出程序.
// Shutdown
KillGLWindow();
// Kill The Window
return (msg.wParam);
// Exit The Program
在本节中,我尝试解释尽量多的细节,包括所有初始化步骤,例如像创建全屏模式的OpenGL程序,按ESC退出,监控窗体是否激活.我花了2周时间写这节的代码,..(后面省略一大堆话,作者应该是有工匠/艺术家情结的,或者说是完美主义)
阅读(...) 评论()}

我要回帖

更多关于 opengl硬件加速 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信