来源:http://www.cnblogs.com/fence/archive/2010/03/12/1683918.html
友情提醒:所谓的框架是指SDK目录下\Samples\C++\Common路径下的DXUT系列函数包装。学习框架的前提是必须有足够的 Windows API,GUI编程经验,必须熟悉Windows的消息机制,回调机制,最好有万行左右的C/C++编程经验。MFC在这里没有任何用处。另外我觉得最好 在看程序之前对于D3D的所有概念有点了解,什么是vertex,texture,matrix,lighting,mesh等等,以及相关的数学概念。 这些都可以在网上找到中文翻译,帮助你快速入门。
DXSDK2006和2003版的比起来更新了不少东西,比如DirectX10,还有Managed
DirectX等等。不过我关心的还是D3D9。除了个别接口的更改之外,DXSDK2006还提供了一套图形控件的类库,它的界面还是很漂亮
的:)如图:
学习一个框架还是从它的入口学习比较方便,否则容易迷失在无穷无尽的API和层层包装之中。DXSDK2006的框架和2003版的DX9.0c框架有
很大的不同。首先是2003版的框架中提供了一个CD3DApplication类,这个类对于初始化,清除,以及游戏窗口的创建,游戏主循环进行了包
装。这是一个不错的类,不知道为什么在2006版中去掉了。不过不要紧,2006版的框架中提供的一些C包装函数已经足够了。在看这些函数之前,我们还是
先来看看SDK目录下\Samples\C++
\Direct3D\Tutorials中有些什么吧。Tut01_CreateDevice是创建框架,这个程序不用框架,研究一下有助于了解D3D的
大致工作流程。下面是winmain函数中的一部分。
// Initialize Direct3D
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
// Show the window
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
// Enter the message loop
MSGmsg;
while( GetMessage( &msg, NULL, 0, 0 ) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
}
在消息循环之前有个初始化设备的函数InitD3D( hWnd ),其代码如下:
HRESULTInitD3D( HWNDhWnd )
{
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
returnE_FAIL;
D3DPRESENT_PARAMETERSd3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp,
&g_pd3dDevice ) ) )
{
returnE_FAIL;
}
returnS_OK;
}
主要是调用Direct3DCreate9和g_pD3D->CreateDevice这两个函数。查看DXSDK文档中关于D3DPRESENT_PARAMETERS的定义,大致了解一下。
接下来要关心的就是
消息循环了,在回调函数MsgProc中
处理了两个消息,一个是WM_DESTROY,
里面调用了Cleanup函数,另一个是WM_PAINT函数,里面调用了Render函数。Cleanup函数很简单,就是调用D3D对象及其设备对象
的Release函数释放资源,而Render函数就是D3D中最重要的函数了。
VOID Render()
{
if( NULL==g_pd3dDevice)
return;
// Clear the backbuffer to a blue color
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
// Begin the scene
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
// Rendering of scene objects can happen here
// End the scene
g_pd3dDevice->EndScene();
}
// Present the backbuffer contents to the display
g_pd3dDevice->Present( NULL, NULL, NULL, NULL);
}
主要调用的函数有BeginScene, EndScene和Present函数。
对D3D应用程序有了大概了解之后就可以看空框架程序了。这个程序可以在Samples\C++
\Direct3D\EmptyProject中找到。
从WinMain中的调用可以看到,框架首先设定一堆回调函数,很多事情的是在用户自己写的回调函数中实现。从DXUTInit开始,程序开始调用框架
内的API来完成初始化——创建窗口——创建设备——主消息循环——退出等一系列操作。调查Common目录下DXUT.cpp文件就可以发现
DXUTInit函数干了以下几件事情
① 设定开始调用
这个函数的标志符
② InitCommonControls
③ 保存当前的
sticky/toggle/filter键
④ 通过事先导入
winmm.dll的方法timeBeginPeriod来确保调用Sleep的准确性
⑤ 设定一些标志
附,读取命令行参数
⑥ 检查版本
⑦ 获
得D3D对象指针。值得一提的是框架中大部分全局变量是通过类DXUTState的静态变量state的get/set方法得到的。这些get/set方
法是用宏定义的,里面调用了加锁和解锁,因此保证了全局变量设定的线程安全。这些全局性的变量包括D3D对象指针,D3D设备对象指针,
BackBufferSurfaceDesc,DeviceCaps,窗口HINSTANCE,窗口句柄HWND,焦点句柄HWNDFocus,全屏设备
句柄,窗口设备句柄,窗口客户端矩形,模式切换时窗口客户端矩形,模式切换时全屏客户端矩形,Time,ElapsedTime,FPS数,窗口标题,设
备数据DeviceStats,以及是否暂停渲染,时间是否暂停,窗口是否激活等标志,一些窗口事件等等。这些都可以通过
DXUTGETXXX/DXUTSETXXX/DXUTISXXX系列包装函数获得。
⑧ 通过
DXUT_Dynamic_Direct3DCreate9创建D3D对象。很多D3D底层API都是通过动态的方式加载的,这样有利于效率的提高。
⑨ 重设全局时钟
⑩ 设
定DXUTInited为true。很多DXUT系列的函数都喜欢在入口设定一个开始调这个函数的标志,在出口设定一个这个函数已经被调过的标志,这样可
以在以后再次调用这个函数的时候了解当前什么工作已经做了,什么工作没做需要补做。我想这个主要是用来防止函数重入问题的吧。其他函数中的这一对函数就不
再提了
呼~第一个函数大致看完了,接下来是DXUTCreateWindow函数。什么?要问DXUTSetCursorSettings为什么
被无视?因为这个函数不重要。DXUTCreateWindow的工作大致是这样的
① 判断关于设备
的CallBack有没有设定好
② 判断
DXUTInit()有没有被调用成功(注意不是有没有调用)。
③ 获得焦点句
柄,因为窗口还没有创建,所以这个句柄应该是NULL
④ 设定
HInstance
⑤ 设定窗口类
⑥ 注册窗口类
⑦ 设定窗口位置
和大小。好长一段代码,汗
⑧ 创建窗口。终
于。。。
⑨ 设定窗口焦点
句柄,全屏设备句柄,窗口设备句柄
接下来的函数是DXUTCreateDevice。这个函数就是用来选择最优设备并创建的。
① 设定参数中的
回调函数和上下文,以备后用
② 检查窗口是否
被成功创建,否则再调用一次DXUTCreateWindow
③ 枚举所有可能
的显示模式。枚举过程非常复杂,用到了CD3DEnumeration中的一些包装函数,这些设备信息包括分辨率,颜色位深等等。这里会用到
DXUTCreateDevice传进来的参数IsDeviceAcceptable
④ 如果命令行设
定过显示模式,那么将刚才得到的信息覆盖。
⑤ 采用某种权重
的算法找出最优显示模式(DXUTFindValidDeviceSettings)
⑥ 切
换设备。这里用到了DXUTCreateDevice传进来的参数ModifyDeviceSettings。切换设备时要考虑很多问题:比如需要暂时忽
略WM_SIZE消息;只有在第一次创建设备的时候才用命令行参数;按照需要调用DXUTCreate3DEnvironment和
DXUTReset3DEnvironment;分全屏和窗口设备重设;重设完了根据需要处理WM_SIZE消息;显示窗口,允许WM_SIZE消息等等
最后是DXUTMainLoop。
① 检查是否有重
入问题
② 设定进入主循
环标志
③ 检查设备是否
已经被成功创建,没创建的话用默认参数创建一次
④ 检查前面三个
函数是否成功调用。汗,又是检查
⑤ 处理窗口消
息,注意只有在没有消息处理的时候才调用DXUTRender3DEnvironment()
⑥ 在消息循环退
出之后清除加速表。应该是类似SHIFT+X这种键盘加速表的清除吧
⑦ 更改主循环标
志
还是有必要看一下主消息循环中的DXUTRender3DEnvironment
① 检查设备是否
丢失
② 在窗口模式下
检查桌面分辨率位深设定,以便重设设备
③ 尝试重设设备
DXUTReset3DEnvironment
④ 判断上次渲染
到现在时间(elapsed time)决定是否要进行渲染
⑤ 调用用户的
FrameMove函数
⑥ 调用用户的
FrameRender函数
⑦ 调用
Present函数
⑧ 更新当前
Frame
⑨ 根据命令行检
查是否需要关闭应用程序
主函数看完之后,剩下的就是一些回调函数了。要正确使用这些回调函数,除了知道它们的作用之外,还需要知道这些函数是何时被调用的。下面是
调用顺序
- 程序启动:InitApp →MsgProc →IsDeviceAcceptable →ModifyDeviceSettings → OnCreateDevice →OnResetDevice → 渲染主循环
- 渲染主循环:OnFrameMove → OnFrameRender
- 改变设备:ModifyDeviceSettings → OnLostDevice →根据需要调用OnDestroyDevice → OnResetDevice → 渲染主循环
- 程序退出:OnLostDevice →OnDestroyDevice
下面是各函数的作用:
InitApp | 初始化一些图形控件和GUI的消息处理函数 |
OnCreateDevice | 创建设备时的回调函数,用于创建D3DPOOL_MANAGED资源 |
OnResetDevice | 重设设备时的回调函数,用于创建D3DPOOL_DEFAULT资源 |
OnFrameMove | 动画实现处,常用于矩阵转换等操作 |
OnFrameRender | 渲染实现处,常用于渲染场景 |
OnLostDevice | 设备丢失时的回调函数,释放由OnResetDevice创建的资源 |
OnDestroyDevice | 设备析构时的回调函数,释放由OnCreateDevice创建的资源 |
IsDeviceAcceptable | 创建设备时用来对所有可用设备进行过滤的函数 |
ModifyDeviceSettings | 更改设备时的回调函数,用于实现更改设备时所需做的其他操作 |
MsgProc | 安排各空件处理消息的顺序 |
OnGUIEvent | 程序控件绑定的消息处理回调函数 |
以上函数均可以更换名字,这里只是用框架默认的函数名字。