最后一篇的"自剖"。本篇是:自剖一下自己用的NEHE OpenGL框架(下篇),可能涉及更多的关于OpenGL与MFC、系统间的关联。——ZwqXin.com
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/3-nehe-frame-vc6.html.html
之前一直觉得,为了逃避MFC那一套,对自己用了这么长时间的狂架却没有个很好的了解,真惭愧。于是这三篇傻傻的文章就当作自己 更好地了解NEHE OpenGL框架,同时学习一下MFC咯,自我修炼。(友情提醒:如果你只为OPENGL初学者之初学者,而不懂MFC也不想懂MFC,建议碰到类似的文章先别看[包括本文],更有益的是先关注渲染部分^^)
上两篇日志请看:自剖一下自己用的NEHE OpenGL框架(上篇)
:自剖一下自己用的NEHE OpenGL框架(中篇)
好了,继续探讨CMainframe类的实现:
- CMainFrame::CMainFrame() //构造函数
- {
- ...................//这里对应,向导为我们设定了头文件中变量相应初始值,某些可在向导里更改,我一向用1024*768,depth:32
- // 开始渲染前问问是否需要全屏幕,MessageBox函数参数一目了然
- if (MessageBox("Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDYES)
- { m_bFullScreen=TRUE;
- // 加自定义的变量初始值于此
- }
- CMainFrame::~CMainFrame() //析构,调用KillGLWindow()
- { KillGLWindow(); }
接下来的就比较重要了:
- //在建立一个窗口前,你总得做些什么。从第一行代码看会觉得它带来了CFrameWnd的一切。但是其实它们的本源还是 CWnd(重载于它)。窗口始终要被附加(attach)到一个CWnd对象上操作,CWnd会最终创建窗口
- BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
- {
- //CFrameWnd的PreCreateWindow究竟做了什么?太底层了我现在不想追查下去
- if (!CFrameWnd::PreCreateWindow(cs))
- return FALSE;
- //EnumDisplaySettings获得当前显示驱动所支持的所有显示模式,这里是指渲染窗口未出现时我们的屏幕设置。存储到上文提到过的m_DMsaved中
- //ChangeDisplaySettings则是相应的改变显示模式了(见稍下方)
- EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &m_DMsaved);
- //全屏幕时候的设置:临时搭建一个DEVMODE对象并给予它“我们所定义的屏幕设置”的信息,把它的显示模式“拉伸”改变为全屏幕(注意,屏幕还是我自己设定那个1024*768*32,只是被“拉伸”了,事实上我自己屏幕就是1024*768,拉不拉没所谓,不过全屏下退出只有按ESC键了)
- if (m_bFullScreen)
- {
- DEVMODE dmScreenSettings;
- dmScreenSettings.dmPelsWidth = VB_WIDTH;
- dmScreenSettings.dmPelsHeight = VB_HEIGHT;
- dmScreenSettings.dmBitsPerPel = VB_DEPTH;
- dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;
- if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
- { 如果改变失败时执行的操作,无非是提示用户想不想退出程序之类,向导给我们做好了}
- }
- // 参入的CREATESTRUCT结构的cs,在CWnd中定义了创建函数创建窗口所用的初始参数(窗口风格),成员变量cx,cy就是窗口宽高了
- cs.cx = VB_WIDTH;
- cs.cy = VB_HEIGHT;
- if (m_bFullScreen)//全屏时还要加些样式
- {
- cs.dwExStyle=WS_EX_APPWINDOW;//这个叫拓展样式,你查下,很傻B的
- cs.style=WS_POPUP;// 猥琐(?)地弹出
- ShowCursor(FALSE);//隐藏鼠标
- }
- else
- {
- cs.dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;//突起边缘
- cs.style=WS_OVERLAPPEDWINDOW;//一个有标题栏的窗口,不猥琐
- //cs.y和cs.x是新窗口的左上角Y,X坐标,GetSystemMetrics(SM_CYSCREEN)获得屏幕高,下面那个就是屏幕宽了.计算结果窗口就位于屏幕中心了
- cs.y = (int) GetSystemMetrics(SM_CYSCREEN) / 2 - cs.cy / 2;
- cs.x = (int) GetSystemMetrics(SM_CXSCREEN) / 2 - cs.cx / 2;
- }
- //LPCSTR变量lpszClass是新窗口的窗口类名,默认的CS_OWNDC说明窗口有自己的DC.另外里面还嵌套了个LoadCursor(第一参数是NULL,说明LOAD的系统光标I,DC_ARROW表示指针)
- cs.lpszClass = AfxRegisterWndClass(CS_OWNDC|CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,
- LoadCursor(NULL,IDC_ARROW), NULL, NULL);
- return TRUE;
- }
- #ifdef _DEBUG
- void CMainFrame::AssertValid() const //这些DEBUG函数在中篇说过了
- {CFrameWnd::AssertValid();}
- void CMainFrame::Dump(CDumpContext& dc) const
- {CFrameWnd::Dump(dc);}
- #endif //_DEBUG
除了初始化窗口样式,还有更重要的就是OnCreateClient重载函数。它Cwnd建窗时执行 OnCreate()时候被自动调用。一般告诉你别改,但是这里是为了加一些另外的设置,实际上第一条语句就是调用上级类的重载版了,参数也没拿来改。恩,我们是为了在建立窗口的同时希望系统附带上我们给OPENGL的一些设置。而这些设置很重要,是OPENGL与系统设备的交流,也是它注定比DirectX更高移植性的体现,这里只是一个与WINDOWS设备交互的例子而已。
我们在上篇文章讲过DC和渲染描述句柄RC。正确点来说它们就是两块内存。内存当然要来存东西。
设备描述句柄(DC):一个定义一组图形对象及其属性、影响输出的图形方式(数据)结构。它是WINDOWS提供的关于我们目前计算机上的设备的信息。你要在屏幕上搞东西啊?行!不过得问过我们的显示器屏幕(获得屏幕的DC)先。
渲染描述句柄(RC):这个就是属于我们OPENGL的了。它定义了一系列渲染相关信息,例如下面你将看到的像素信息。只有一个现行RC存在(对应于一个线程,唯一),OPENGL才会工作。
DC和RC的交互,因此也就是OPENGL与WINDOWS系统设备的交流。
- BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
- {
- // 我们先调用“真正的”OnCreateClient,成功了才来做我们的设置
- BOOL bRet = CFrameWnd::OnCreateClient(lpcs, pContext);
- if (bRet)
- {
- GLuint PixelFormat; //用来存储所选择的像素信息
- //PIXELFORMATDESCRIPTOR结构类型ptd描述了屏幕上所有像素的格式,可以说是一个描述器
- static PIXELFORMATDESCRIPTOR pfd=
- {
- sizeof(PIXELFORMATDESCRIPTOR),//这个描述器大小
- 1, //版本
- //以下三项表明兼容性:这种像素格式要符合WINDOWS窗口,OPENGL ,双缓冲(可选)标准
- PFD_DRAW_TO_WINDOW |
- PFD_SUPPORT_OPENGL |
- PFD_DOUBLEBUFFER,
- PFD_TYPE_RGBA,//颜色格式RGBA
- VB_DEPTH, //颜色深度就用屏幕深度
- //然后进行一些像素规格设置,有些也不知道是什么来的,我用原来向导给的英文注释标明防歧义
- 0, 0, 0, 0, 0, 0, //Color Bits Ignored
- 0, //alpha通道缓冲,不设置
- 0, // Shift Bit Ignored
- 0, //累积缓冲,OPENGL五大缓存之一,需要时可启用
- 0, 0, 0, 0,// Accumulation Bits Ignored
- 16, //设置16位深度缓冲,五大缓存之一
- 0, //蒙板缓冲,五大缓存之一,需要时可启用
- 0, //辅助缓冲,五大缓存之一,需要时可启用
- PFD_MAIN_PLANE,// Main Drawing Layer(主绘图区)
- 0, //保留
- 0, 0, 0 //Layer Masks Ignored
- };
- //注,五大缓存还有一个是颜色缓存(都设RGBA了,肯定有的,不然渲染窗口显示个啥)
- //下面就是像素设置了(注:以下{...}部分无非威胁我们设置不成功会如何如何(其实很必要的))
- //先由Cwnd为我们获得DC,存入CMainframe的变量m_hDC 中:
- if ( !( m_hDC = ::GetDC ( m_hWnd ) ) ) {.......}
- //根据DC和刚才的像素描述器选择像素格式,返回像素格式索引号,存入刚才定义的PixelFormat 中
- if ( !( PixelFormat = ChoosePixelFormat ( m_hDC, &pfd ) ) ) {.......}
- //根据DC,描述表和像素格式索引号设置像素格式:
- if ( !SetPixelFormat ( m_hDC, PixelFormat, &pfd ) {.......}
- //下面是管理RC.先说一下用来管理RC的WGL函数(WGL – Windows的 OpenGL扩展层):
- ※ HGLRC wglCreateContext(HDC hdc) //设置完像素格式后调用,根据DC创建一个RC
- ※BOOL wglMakeCurrent(HDC hdc, HGLRC hglrc) //关联DC和RC,让RC成为现行(激活)的
- ※HGLRC wglGetCurrentContext() //返回现行RC
- ※HDC wglGetCurrentDC() //返回与现行RC关联的那个DC
- ※BOOL wglDeleteContext(HGLRC hglrc) //删除一个RC(最好先让它变成非现行状态)
- ※BOOL wglUseFontBitmaps(HDC hdc, DWORD dwFirst, DWORD dwCount, DWORD dwBase) //这个用来根据DC创建字符显示列表
- //下面需要用到最重要的第1,2个,至于第5个(删除)在后面退出时用:
- if ( !( m_hRC = wglCreateContext ( m_hDC ) ) ) {.......}
- if ( !wglMakeCurrent ( m_hDC, m_hRC ) ) {.......}
- if ( !InitGL () ) {.......} //最后还要处理opengl的初始化
- m_bAppIsActive = TRUE;//以上全成功了,可以渲染了!
- }
- return bRet; //没错的话,现在可以打开窗口了
- }
然后看看由析构函数调用的KillGLWindow()
- GLvoid CMainFrame::KillGLWindow(GLvoid)
- {
- //当前是全屏的话,首先检查一下未关闭前的这个屏幕设置能不能直接用到关闭后正常的那个屏幕上,可以的话就直接RESRT成那样就可以了;
- //不行的话将当前设置变空,再把当初打开窗口前放进m_DMsaved(还记得么)的设置拿出来还给屏幕
- if (m_bFullScreen)
- {
- if (!ChangeDisplaySettings(NULL,CDS_TEST)) {
- ChangeDisplaySettings(NULL,CDS_RESET);
- ChangeDisplaySettings(&m_DMsaved,CDS_RESET);
- } else {
- ChangeDisplaySettings(NULL,CDS_RESET);
- }
- ShowCursor(TRUE);
- }
- //在有现行RC的情况下,关闭窗口的时候要delete它(delete前最好让它"非现行化")
- if ( m_hRC ) {
- if ( !wglMakeCurrent ( NULL, NULL ) ){....;}//省略的是出错提醒
- if ( !wglDeleteContext ( m_hRC ) ) { .....;m_hRC = NULL;}
- }
- //最后删除DC,删除窗口
- if ( m_hDC && !::ReleaseDC ( m_hWnd, m_hDC ) ) {....;m_hDC = NULL;}
- if ( m_hWnd && !::DestroyWindow ( m_hWnd ) ){....;m_hDC = NULL;}
- }
最后来粗略看看我说过的NEHE OpenGL框架中与渲染最相关的函数RenderGLScene,ReSizeGLScene,InitGL,因为这些都已经脱离MFC那套,完全是属于OPENGL的东西了。所以详细讲解这里不说了,有兴趣可看看我的OPENGL类别的关于它们的文章。
- void CMainFrame::RenderGLScene()
- {
- if (!m_bAppIsActive)return;//记得吗,m_bAppIsActive为真时才渲染
- glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除背静 // Clear Screen And Depth Buffer
- glLoadIdentity();//单位化
- ........................
- SwapBuffers(m_hDC);//更新缓存区
- Invalidate(FALSE); //拒绝无效化
- }
- //初始化和重置窗口大小,要使用时相关代码移到RenderGLScene开始部分
- GLvoid CMainFrame::ReSizeGLScene(GLsizei width, GLsizei height){......}
- //初始化程序,作OPENGL的预处理
- int CMainFrame::InitGL(GLvoid){.....}
终于来到这里,这两天基本都在搞这个啦,一连三篇。辛苦啦,ZwqXin!首先感谢MSDN(网络英文版),其次感谢CSDN讨论区的资料,还有数不请BLOG的作者们呵呵,感谢“居然能看这种文章”然后看到这里的各位大大(我的理解若有问题请不吝指出哦!)。
本文来源于 ZwqXin (http://www.zwqxin.cn/), 转载请注明
原文地址:http://www.zwqxin.cn/archives/MFC/3-nehe-frame-vc6.html.html