基于MFC多文档多视图结构的OGRE指北针程序
0.前沿
作者: 化凡
QQ: 371691096
Mail:[email protected]
1. 功能描述
本程序作为OGRE和MFC结合,欲实现以下几个功能:
(1) MFC是多文档多视图结构;
(2) 每打开一个子窗口,就显示一个不同的场景,可以在不同子窗口场景中任意添加几何体,可以扩充为模型浏览器和场景编辑器;
(3) 每个子窗口包含一个主场景,用于显示模型;包含一个指北针场景,专门用于显示场景坐标系,固定在窗口左下角显示。
(4) 可以任意打开、关闭一个子窗口。
(5) 实现效果图:
2. 关键技术点
(1) MFC多文档多视图框架(不是本文重点);
(2) OGRE的Root对象定义时机和位置;
(3) SceneManager、Camera、Viewport的动态添加和释放;
(4) 多个视口的背景融合;
(5) 深度缓存控制(扩展到颜色缓存、模板缓存的控制)
3. 实现步骤
3.1 MFC多文档多视图实现
a) 新建一个MFC多文档工程;
b) 新建三个类CMyDoc : public CDocument、CMyView : public CView、CMyChildFrame : public CMDIChildWnd;
c) 添加菜单资源IDR_MENU_MY、如果每个子窗口需要工具栏,则添加工具栏IDR_TOOLBAR_MY;
d) 在CXXApp里添加变量:
CMultiDocTemplate* pDocTemplate; //管理文档一
CMultiDocTemplate* pMyDocTemplate; //管理文档二
e) 后台CXXApp::InitInstance()里添加类似这样的代码:
//文档一
pDocTemplate = new CMultiDocTemplate(IDR OgreMDITYPE,/*工程资源ID*/
RUNTIME_CLASS(COgreMDIDoc),
RUNTIME_CLASS(CChildFrame), // custom MDI child frame
RUNTIME_CLASS(COgreMDIView));
//文档二
pMyDocTemplate= new CMultiDocTemplate(IDR_MENU_MY, /*新加文档的菜单ID*/
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMyChildFrame), // custom MDI child frame
RUNTIME_CLASS(CMyView));
注意:包含对应的头文件!
f) 每个子窗口添加自己的工具栏,类似下面代码:
int CMyChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_TOOLBAR_MY))
{
return -1; // fail to create
}
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
----------------到现在运行程序,就可以看到MFC多文档多视图的效果了-------------------------
3.2 OGRE的Root对象定义时机和位置
参考OGRE提供的ExampleApplication例子,自己加以改写封装,需要注意的是,整个应用程序只需要一个Root对象,只需要一次初始化,然后可以动态地添加和删除SceneManager、Camera、Viewport等对象。具体类设计参考程序源码。
本文封装了一个OgreDemo类,然后定义一个全局对象:
OgreDemo app;
Root *mRoot;//方便引用Root对象
3.3 SceneManager、Camera、Viewport的动态添加和释放
每次需要动态添加和释放的信息单独设计了一个类,叫class MDINode,动态添加操作通过函数MDINode * OgreDemo::addMDI(HWND hWnd,int w,int h,HWND hMainWnd,String wName)来实现,具体看代码。大致思路是:
(a) 第一次打开子窗口,需要对OGRE做整体初始化,然后新建MDINode节点;
(b) 以后每次开打子窗口,直接新建MDINode节点即可;
(c) MDINode节点释放,在对应的CMyView析构函数里;
(d) 每次添加节点主要包含“基本场景的创建及系列设置”和“坐标系场景的创建及系列设置”;
3.4多个视口的背景融合
对每个子窗口,含有两套SceneManager、两个Camera和两个Viewport,但是这些信息都显示在一个mWindow里。如果不经过特殊设置,后面的场景会遮挡前面的场景,本文中,坐标系场景会遮挡住前面的场景。这是需要解决一个问题:多视口背景融合。
只要理解三维图形渲染流程,这个问题很容易解决。直观的想法是,在渲染后面场景的时候,不清空颜色缓存,那么后续渲染直接写到颜色缓存,那么背景就会存在了,从而达到融合的效果。因此,在定义第二个视口的时候,加上这句代码,即可实现视口背景融合问题:
vp->setClearEveryFrame(false);//这个设置很重要,实现和帧缓存混合
3.5深度缓存控制(扩展到颜色缓存、模板缓存的控制)
视口背景融合问题结局了,似乎任务已经完成了,但是添加物体后,把物体移动到坐标系附近,发现坐标系被遮挡住了。这肯定不是我们想要的效果。耐心分析一下不难想出解决方法:如果在绘制第二个场景的时候,把深度测试关掉,不就可以了吗?!答案确实是这样的。看似简单的问题,但操作起来却无从下手。
由于OGRE做了封装,整个渲染流程被隐藏起来了,真要自己控制,还真不简单。于是,我从网上下载了OGRE的元代码,仔细分析几个重要类的源代码,尝试过用RenderSystem对象进行设置,但是失败了,后来把焦点放在SceneManager的_setPass函数,里面有段代码比较符合我的意思mDestRenderSystem->_setDepthBufferCheckEnabled(pass->getDepthCheckEnabled()),可以看出这个好像是对深度缓存就行操作,通过实验确实是这样的。
那么为什么会这样呢?这段代码明明是关于材质方面设置的,怎么会影响到深度缓存控制呢!对,我也有这样的疑问。但是事实就是这样的。我的猜测解释是:OGRE会给每个模型施加材质,如果你没有显示给一个模型添加纹理,OGRE会用模型纹理帮你添加上,但是你又看不出来,可能OGRE为了统一管理吧。然后关于深度缓存、颜色缓存等方面的控制,主要是通过纹理通道控制的,所以,必须通过设置材质通道的深度缓存控制属性,来控制深度缓存的读写。由于坐标系没有显示使用纹理,因此为了禁用深度测试,必须设置默认材质的通道属性,代码如下:
MaterialPtr mDefault = MaterialManager::getSingleton().getDefaultSettings();
Pass *dPass=mDefault->getTechnique(0)->getPass(0);
dPass->setDepthCheckEnabled(false);
4. 总结
到此,整个程序的框架和关键点都介绍完毕,具体实现可参考代码。需要说明的是,用OGRE引擎开发东西,需要首先对OGRE的机制有点了解,对三维图形的渲染流程和基本原理有所掌握,这样在遇到实际问题时,才可以比较容易地解决。再者,下载一套OGRE的代码,遇到问题时,分析它的源代码,也有助于解决问题。