轉載請注明出處:http://www.cnblogs.com/Ray1024
一、概述
在此系列最開始的文章Direct3D11學習:(一)開發環境配置中,我們運行了一個例子BoxDemo,看過這個例子源碼的朋友都會發現,代碼量比較大,但是Win32窗口初始化和Direct3D11初始化工作占用了很多大一部分代碼,然而,我們真正關心的繪制代碼並不是這些。
為了避免以后每次創建演示程序都需要重復的初始化工作,把我們的注意力集中在演示程序度所要表達的特定細節上,我們把重復的初始化代碼封裝到一個簡單的程序框架D3D11App中,位於D3D11App.h和D3D11App.cpp文件中。D3D11App.h和D3D11App.cpp文件包含了窗口初始化和D3D11初始化的核心代碼,可以在新的演示程序中包含它,導入該框架,直接編寫我們的核心代碼就可以了。
二、演示程序框架
2.1 D3D11Util.h文件
在介紹演示程序框架之前,我們需要介紹一組文件D3D11Util.h和D3D11Util.cpp。
這組文件包含了一些有用的工具代碼,都是程序中常用的工具。比如COM對象安全釋放的宏ReleaseCOM()、HRESULT值的錯誤處理宏HR()和常用顏色值定義等等。
這里解釋一下HRESULT值的錯誤處理宏HR():
#ifndef HR #define HR(x) \ { \ HRESULT hr = (x); \ if(FAILED(hr)) \ { \ DXTrace(__FILE__, (DWORD)__LINE__, hr, L#x, true); \ } \ } #endif
在這個宏中,使用了DX的錯誤處理庫dxerr.lib庫中的函數DXTrace。當函數的返回值表明調用失敗時,我們把返回值傳遞給DXTrace函數。如果hr=S_FALSE時,彈出消息框顯示錯誤信息。
HR()宏使用方法:
HR(m_pD3DDevice->CheckMultisampleQualityLevels( DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality));
D3D11Util.h和D3D11Util.cpp文件中其他的工具在這里就不介紹了,有興趣的朋友可以在文章最后的下載源碼處下載瀏覽源碼。
2.2 核心類D3D11App
D3D11App是我們學習D3D11中所有應用程序類的基類,它提供了用於創建主應用程序窗口、運行應用程序消息循環、處理窗口消息和初始化D3D11的函數。另外,這個類還定義了一些框架函數。所有的D3D11應用程序類都繼承於D3D11App類,重載它的virtual框架函數,並創建一個D3D11App派生類的單例對象。D3D11App類的定義如下:
class D3D11App { public: D3D11App(HINSTANCE hInstance); virtual ~D3D11App(); // 獲取應用程序實例句柄 HINSTANCE AppInst()const; // 獲取主窗口句柄 HWND MainWnd()const; // 后台緩存區的長寬比 float AspectRatio()const; // 應用程序消息循環 int Run(); // 框架方法 // 派生類需要重載這些方法實現所需的功能 virtual bool Init(); virtual void OnResize(); virtual void UpdateScene(float dt)=0; virtual void DrawScene()=0; virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); // 處理鼠標輸入事件的便捷重載函數 virtual void OnMouseDown(WPARAM btnState, int x, int y){ } virtual void OnMouseUp(WPARAM btnState, int x, int y) { } virtual void OnMouseMove(WPARAM btnState, int x, int y){ } protected: // 創建窗口 bool InitMainWindow(); // 初始化D3D bool InitDirect3D(); // 計算幀率 void CalculateFrameStats(); protected: HINSTANCE m_hAppInst; // 應用程序實例句柄 HWND m_hMainWnd; // 主窗口句柄 bool m_appPaused; // 程序是否處在暫停狀態 bool m_minimized; // 程序是否最小化 bool m_maximized; // 程序是否最大化 bool m_Resizing; // 程序是否處在改變大小的狀態 UINT m_4xMsaaQuality; // 4X MSAA質量等級 GameTimer m_timer; // 用於記錄deltatime和游戲時間 ID3D11Device* m_pD3DDevice; // D3D11設備 ID3D11DeviceContext* m_pD3DImmediateContext; // 上下文 IDXGISwapChain* m_pSwapChain; // 交換鏈 ID3D11Texture2D* m_pDepthStencilBuffer; // 深度緩沖區 ID3D11RenderTargetView* m_pRenderTargetView; // 渲染目標視圖 ID3D11DepthStencilView* m_pDepthStencilView; // 深度緩沖視圖 D3D11_VIEWPORT m_screenViewport; // 視口 std::wstring m_mainWndCaption; // 窗口標題 D3D_DRIVER_TYPE m_D3DDriverType; // 是否使用硬件加速 int m_clientWidth; // 窗口大小 int m_clientHeight; // 窗口大小 bool m_enable4xMsaa; // 是否使用4XMSAA };
上面的代碼中有注釋,從注釋中我們可以看到成員函數和變量的作用。
下面的部分我們將詳細介紹D3D11App一部分成員函數。
2.3 D3D11App部分成員函數介紹
在這節中我們主要介紹D3D11App類的成員函數中的框架方法。所謂框架方法,就是D3D11App中的虛函數,在之后學習的每個演示程序中,我們可以重載這些方法來實現特定示例中的代碼細節。D3D11App類實現的這種結構可以將所有的初始化代碼、消息處理代碼和其他代碼安排得井井有條,使派生類專注於實現演示程序的特定代碼。下面是對這些框架方法的描述:
(1)Init:該方法包含應用程序的初始化代碼,比如分配資源、初始化對象和設置燈光。該方法在D3D11App的實現中包含InitMainWindow和InitDirect3D方法的調用語句;所以,當在派生類中重載該方法時,應首先調用該方法的D3D11App版本,就像下面這樣:
void TestApp::Init() { if(!D3D11App::Init()) return false; /* 剩下的初始化代碼從這里開始 */ }
(2) OnResize:該方法在D3D11App::MsgProc收到WM_SIZE消息時調用。當窗口的尺寸改變時,一些與客戶區大小相關的Direct3D屬性也需要改變。尤其是需要重新創建后台緩沖區和深度/模板緩沖區,使它們與窗口客戶區的大小一致。后台緩沖區的大小可以通過調用IDXGISwapChain::ResizeBuffers方法來進行調整。而深度/模板緩沖區必須被銷毀,然后根據新的大小重新創建。另外,渲染目標視圖和深度/模板視圖也必須重新創建。OnResize方法在D3D11App的實現中包含了調整后台緩沖區和深度/模板緩沖區的代碼;詳情請直接參見源代碼。除緩沖區外,依賴於客戶區大小的其他屬性(例如,投影矩陣)也必須重新創建。我們把該方法作為框架的一部分是因為當窗口大小改變時,客戶代碼可能需要執行一些它自己的邏輯。
(3)UpdateScene:該抽象方法每幀都會調用,用於隨着時間更新3D應用程序(例如,實現動畫和碰撞檢測、檢查用戶輸入、計算每秒幀數等等)。
(4)DrawScene:該抽象方法每幀都會調用,用於將3D場景的當前幀繪制到后台緩沖區。當繪制當前幀時,我們調用了IDXGISwapChain::Present方法將后台緩沖區的內容呈現在屏幕上。
(5)MsgProc:該方法是主應用程序窗口的消息處理函數。通常,當你只需重載該方法,就可以處理未由D3D11App::MsgProc處理(或者沒按照你所希望的方式處理)的消息。如果你重載了這個方法,那么那些你沒有處理的消息都會送到D3D11App::MsgProc中進行處理。
另外,除了上述的五個框架方法之外,為了使用起來更方便,我們還提供了三個虛函數,用於處理鼠標點擊、釋放和移動的事件。
virtual void OnMouseDown(WPARAM btnState, int x, int y){ } virtual void OnMouseUp(WPARAM btnState, int x, int y) { } virtual void OnMouseMove(WPARAM btnState, int x, int y){ }
你可以重載這些方法處理鼠標事件,而用不着重載MsgProc方法。這些方法的第一個參數WPARAM都是相同的,保存了鼠標按鍵的狀態(例如,哪個鼠標按鍵被按下),第二、三個參數是光標在客戶區域的(x,y)坐標。
2.4 將程序框架導入新建演示程序
這個框架還需要另外一個部分:游戲計時器,上一篇文章我們介紹過了游戲計時器的實現,這里就不提了。
我們將上面提到的三組文件(D3D11Util.h和D3D11Util.cpp、D3D11App.h和D3D11App.cpp、GameTimer.h和GameTimer.cpp)放到一個文件夾Common中,這樣每個演示程序都可以使用,避免多次復制了。我們的演示程序需要使用程序框架時,需要導入框架,即把Common文件夾中的文件添加到演示程序中,並把Common文件夾的目錄添加到演示程序項目屬性的包含目錄中。
這樣,程序框架就成功地導入到新建演示程序中了,我們接下來就可以使用程序框架編寫演示程序了。
2.5 使用框架編寫演示程序
(1)創建一個繼承自主框架類D3D11App的類TestApp:
class TestApp : public D3D11App { public: TestApp(HINSTANCE hInstance); void UpdateScene(float dt); void DrawScene(); };
(2)添加TestApp類的成員函數實現
// // TestApp Implement // TestApp::TestApp(HINSTANCE hInstance) : D3D11App(hInstance) { m_mainWndCaption = L"2_D3DTimingAndAnimation"; } void TestApp::UpdateScene(float dt) { } void TestApp::DrawScene() { assert(m_pD3DImmediateContext); assert(m_pSwapChain); m_pD3DImmediateContext->ClearRenderTargetView(m_pRenderTargetView, reinterpret_cast<const float*>(&Colors::LightSteelBlue)); m_pD3DImmediateContext->ClearDepthStencilView(m_pDepthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0); HR(m_pSwapChain->Present(0, 0)); }
(3)添加程序入口
// 程序入口 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR cmdLine, int showCmd) { #if defined(DEBUG) | defined(_DEBUG) _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); #endif TestApp theApp(hInstance); if( !theApp.Init() ) return 0; return theApp.Run(); }
到此為止,我們已經利用程序框架成功地生成了一個演示程序。
有興趣看源碼的朋友可以點擊這里下載,源碼為3_D3DFrame文件夾。
三、結語
基本上,我們用不着做任何實際工作就可以實現這個程序,因為基類D3D11App已經實現了它所需要的大部分功能。
有了程序框架之后,我們在學習過程中的演示程序的編寫就簡單多了,直接導入程序框架,只寫我們關心的核心代碼就可以了。