« OpenGL常用的库自剖一下自己用的NEHE OpenGL框架(中篇) »

自剖一下自己用的NEHE OpenGL框架(上篇)

话说学OpenGL也有一些时间了,但是其实感觉自己对很多东西还是一知半懂。在大战开始前夕(考试那段日子被shadow volume原理弄得半崩溃状,曾下决心寒假初一定要自己呕个实现出来~),我得先捡回一些“一知半懂”的知识。这里作为自我修炼的一环,目的在于回头看看自己一直在用的NEHE框架。(友情提醒:如果你只为OPENGL初学者之初学者,而不懂MFC也不想懂MFC,建议碰到类似的文章先别看[包括本文],更有益的是关注渲染部分^^)——ZwqXin.com

此日志自我学习用。

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/MFC/nehe-frame-vc6.html

向导生成了一个 CMainframe类和一个C***App类。先来看看C***App类。
C***App类(头文件部分):

  1. #if _MSC_VER > 1000    //如果vc编译器的版本大于1000则...     
  2. #pragma once
  3. #endif // _MSC_VER > 1000
  4.  
  5. //关于#pragma once和#ifndef-#define-#endif的区别:
  6. //1.前者指由编译器保证“某一个文件”(the same file)不会被包含多次
  7. //(但是如果有多个"同名同内容的头文件",那么它们就造成了重复包含,这时候应该用后者来避免);
  8. //2.后者指编译器保证"同一个名称的文件"(files with the same name)不会被包含多次
  9. //(但是因为它是按名称来标识文件的,所以如果有多个“同名而不同内容的头文件“,其中一个就会被掩盖了)。
  10. //所以,如果有两个同名的.h,(而不想或无法或忘记改名的时候,)如果内容不同,#pragma once; 相同,#ifndef
  11.  
  12. #ifndef __AFXWIN_H__
  13.     #error include 'stdafx.h' before including this file for PCH
  14. #endif
  15. //这是要求所有源代码模块都包含预编译头文件(这样能够加快编译的速度),如果没包含就报错。
  16.  
  17. #include "resource.h"
  18.  //资源文件和源代码文件的桥梁,伙同资源文件.rc,链成.res

  1. class C***App : public CWinApp  //继承CWinApp, 控制整个应用程序
  2. {   //运行程序要将该类实例化,并调用构造和重载的InitInstance()
  3.   //因此事实上是透过CWinApp进入WinMain,WinMain对其InitInstance()
  4.   //的控制导致C***App ::InitInstance()的自动执行,开始了一个应用程序实例的进程
  5.  
  6. public:       C***App();
  7. private:
  8.     LONGLONG   m_lCurTime;   //当前时间 
  9.     DWORD       m_dwTimeCount; //每帧的用时(没counter的时候就拿它作默认值)
  10.     LONGLONG   m_lPerfCounter; // 定时器频率
  11.     BOOL        m_bPerFlag; // 选择器,决定哪个定时器起作用
  12.     LONGLONG   m_lNextTime;// 渲染下一帧的时间
  13.     LONGLONG   m_lLastTime; // 当前帧(开始)的时间
  14.     double       m_dTimeElapsed;    // 自从当前帧开始到现在的时间
  15.     double       m_dTimeScale;  // 一个时间的乘数
  16.     LONGLONG    m_lFramesPerSecond; // 程序运行时的FPS
  17. // 重载的函数
  18. public:
  19.     virtual BOOL InitInstance();
  20.     virtual int Run();   //这个很明显....run
  21. public:         
  22.     //这里留给编译器,谁也别动 (DO NOT EDIT HERE)              
  23.     DECLARE_MESSAGE_MAP() //消息映射函数,关于它可可以延伸一下:
  24. };
  25. //MFC中提供了四个宏: 
  26. //DECLARE_MESSAGE_MAP() :声明一些函数供接下来使用
  27. //BEGIN_MESSAGE_MAP(子类,父类) :实现开始( 把消息函数的函数指针联系起来)
  28. //ON_COMMAND    :定义具体的消息和消息对应的函数
  29. //END_MESSAGE_MAP() : 实现结束.  你看下一篇接下来的实现就明白啦    

然后就来看看实现啦:

  1. #ifdef   _DEBUG     //   判断是否定义_DEBUG,如果是,则:
  2. #define  new    DEBUG_NEW     //   定义调试new宏,取代new关键字
  3. //原来我们一直在用的new早被替换了呀~阴啊阴啊
  4. //这个DEBUG_NEW可以在DEBUG状态下定位内存泄露并且跟踪文件名
  5. //追踪它,发现 #define DEBUG_NEW new(THIS_FILE, __LINE__)
  6. //晕,这是啥米new啊?既不像placement-new也没看见<new>啊
  7.  
  8. //网上说是new的重载只要满足第一个参数是个SIZE,接下来可以乱(?)搞啦
  9. //MFC把这里得到每个new调用的文件名及所在源文件中的行数记录下来,
  10. //如此当delete时就可以检查所有原申请的内存,
  11. //如果 发现内存指针没有被释放,呵呵,你就惨了(内存泄露了!!),
  12. //这时编译器会告诉你是哪个new(在哪行)申请的内存没被释放。
  13.     
  14. #undef   THIS_FILE     //   取消THIS_FILE的宏定义,使得其暂无定义  
  15. static   char   THIS_FILE[]=__FILE__;     //   定义THIS_FILE指向文件名   
  16. //THIS_FILE 变量,如你所见,作为一个数组存储__FILE__ 
  17. //__FILE__   常量,是编译器能识别的预定义ANSI- C 6宏之一,武功盖世,代表当前文件名(**.cpp)
  18. #endif     //   结束      
 其实MFC里所有实现文件都这样做,是为什么?呵呵,一个DEBUG除虫的环境啦!
  1. BEGIN_MESSAGE_MAP(CFgfgfApp, CWinApp)
  2. //..............  这啥?没东西?呵呵,都说例子在下篇日志咯,这里COMMAND为空do nothing
  3. END_MESSAGE_MAP()
  4.  
  5. C***App::C***App()  //构造函数,基本设0,除了:
  6. {.........} //m_dwTimeCount = 8(每帧持续8秒)FPS预设90
  7.  
  8. C***App theApp; //实例了啊实例了啊
  9.  
  10. BOOL C***App::InitInstance()
  11. {
  12. //公司注册??汗
  13.     SetRegistryKey(_T("Local AppWizard-Generated Applications"));
  14.  
  15. //接下来才是真的:构筑窗口作为应用程序的主窗口对象:
  16.     m_pMainWnd = NULL;
  17.     CMainFrame* pFrame = new CMainFrame; //调用CMainFrame类(框架类)了,下篇讲述
  18.  
  19. //这个Create()函数是CMainFrame类的父亲CFrameWnd的,然后CFrameWnd有个父亲叫CWnd,CWnd有个父亲叫CCmdTarget,CCmdTarget有个父亲叫CObject,(它别名可能叫亚当之子)
  20.     if (!pFrame->Create(NULL,"MFC OpenGL"))
  21.         return FALSE;
  22.  
  23. //话说回来这个m_pMainWnd是从哪里弹出来的呢?哦~原来是咱们C***App的父亲CWinApp的父亲CWinThread那继承来的啊.(注意,CWinThread父亲就是上面提到的CCmdTarget. 哈都是CObject子孙嘛)
  24.     m_pMainWnd = pFrame; //获得窗口对象
  25.     pFrame->ShowWindow(m_nCmdShow); //显示窗口
  26.     pFrame->UpdateWindow();//更新,重绘屏幕
  27.  
  28.     return TRUE;
  29. }

接下来就是一个带计时器功能,控制窗口怎样重绘法的Run()函数啦,别忘记它也是重载而来的哦.

  1. int C***App::Run()
  2. {
  3.     if (!m_pMainWnd)
  4.         AfxPostQuitMessage(0);
  5.  
  6.     MSG msg;
  7. //STATIC_DOWNCAST .....强制类型转换的宏?请看MSDN
  8. //m_pMainWnd 被直接转换为指向CMainFrame类(这编译器转型要求此类必须继承于CObject  - -)的指针了
  9. CMainFrame* pFrame = STATIC_DOWNCAST ( CMainFrame, m_pMainWnd );
  10.  
  11. //QueryPerformanceFrequency:高精计频器获取一个指向定时器每秒的频率数(m_lPerfCounter ,单位n/s)的指针,并把系统值(CPU时钟频率,每秒嘀哒声的个数)交给它.返回值表明是否支持高精计时
  12. //LARGE_INTEGER,结构体类型,模拟64位有符号的二进制整数 (或者用__int64)
  13.     if ( QueryPerformanceFrequency ( ( LARGE_INTEGER *) &m_lPerfCounter ) ) { 
  14.         
  15.         m_bPerFlag = TRUE; //选择系统时钟
  16.  
  17. //原默认值为8的m_dwTimeCount 现在就是实时的(n/s)/(f/s)=n/f每帧的频数
  18.         m_dwTimeCount = unsigned long(m_lPerfCounter / m_lFramesPerSecond);
  19.  
  20. //同样,高精计数器QueryPerformanceCounter 获取用于指向当前"CPU运行到现在的嘀哒数(n)"的指针,并把值交给它(m_lNextTime)来指向
  21.         QueryPerformanceCounter ( ( LARGE_INTEGER * ) &m_lNextTime ); 
  22.         m_dTimeScale = 1.0 / m_lPerfCounter;  //  s/n
  23.     } else  { 
  24.  
  25. // 如果系统不支持高精计数器,没办法了,用回粗糙的timeGetTime多媒体计时器
  26.         m_lNextTime = timeGetTime ();  
  27.  
  28. //当然这时高精计频器也没什么作用了,干脆让乘数0.001(相当于m_lPerfCounter取1000)
  29.         m_dTimeScale = 0.001;
  30.     } 
  31.  
  32. //保存为当前帧的时间(这里其实用计数表示时间),相当于初始化了,接下来它们俩将交替出现
  33.     m_lLastTime = m_lNextTime; 
  34.  
  35.  
  36.  
  37. //******开始了!无限循环,除非出错或退出程序
  38.     while ( TRUE ) {  
  39. //这里我理解为,当有消息发过来,(譬如你最小化啦遮蔽了窗口啦中断(INT)等等,
  40. //就先暂停计时器运算,停继续渲染,先处理所有消息,这个延伸起来很复杂,先放放.
  41. //注意,没有消息来时,PeekMessage会返回一个空值到应用程序,GetMessage会在此时让应用程序休眠。
  42.         if ( ::PeekMessage ( &msg, NULL, 0, 0, PM_NOREMOVE ) ) { 
  43.             do        //如果消息渠里还有消息
  44.             {
  45.                 if ( !PumpMessage () )
  46.                     return ExitInstance ();
  47.             } 
  48.             while ( ::PeekMessage ( &msg, NULL, 0, 0, PM_NOREMOVE ) );
  49.         } else {   //没消息要处理了,就继续:
  50.  
  51. //这个摆明了,如果用QueryPerformanceCounter 不行,就用timeGetTime
  52.             if ( m_bPerFlag ) {
  53.     QueryPerformanceCounter ( ( LARGE_INTEGER * ) &m_lCurTime );    
  54.             } 
  55.             else {
  56.     m_lCurTime=timeGetTime (); 
  57.             }
  58.  
  59. //如果当前计数(代表着时间)到达先前设定的m_lNextTime,譬如从帧1来到帧2 :
  60.             if ( m_lCurTime > m_lNextTime ) { 
  61.  
  62. // 计算当前时间(刚到达帧2的时间)与刚才帧1开始时间之差,由计数转换为时间表示
  63.     m_dTimeElapsed = ( m_lCurTime - m_lLastTime )  *  m_dTimeScale;
  64.                 
  65. // 现在当前帧(帧2)就成了名义上的"上一帧"了,接下来会变成帧2到帧3
  66.                 m_lLastTime = m_lCurTime;
  67.                 
  68. //这个m_bAppIsActive在CMainframe类里会经常见到,只有它是TRUE时才渲染
  69. //也就是初始化呀屏幕像素设置呀搞好后才开始渲染
  70.                 if ( !pFrame->m_bAppIsActive )
  71.                     WaitMessage();
  72.                 else
  73.                     pFrame->RenderGLScene ();
  74.  
  75. //计算下一帧触发时的计数,即把这个当前计数与刚才高精出来的"每帧的频数(计数)"相加
  76.                 m_lNextTime = m_lCurTime + m_dwTimeCount; 
  77.  
  78.             } // 对应if(m_lCurTime > m_lNextTime)那里
  79.  
  80.         } // 对应"没消息来了"的那个else
  81.  
  82.     } // end while
  83.     return msg.wParam;//将返回代码依次返回调用层(退出)
  84.  

好了,接下来的日志就是剖CMainframe了!请看:
ZwqXin:自剖一下自己用的NEHE OpenGL框架(中篇)
呃?怎么那么像其实是在讲MFC? 恩,赶忙加回个MFC的Tag先!

本文来源于 ZwqXin (http://www.zwqxin.com/), 转载请注明
      原文地址:http://www.zwqxin.com/archives/MFC/nehe-frame-vc6.html

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

IE下本页面显示有问题?

→点击地址栏右侧【兼容视图】←

日历

Search

网站分类

最新评论及回复

最近发表

Powered By Z-Blog 1.8 Walle Build 100427

Copyright 2008-2013 ZwqXin. All Rights Reserved. Theme edited from ipati.