友情提醒:所謂的框架是指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中最重要的函數了。
VOIDRender()
{
??? 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 | 程序控件綁定的消息處理回調函數 |
?
以上函數均可以更換名字,這里只是用框架默認的函數名字。