小鸟在MFC下的OpenGL学习笔记(一)

大家好,我是小鸟.小鸟经过一段时间的学习,OpenGL红宝皮书,NE_HE的OpenGL的教程都看了一些.这两个教程的全部是用C语言编写的,用的全是API函数.报着学习的态度,小鸟产生了一个想法,把NE_HE教程里面的例子全部用MFC重新写一遍,算是对自己学习的一个检验.其中大部分代码都是从NE_HE的OPENGL教程中摘下来的.小鸟的水平有限,希望和大家共勉.有不足指出也请大家指正.

   第一课.创建一个OpenGL窗口

     创建一个空的OpenGL窗口,关键在于在MFC下配置OpenGL的环境.参考网上的一些配置,主要有三个步骤:设置窗口像素格式;产生RC;设置为当前RC.

1.创建一个空的MFC对话框工程,所有选项都为默认值.

2.与教程中相同,将OpenGL32.lib GLu32.lib 和 GLaux.lib 加入到工程中,方法:在Project->Settings->Link->Object/library modules中输入OpenGL32.lib GLu32.lib GLaux.lib,不要忘记空格.

3.删除对话框资源中的所有按钮,控件.这样对话框看起来比较干净.

4.在StAfx.h文件中增加#include <glew.h> #include<glut.h>包含.这里不需要windows.h,因为afxwin.h中已经包含了windows.h.

至此,对工程的设置以及库的引用都设置完毕.然后根据前面的三个步骤来对OpenGL进行初始化.首先在dlg.h文件中增加以下变量

    HGLRC           hRC;        // 窗口着色描述表句柄
    HDC             hDC;        // OpenGL渲染描述表句柄

 

    1.设置窗口像素格式     增加函数SetWindowPixelFormat(HDC m_hDc),函数代码如下:

 

BOOL CLesson1Dlg::SetWindowPixelFormat(HDC m_hDc)
...{
    
static    PIXELFORMATDESCRIPTOR pfd=                    // /pfd 告诉窗口我们所希望的东东,即窗口使用的像素格式
    ...{
        
sizeof(PIXELFORMATDESCRIPTOR),                    // 上述格式描述符的大小
            1,                                // 版本号
            PFD_DRAW_TO_WINDOW |                        // 格式支持窗口
            PFD_SUPPORT_OPENGL |                        // 格式必须支持OpenGL
            PFD_DOUBLEBUFFER,                        // 必须支持双缓冲
            PFD_TYPE_RGBA,                            // 申请 RGBA 格式
            24,                                // 选定色彩深度
            000000,                        // 忽略的色彩位
            0,                                // 无Alpha缓存
            0,                                // 忽略Shift Bit
            0,                                // 无累加缓存
            0000,                            // 忽略聚集位
            16,                                // 16位 Z-缓存 (深度缓存)
            0,                                // 无蒙板缓存
            0,                                // 无辅助缓存
            PFD_MAIN_PLANE,                            // 主绘图层
            0,                                // Reserved
            000                                // 忽略层遮罩
    }
;
    
int PixelFormat = 0;
    
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))                // Windows 找到相应的象素格式了吗?
    ...{
        MessageBox(
"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
        
return FALSE;                            // 返回 FALSE
    }

    
if(!SetPixelFormat(hDC,PixelFormat,&pfd))                // 能够设置象素格式么?
    ...{
        MessageBox(
"不能设置像素格式","错误",MB_OK|MB_ICONEXCLAMATION);
        
return FALSE;                            // 返回 FALSE
    }


    
return TRUE;
}

响应WM_CREATE消息,在OnCreate函数中加入

 

    HWND hWnd1 = this->GetSafeHwnd();
    hDC 
= ::GetDC(hWnd1);
    
if(!SetWindowPixelFormat(hDC))
    
...{
        
return 0 ;
    }

 2.产生RC

在OnCreate函数中加入

if (!(hRC=wglCreateContext(hDC)))                    // 能否取得着色描述表?
    ...{
        MessageBox(
"不能创建OpenGL渲染描述表","错误",MB_OK|MB_ICONEXCLAMATION);
        
return FALSE;                            // 返回 FALSE
    }

 3.设置当前RC

在OnCreate函数中加入

 

if(!wglMakeCurrent(hDC,hRC))                        // 尝试激活着色描述表
    ...{
        MessageBox(
"不能激活当前的OpenGL渲然描述表","错误",MB_OK|MB_ICONEXCLAMATION);
        
return FALSE;                            // 返回 FALSE
    }

然后,响应WM_SIZE消息,当窗口大小改变时,改变视口大小.

 

void CLesson1Dlg::OnSize(UINT nType, int cx, int cy) 
...{
    CDialog::OnSize(nType, cx, cy);    
    
// TODO: Add your message handler code here
    GLsizei width,height;
    width 
= cx;
    height 
= cy;
    
if (cy==0)                                // 防止被零除
    ...{
        height
=1;                            // 将Height设为1
    }

    glViewport(
0,0, width, height);                    // 重置当前的视口
    glMatrixMode(GL_PROJECTION);                        // 选择投影矩阵
    glLoadIdentity();                            // 重置投影矩阵    
    
// 设置视口的大小
    gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
    glMatrixMode(GL_MODELVIEW);                        
// 选择模型观察矩阵
    glLoadIdentity();                            // 重置模型观察矩阵
}

响应WM_DESTROY消息,当窗口关闭时释放HRC与HDC的关联.在这里,大家很明白了.其实所谓的OpenGL的初始化,或者是设置,就是将OpenGL帮定到窗口的绘图区,这样当你进行绘制的时候就知道你要在那里进行绘制了.

void CLesson1Dlg::OnDestroy() 
...{
    CDialog::OnDestroy();
    
    
if (hRC)                                // 我们拥有OpenGL渲染描述表吗?
    ...{
        
if (!wglMakeCurrent(NULL,NULL))                    // 我们能否释放DC和RC描述表?
        ...{
            MessageBox(
"释放DC或RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
        }

        
if (!wglDeleteContext(hRC))                    // 我们能否删除RC?
        ...{
            MessageBox(
"释放RC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
        }

        hRC
=NULL;                            // 将RC设为 NULL
    }

    
if (hDC && !::ReleaseDC(this->GetSafeHwnd(),hDC))                    // 我们能否释放 DC?
    ...{
        MessageBox(
"释放DC失败。","关闭错误",MB_OK | MB_ICONINFORMATION);
        hDC
=NULL;                            // 将 DC 设为 NULL
    }

    
}

然后,参照NE_HE教程,就可以进行OpenGL的绘制了,将NE_HE的教程移植如下:

初始化函数:

 

int CLesson1Dlg::InitGL(GLvoid)
...{
    glShadeModel(GL_SMOOTH);                        
// 启用阴影平滑
    glClearColor(1.0f0.0f0.0f0.0f);                    // 黑色背景
    glClearDepth(1.0f);                            // 设置深度缓存
    glEnable(GL_DEPTH_TEST);                        // 启用深度测试
    glDepthFunc(GL_LEQUAL);                            // 所作深度测试的类型
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);            // 告诉系统对透视进行修正
    return TRUE;                                // 初始化 OK    
}

 绘制函数:

int CLesson1Dlg::DrawGLScene(GLvoid)
...{
    glClear(GL_COLOR_BUFFER_BIT 
| GL_DEPTH_BUFFER_BIT);            // 清除屏幕和深度缓存
    glLoadIdentity();                            // 重置当前的模型观察矩阵
    
    SwapBuffers(hDC);
    
return TRUE;                                //  一切 OK
}

这里小鸟也有个疑问,希望高手能帮我解答.为什么我起用新线程绘制的方法,如果把SwapBuffers(hDC)放在绘制线程中,我用FRAPS工具查看,他不会进行重绘.不知道是

在OnCreate函数中调用InitGL函数,在OnPaint函数中调用DrawGLScene函数,如下:

void CLesson1Dlg::OnPaint() 
...{
    
if (IsIconic())
    
...{
        CPaintDC dc(
this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 
0);

        
// Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        
int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(
&rect);
        
int x = (rect.Width() - cxIcon + 1/ 2;
        
int y = (rect.Height() - cyIcon + 1/ 2;

        
// Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }

    
else
    
...{
        DrawGLScene();
        CDialog::OnPaint();
    }

}

最后,就是对场景进行循环绘制了.很多人在MFC中进行循环绘制的时候都喜欢用OnTimer函数定时绘制.虽然方便,但OnTimer函数的精度只有毫秒,对于大型场景的绘制来说,特别是大型3D游戏,这种精度是很不够的.所以在这里,我还是启用一个绘制线程,进行循环绘制.

循环绘制线程:

UINT DrawThread(LPVOID lparam)
...{
    CLesson1Dlg
* lDlg = (CLesson1Dlg*)lparam;
    
while (!lDlg->GetSafeHwnd())
    
...{
    }

    
while (TRUE)
    
...{

        lDlg
->Invalidate(FALSE);    
    }

    
return 0;
}

这里小鸟也有个疑问,希望高手能帮我解答.为什么把SwapBuffers(hDC)放在绘制线程中,我用FRAPS工具查看,他不会进行重绘.所以只有将交换缓存的代码放在绘制函数DrawGLScene中了.

在这里小鸟是按照NE_HE的教程,直接循环不停绘制.并没有对FPS进行绘制.这样程序的FPS是很不稳定的,并且很高.由于MFC中有WM_TIMER事件,处理定时器信息很方便.因此,很多用MFC开发的人都喜欢用WM_TIMER时间来定时绘制,以次来保证FPS.但是WM_TIMER消息的精度是很低的,大概只有55ms(网上说的,小鸟还没有验证过,姑且相信吧),做简单的动画还可以,但一旦绘制的场景比较复杂,绘制耗费的时间比较多的时候,就很难保证FPS了.下面,给出一个精确控制FPS的绘制方法,精确到微秒.不要问小鸟是怎么知道的,在网上去找找资源吧.首先熟悉2个函数:QueryPerformanceFrequency()和 QueryPerformanceCounter()函数.函数的原型如下:

BOOL  QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
       BOOL  QueryPerformanceCounter(LARGE_INTEGER *lpCount);

对于_LARGE_INTEGER结构,大家自己去查查申明吧.QueryPerformanceFrequency返回一秒间的计时频率数,即时钟频率,QueryPerformanceCounter()返回CPU运行时长,这里是以CPU运行记数来统计的.,是精确到微秒的哦.好了,先把代码贴下来再解释吧.绘制线程代码如下:

UINT DrawThread(LPVOID lparam)
...{
    CLesson1Dlg
* lDlg = (CLesson1Dlg*)lparam;
    
while (!lDlg->GetSafeHwnd())
    
...{
    }

    _int64 freq, cnt, oldcnt;
    QueryPerformanceFrequency((PLARGE_INTEGER)
&freq);    //取CPU运行频率
    QueryPerformanceCounter((PLARGE_INTEGER)&oldcnt);    //取开始绘制时CPU运行记数
    freq /= 60;                                          //1/60秒内CPU要运行多少下
    while (TRUE)
    
...{
        lDlg
->Invalidate(FALSE);                         //重新绘制
        do
        
...{
            QueryPerformanceCounter((PLARGE_INTEGER)
&cnt);   //取当前CPU运行记数
        }
 while (cnt - oldcnt < freq);                       //判断是否过了1/60秒
             oldcnt = cnt;                                   //重新给开始绘制时CPU运行记数赋值
    }

    
return 0;
}

在这里,我是保证FPS为60.啊?什么?FPS为60是什么意思?小鸟要晕了.简单的说,就是一秒内绘制60次了.大家可以到网上去找找更加专业的解释.那就是说,绘制一桢的时间为1/60秒了.所以取了CPU运行频率后,用freq /= 60计算1/60秒内CPU要记多少次数.绘制结束后,就要判断时间间隔了,一定要保证整个过程间CPU的技术是恰好大于或等于刚才得出的CPU在1/60秒内的记数的,这样就可以保证绘制过程总是保持恰好在1/60秒完成.当然,这种方法也不是完全的无误差,因为QueryPerformanceFrequency()和 QueryPerformanceCounter()函数的精度都取决于你的CPU的速度的,但确是目前小鸟以知的方法中最精确的了(没办法,小鸟的水平有限啊).

 在OnCreate函数的最后启动线程: 

//启动绘制线程
AfxBeginThread(DrawThread,(LPVOID)this);

不知道为什么贴不了代码了,就这样吧.

至此,第一课的内容就完全的移到MFC下了.同时也填加了自己的一些东西,比如说对FPS进行控制.但是,有一点还是没有实现,就是全屏功能.这是因为小鸟暂时也不知道MFC下怎么样实现全屏幕.小鸟会继续努力,把全屏功能补上的.还有,键盘响应我也没有填加.问我为什么?其实大家都知道,MFC有键盘响应消息.但小鸟刚用了高精度的方法控制FPS,所以就觉得MFC的键盘响应的精度不够了.用MFC的键盘消息做键盘响应,相信大家都知道.这里就不多说了。我会把高精度的键盘响应补上的.到这里,其实MFC下OpenGL的框架也就搭好了,可以在这一课的基础上进行下面的学习了.等着小鸟的下一集学习笔记吧

 

 
阅读更多
想对作者说点什么? 我来说一句

opengl mfc 载入ply2文件

Half_open Half_open

2015-11-20 22:01:35

阅读数:1492

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭