DirectX11 With Windows SDK--01 DirectX11初始化
前言
由于個人覺得龍書里面第4章提供的Direct3D 初始化項目封裝得比較好,而且DirectX SDK Samples里面的初始化程序過于精簡,不適合后續使用,故選擇了以Init Direct3D項目作為框架,然后還使用了微軟提供的示例項目,兩者結合到一起。建議下載項目配合閱讀。
這一章內容大部分屬于龍書的內容,但仍有一些不同的地方。因為后續的所有項目都使用該基礎框架,你也可以直接使用第一章的項目源碼,然后需要了解以下差異部分:
其中前面兩個部分在下面的鏈接可以看到:
| ComPtr智能指針 |
| HR宏關于dxerr庫的替代方案 |
DirectX11 With Windows SDK完整目錄
Github項目源碼
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
如何開啟新項目
安裝準備
如果你打算從VS2015開始,則在安裝的時候需要勾選下列選項:
編程語言那一項會自動被勾選。
而如果是使用VS2017或19的話,則在安裝的時候需要勾選下列選項:
注意:VS2015使用的Windows SDK版本為10.0.14393.0,而VS2017與VS2019使用的Windows SDK版本為10.0.17763.0
安裝完成后,新建項目需要從空項目開始。
移除你的項目中有關DX SDK的庫路徑和包含路徑
如果你曾經用過DX SDK來編寫DX項目,務必要把你之前配置的DX SDK庫路徑和包含路徑給清理掉,使用項目默認的庫路徑和包含路徑!
鏈接靜態庫
這里的每一個項目都需要包含靜態庫:d3d11.lib,dxgi.lib,dxguid.lib,D3DCompiler.lib和winmm.lib。可以在d3dApp.h添加下面的語句:
#pragma comment(lib, "d3d11.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "dxguid.lib") #pragma comment(lib, "D3DCompiler.lib") #pragma comment(lib, "winmm.lib")也可以在項目屬性-鏈接器-輸入-附加依賴項 添加上面的庫。
字符集設置為Unicode
在項目屬性頁中可以直接進行修改。
Win7系統下的額外配置
由于Win10 SDK中的某些函數在Win7是不支持的,我們還需要在屬性頁-配置屬性-C/C++ -預處理器中,添加預處理器定義以限制API集合:_WIN32_WINNT=0x601
現在最基本的配置已經完成,你可以嘗試將本教程用到的項目01中所有的頭文件和源文件添加進你的項目,然后生成項目并運行以檢測。
項目結構
現在把目光拉回到我們的教程項目。目前項目中包含頭文件的具體功能如下:
| d3dApp.h | Direct3D應用程序框架類 |
| d3dUtil.h | 包含一些常用頭文件及自己編寫的函數 |
| DXTrace.h | 包含了HR宏與DXTraceW函數 |
| GameApp.h | 游戲應用程序擴展類,游戲邏輯在這里實現,繼承自D3DApp類 |
| GameTimer.h | 游戲計時器類 |
其中d3dApp類和GameTimer類是龍書源碼提供的,我們可以搬運過來,但是對d3dApp框架類我們還需要進行大幅度修改,畢竟我們的最終目的就是要完全脫離舊的DirectX SDK,使用Windows SDK來實現DX11。修改完成后,d3dApp就幾乎已經定型而不需要我們操心了。
GameApp類則是我們編寫游戲邏輯的地方,這里需要進行逐幀的更新及繪制。
D3DApp框架類
D3DApp.h展示了框架類的聲明,這里的接口類指針全部換上了ComPtr智能指針:
class D3DApp { public:D3DApp(HINSTANCE hInstance); // 在構造函數的初始化列表應當設置好初始參數virtual ~D3DApp();HINSTANCE AppInst()const; // 獲取應用實例的句柄HWND MainWnd()const; // 獲取主窗口句柄float AspectRatio()const; // 獲取屏幕寬高比int Run(); // 運行程序,進行游戲主循環// 框架方法。客戶派生類需要重載這些方法以實現特定的應用需求virtual bool Init(); // 該父類方法需要初始化窗口和Direct3D部分virtual void OnResize(); // 該父類方法需要在窗口大小變動的時候調用virtual void UpdateScene(float dt) = 0; // 子類需要實現該方法,完成每一幀的更新virtual void DrawScene() = 0; // 子類需要實現該方法,完成每一幀的繪制virtual LRESULT MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);// 窗口的消息回調函數 protected:bool InitMainWindow(); // 窗口初始化bool InitDirect3D(); // Direct3D初始化void CalculateFrameStats(); // 計算每秒幀數并在窗口顯示protected:HINSTANCE m_hAppInst; // 應用實例句柄HWND m_hMainWnd; // 主窗口句柄bool m_AppPaused; // 應用是否暫停bool m_Minimized; // 應用是否最小化bool m_Maximized; // 應用是否最大化bool m_Resizing; // 窗口大小是否變化bool m_Enable4xMsaa; // 是否開啟4倍多重采樣UINT m_4xMsaaQuality; // MSAA支持的質量等級GameTimer m_Timer; // 計時器// 使用模板別名(C++11)簡化類型名template <class T>using ComPtr = Microsoft::WRL::ComPtr<T>;// Direct3D 11ComPtr<ID3D11Device> m_pd3dDevice; // D3D11設備ComPtr<ID3D11DeviceContext> m_pd3dImmediateContext; // D3D11設備上下文ComPtr<IDXGISwapChain> m_pSwapChain; // D3D11交換鏈// Direct3D 11.1ComPtr<ID3D11Device1> m_pd3dDevice1; // D3D11.1設備ComPtr<ID3D11DeviceContext1> m_pd3dImmediateContext1; // D3D11.1設備上下文ComPtr<IDXGISwapChain1> m_pSwapChain1; // D3D11.1交換鏈// 常用資源ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer; // 深度模板緩沖區ComPtr<ID3D11RenderTargetView> m_pRenderTargetView; // 渲染目標視圖ComPtr<ID3D11DepthStencilView> m_pDepthStencilView; // 深度模板視圖D3D11_VIEWPORT m_ScreenViewport; // 視口// 派生類應該在構造函數設置好這些自定義的初始參數std::wstring m_MainWndCaption; // 主窗口標題int m_ClientWidth; // 視口寬度int m_ClientHeight; // 視口高度 };而在d3dApp.cpp中,可以看到有一個全局變量g_pd3dApp:
namespace {// This is just used to forward Windows messages from a global window// procedure to our member function window procedure because we cannot// assign a member function to WNDCLASS::lpfnWndProc.D3DApp* g_pd3dApp = 0; }設置該全局變量是因為在窗口創建的時候需要綁定一個回調函數,受到回調函數指針類型的限制,我們不可以綁定d3dApp::MainWndProc的成員方法,所以還需要實現一個全局函數用于回調函數的綁定:
LRESULT CALLBACK MainWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {// Forward hwnd on because we can get messages (e.g., WM_CREATE)// before CreateWindow returns, and thus before m_hMainWnd is valid.return g_pd3dApp->MsgProc(hwnd, msg, wParam, lParam); }D3DApp::InitWindow和D3DApp::MsgProc方法目前在這里不做過多描述,因為這不是教程的重點部分,但后續可能還要回頭修改這兩個方法。有興趣的可以去MSDN查閱這些函數和結構體的信息。
Direct3D初始化
注意當前項目使用的是d3d11_1.h頭文件
Direct3D初始化階段首先需要創建D3D設備和D3D設備上下文
D3D設備(ID3D11Device)包含了創建各種所需資源的方法,最常用的有:資源類(ID3D11Resource, 包含紋理和緩沖區),視圖類以及著色器。
D3D設備上下文(ID3D11DeviceContext)可以看做是一個渲染管線,負責渲染工作,它需要綁定來自D3D設備創建的各種資源、視圖和著色器才能正常運轉,除此之外,它還能夠負責對資源的直接讀寫操作。
而如果支持Direct3D 11.1的話,則對應的接口類為:ID3D11Device1、ID3D11DeviceContext1,它們分別繼承自上面的兩個接口類,區別在于額外提供了少數新的接口,并且接口方法的實現可能會有所區別。
現在,我們從D3DApp::InitDirect3D方法開始,一步步進行分析。
D3D設備與D3D設備上下文的創建
D3D11CreateDevice函數--創建D3D設備與D3D設備上下文
創建D3D設備、D3D設備上下文使用如下函數:
HRESULT WINAPI D3D11CreateDevice(IDXGIAdapter* pAdapter, // [In_Opt]適配器D3D_DRIVER_TYPE DriverType, // [In]驅動類型HMODULE Software, // [In_Opt]若上面為D3D_DRIVER_TYPE_SOFTWARE則這里需要提供程序模塊UINT Flags, // [In]使用D3D11_CREATE_DEVICE_FLAG枚舉類型D3D_FEATURE_LEVEL* pFeatureLevels, // [In_Opt]若為nullptr則為默認特性等級,否則需要提供特性等級數組UINT FeatureLevels, // [In]特性等級數組的元素數目UINT SDKVersion, // [In]SDK版本,默認D3D11_SDK_VERSIONID3D11Device** ppDevice, // [Out_Opt]輸出D3D設備D3D_FEATURE_LEVEL* pFeatureLevel, // [Out_Opt]輸出當前應用D3D特性等級ID3D11DeviceContext** ppImmediateContext ); //[Out_Opt]輸出D3D設備上下文如果D3D_DRIVER_TYPE_HARDWARE不支持,則需要自己通過循環的形式再檢查D3D_DRIVER_TYPE_WARP是否支持。
關于D3D_DRIVER_TYPE的詳細描述,可以去查閱MSDN官方文檔詳細了解一下。
注意:如果你的系統支持Direct3D 11.1的API,卻把pFeatureLevels設置為nullptr,D3D11CreateDevice將創建出特性等級為D3D_FEATURE_LEVEL_11_0的設備。而如果你的系統不支持Direct3D 11.1的API,D3D11CreateDevice會立即停止特性數組的輪詢并返回E_INVALIDARG。為此,你必須要從D3D_FEATURE_LEVEL_11_0或更低特性等級開始輪詢。
在Win10, Win8.x 或 Win7 SP1且安裝了KB2670838補丁的系統都支持Direct3D 11.1的API,而純Win7系統僅支持Direct3D 11的API
從上面的描述我們可以得知,特性等級和D3D設備的版本并不是互相對應的:
1. 特性等級的支持情況取決于當前使用的顯示適配器
2. D3D設備的版本取決于所處的系統
由于該函數可以創建Direct3D 11.1(或者Direct3D 11.0)的設備與設備上下文,但都統一輸出ID3D11Device和ID3D11DeviceContext。如果想要查看是否支持Direct3D 11.1的API,可以使用下面的方式:
ComPtr<ID3D11Device1> md3dDevice1; HRESULT hr = md3dDevice.As(&md3dDevice1);同理,想要查看是否支持Direct3D 11.2的API,則可以這樣:
ComPtr<ID3D11Device2> md3dDevice2; HRESULT hr = md3dDevice.As(&md3dDevice2);由于每個電腦的顯示卡設備情況有所差異,該教程采用的是默認顯示卡(有可能會用到集成顯卡),而不是指定顯示卡:
HRESULT hr = S_OK;// 創建D3D設備 和 D3D設備上下文 UINT createDeviceFlags = 0; #if defined(DEBUG) || defined(_DEBUG) createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; #endif // 驅動類型數組 D3D_DRIVER_TYPE driverTypes[] = {D3D_DRIVER_TYPE_HARDWARE,D3D_DRIVER_TYPE_WARP,D3D_DRIVER_TYPE_REFERENCE, }; UINT numDriverTypes = ARRAYSIZE(driverTypes);// 特性等級數組 D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_1,D3D_FEATURE_LEVEL_11_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels);D3D_FEATURE_LEVEL featureLevel; D3D_DRIVER_TYPE d3dDriverType; for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++) {d3dDriverType = driverTypes[driverTypeIndex];hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());if (hr == E_INVALIDARG){// Direct3D 11.0 的API不承認D3D_FEATURE_LEVEL_11_1,所以我們需要嘗試特性等級11.0以及以下的版本hr = D3D11CreateDevice(nullptr, d3dDriverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel, m_pd3dImmediateContext.GetAddressOf());}if (SUCCEEDED(hr))break; }if (FAILED(hr)) {MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);return false; }// 檢測是否支持特性等級11.0或11.1 if (featureLevel != D3D_FEATURE_LEVEL_11_0 && featureLevel != D3D_FEATURE_LEVEL_11_1) {MessageBox(0, L"Direct3D Feature Level 11 unsupported.", 0, 0);return false; }// 檢測 MSAA支持的質量等級 md3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality); assert(m_4xMsaaQuality > 0);注意:
DXGI初始化
DXGI交換鏈
DXGI交換鏈(IDXGISwapChain)緩存了一個或多個表面(2D紋理),它們都可以稱作后備緩沖區(backbuffer)。后備緩沖區則是我們主要進行渲染的場所,我們可以將這些緩沖區通過合適的手段成為渲染管線的輸出對象。在進行呈現(Present)的時候有兩種方法:
注意:考慮到要兼容Win7系統,而且由于我們編寫的是Win32應用程序,因此這里使用的是第一種模型。同時這也是絕大多數教程所使用的。對第二種感興趣的可以了解下面的鏈接:
DXGI翻轉模型
接下來我們需要了解D3D與DXGI各版本的對應關系,這十分重要:
| Direct3D 11.1 | DXGI 1.2 | IDXGIFactory2 | IDXGIAdaptor2 | IDXGISwapChain1 |
| Direct3D 11.0/10.1 | DXGI 1.1 | IDXGIFactory1 | IDXGIAdaptor1 | IDXGISwapChain |
| Direct3D 10.0 | DXGI 1.0 | IDXGIFactory | IDXGIAdaptor | IDXGISwapChain |
d3d與dxgi版本的對應關系你可以通過觀察這些d3d頭文件所包含的dxgi頭文件來了解。
DXGI交換鏈的創建需要通過IDXGIFactory::CreateSwapChain方法進行。但是,如果是要創建Direct3D 11.1對應的交換鏈,則需要通過IDXGIFactory2::CreateSwapChainForHwnd方法進行。
獲取IDXGIFactory1或IDXGIFactory2接口類
現在我們需要先拿到包含IDXGIFactory1接口的對象,但是為了拿到該對象還需要經歷一些磨難。
之前在創建D3D設備時使用的是默認的顯卡適配器IDXGIAdapter(對于雙顯卡的筆記本大概率使用的是集成顯卡),而創建出來的D3D設備本身實現了IDXGIDevice接口,通過該對象,我們可以獲取到當前所用的顯卡適配器IDXGIAdapter對象,這樣我們再通過查詢它的父級找到是哪個IDXGIFactory枚舉出來的適配器。
ComPtr<IDXGIDevice> dxgiDevice = nullptr; ComPtr<IDXGIAdapter> dxgiAdapter = nullptr; ComPtr<IDXGIFactory1> dxgiFactory1 = nullptr; // D3D11.0(包含DXGI1.1)的接口類 ComPtr<IDXGIFactory2> dxgiFactory2 = nullptr; // D3D11.1(包含DXGI1.2)特有的接口類// 為了正確創建 DXGI交換鏈,首先我們需要獲取創建 D3D設備 的 DXGI工廠,否則會引發報錯: // "IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory." HR(m_pd3dDevice.As(&dxgiDevice)); HR(dxgiDevice->GetAdapter(dxgiAdapter.GetAddressOf())); HR(dxgiAdapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory1.GetAddressOf())));// 查看該對象是否包含IDXGIFactory2接口 hr = dxgiFactory1.As(&dxgiFactory2); // 如果包含,則說明支持D3D11.1 if (dxgiFactory2 != nullptr) {HR(m_pd3dDevice.As(&m_pd3dDevice1));HR(m_pd3dImmediateContext.As(&m_pd3dImmediateContext1));// ... 省略交換鏈IDXGISwapChain1的創建 } else {// ... 省略交換鏈IDXGISwapChain的創建 }同時之前也提到,如果支持Direct3D 11.1的話,我們就可以拿到DXGI 1.2的相關對象(如IDXGIFactory2)。
這時m_pd3dDevice和m_pd3dDevice1其實都指向同一個對象,m_pd3dImmediateContext和m_pd3dImmediateContext1,m_pSwapChain和m_pSwapChain1也是一樣的,區別僅僅在于后者實現了額外的一些接口,問題不大。因此不管是Direct3D 11.1還是Direct3D 11.0,后續都主要使用m_pd3dDevice,m_pd3dImmediateContext和m_pSwapChain來進行操作。
IDXGIFactory2::CreateSwapChainForHwnd方法--Direct3D 11.1創建交換鏈
如果是Direct3D 11.1的話,需要先填充DXGI_SWAP_CHAIN_DESC1和DXGI_SWAP_CHAIN_FULLSCREEN_DESC這兩個結構體:
typedef struct DXGI_SWAP_CHAIN_DESC1 {UINT Width; // 緩沖區寬度UINT Height; // 緩沖區高度DXGI_FORMAT Format; // 緩沖區數據格式BOOL Stereo; // 忽略 DXGI_SAMPLE_DESC SampleDesc; // 采樣描述DXGI_USAGE BufferUsage; // 緩沖區用途UINT BufferCount; // 緩沖區數目DXGI_SCALING Scaling; // 忽略DXGI_SWAP_EFFECT SwapEffect; // 交換效果DXGI_ALPHA_MODE AlphaMode; // 忽略UINT Flags; // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型 } DXGI_SWAP_CHAIN_DESC1;typedef struct DXGI_SAMPLE_DESC {UINT Count; // MSAA采樣數UINT Quality; // MSAA質量等級 } DXGI_SAMPLE_DESC;typedef struct DXGI_SWAP_CHAIN_FULLSCREEN_DESC {DXGI_RATIONAL RefreshRate; // 刷新率DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 忽略DXGI_MODE_SCALING Scaling; // 忽略BOOL Windowed; // 是否窗口化 } DXGI_SWAP_CHAIN_FULLSCREEN_DESC;typedef struct DXGI_RATIONAL {UINT Numerator; // 刷新率分子UINT Denominator; // 刷新率分母 } DXGI_RATIONAL;填充好后,Direct3D 11.1使用的創建方法為IDXGIFactory2::CreateSwapChainForHwnd:
HRESULT IDXGIFactory2::CreateSwapChainForHwnd(IUnknown *pDevice, // [In]D3D設備HWND hWnd, // [In]窗口句柄const DXGI_SWAP_CHAIN_DESC1 *pDesc, // [In]交換鏈描述1const DXGI_SWAP_CHAIN_FULLSCREEN_DESC *pFullscreenDesc, // [In]交換鏈全屏描述,可選IDXGIOutput *pRestrictToOutput, // [In]忽略IDXGISwapChain1 **ppSwapChain); // [Out]輸出交換鏈對象上面第一個省略的部分代碼如下:
// 填充各種結構體用以描述交換鏈 DXGI_SWAP_CHAIN_DESC1 sd; ZeroMemory(&sd, sizeof(sd)); sd.Width = m_ClientWidth; sd.Height = m_ClientHeight; sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 是否開啟4倍多重采樣? if (m_Enable4xMsaa) {sd.SampleDesc.Count = 4;sd.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {sd.SampleDesc.Count = 1;sd.SampleDesc.Quality = 0; } sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.BufferCount = 1; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = 0;DXGI_SWAP_CHAIN_FULLSCREEN_DESC fd; fd.RefreshRate.Numerator = 60; fd.RefreshRate.Denominator = 1; fd.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; fd.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; fd.Windowed = TRUE; // 為當前窗口創建交換鏈 HR(dxgiFactory2->CreateSwapChainForHwnd(m_pd3dDevice.Get(), m_hMainWnd, &sd, &fd, nullptr, m_pSwapChain1.GetAddressOf())); HR(m_pSwapChain1.As(&m_pSwapChain));后續我們還可以通過該交換鏈來手動指定是否需要全屏
IDXGIFactory::CreateSwapChain方法--Direct3D 11創建交換鏈
如果是Direct3D 11.0的話,需要先填充DXGI_SWAP_CHAIN_DESC結構體:
typedef struct DXGI_SWAP_CHAIN_DESC {DXGI_MODE_DESC BufferDesc; // 緩沖區描述DXGI_SAMPLE_DESC SampleDesc; // 采樣描述DXGI_USAGE BufferUsage; // 緩沖區用途UINT BufferCount; // 后備緩沖區數目HWND OutputWindow; // 輸出窗口句柄BOOL Windowed; // 窗口化?DXGI_SWAP_EFFECT SwapEffect; // 交換效果UINT Flags; // 使用DXGI_SWAP_CHAIN_FLAG枚舉類型 } DXGI_SWAP_CHAIN_DESC;typedef struct DXGI_SAMPLE_DESC {UINT Count; // MSAA采樣數UINT Quality; // MSAA質量等級 } DXGI_SAMPLE_DESC;typedef struct DXGI_MODE_DESC {UINT Width; // 緩沖區寬度UINT Height; // 緩沖區高度DXGI_RATIONAL RefreshRate; // 刷新率分數表示法DXGI_FORMAT Format; // 緩沖區數據格式DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // 忽略DXGI_MODE_SCALING Scaling; // 忽略 } DXGI_MODE_DESC;typedef struct DXGI_RATIONAL {UINT Numerator; // 刷新率分子UINT Denominator; // 刷新率分母 } DXGI_RATIONAL;Direct3D 11.0下使用的創建方法為IDXGIFactory::CreateSwapChain:
HRESULT IDXGIFactory::CreateSwapChain(IUnknown *pDevice, // [In]D3D設備DXGI_SWAP_CHAIN_DESC *pDesc, // [In]交換鏈描述IDXGISwapChain **ppSwapChain); // [Out]輸出交換鏈對象第二個省略的部分代碼如下:
// 填充DXGI_SWAP_CHAIN_DESC用以描述交換鏈 DXGI_SWAP_CHAIN_DESC sd; ZeroMemory(&sd, sizeof(sd)); sd.BufferDesc.Width = m_ClientWidth; sd.BufferDesc.Height = m_ClientHeight; sd.BufferDesc.RefreshRate.Numerator = 60; sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 是否開啟4倍多重采樣? if (m_Enable4xMsaa) {sd.SampleDesc.Count = 4;sd.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {sd.SampleDesc.Count = 1;sd.SampleDesc.Quality = 0; } sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.BufferCount = 1; sd.OutputWindow = m_hMainWnd; sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; sd.Flags = 0; HR(dxgiFactory1->CreateSwapChain(m_pd3dDevice.Get(), &sd, m_pSwapChain.GetAddressOf()));禁用ALT+ENTER與全屏的關聯
默認情況下按ALT+ENTER可以切換成全屏,如果不想要這種操作,可以使用剛才創建的dxgiFactory1,按照下面的方式來調用即可:
dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);這樣DXGI就不會監聽Windows消息隊列,并且屏蔽掉了對接收到ALT+ENTER消息的處理。
DXGI交換鏈與Direct3D設備的交互
在創建好上述對象后,如果窗口的大小是固定的,則需要經歷下面的步驟:
接下來需要快速了解一遍上述步驟所需要用到的API。
獲取交換鏈的后備緩沖區
由于此前我們創建好的交換鏈已經包含1個后備緩沖區了,我們可以通過IDXGISwapChain::GetBuffer方法直接獲取后備緩沖區的ID3D11Texture2D接口:
HRESULT IDXGISwapChain::GetBuffer( UINT Buffer, // [In]緩沖區索引號,從0到BufferCount - 1REFIID riid, // [In]緩沖區的接口類型IDvoid **ppSurface); // [Out]獲取到的緩沖區為后備緩沖區創建渲染目標視圖
渲染目標視圖用于將渲染管線的運行結果輸出給其綁定的資源,很明顯它也只能夠設置給輸出合并階段。渲染目標視圖要求其綁定的資源是允許GPU讀寫的,因為在作為管線輸出時會通過GPU寫入數據,并且在以后進行混合操作時還需要在GPU讀取該資源。通常渲染目標是一個二維的紋理,但它依舊可能會綁定其余類型的資源。這里不做討論。
現在我們需要將后備緩沖區綁定到渲染目標視圖,使用ID3D11Device::CreateRenderTargetView方法來創建:
HRESULT ID3D11Device::CreateRenderTargetView( ID3D11Resource *pResource, // [In]待綁定到渲染目標視圖的資源const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, // [In]忽略ID3D11RenderTargetView **ppRTView); // [Out]獲取渲染目標視圖現在這里演示了獲取后備緩沖區紋理,并綁定到渲染目標視圖的過程:
// 重設交換鏈并且重新創建渲染目標視圖 ComPtr<ID3D11Texture2D> backBuffer; HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf()))); HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));創建深度/模板緩沖區
ID3D11Device::CreateTexture2D--創建一個2D紋理
除了渲染目標視圖外,我們還需要創建深度/模板緩沖區用于深度測試。深度/模板緩沖區也是一個2D紋理,要求其寬度和高度必須要和窗口寬高保持一致。
通過D3D設備可以新建一個2D紋理,但在此之前我們需要先描述該緩沖區的信息:
typedef struct D3D11_TEXTURE2D_DESC {UINT Width; // 緩沖區寬度UINT Height; // 緩沖區高度UINT MipLevels; // Mip等級UINT ArraySize; // 紋理數組中的紋理數量,默認1DXGI_FORMAT Format; // 緩沖區數據格式DXGI_SAMPLE_DESC SampleDesc; // MSAA采樣描述D3D11_USAGE Usage; // 數據的CPU/GPU訪問權限UINT BindFlags; // 使用D3D11_BIND_FLAG枚舉來決定該數據的使用類型UINT CPUAccessFlags; // 使用D3D11_CPU_ACCESS_FLAG枚舉來決定CPU訪問權限UINT MiscFlags; // 使用D3D11_RESOURCE_MISC_FLAG枚舉,這里默認0 } D3D11_TEXTURE2D_DESC;由于要填充的內容很多,并且目前只有在初始化環節才用到,因此這部分代碼可以先粗略看一下,在后續的章節還會詳細講到。
填充好后,這時我們就可以用方法ID3D11Device::CreateTexture2D來創建2D紋理:
HRESULT ID3D11Device::CreateTexture2D( const D3D11_TEXTURE2D_DESC *pDesc, // [In] 2D紋理描述信息const D3D11_SUBRESOURCE_DATA *pInitialData, // [In] 用于初始化的資源ID3D11Texture2D **ppTexture2D); // [Out] 獲取到的2D紋理下面的代碼是關于深度/模板緩沖區創建的完整過程:
D3D11_TEXTURE2D_DESC depthStencilDesc;depthStencilDesc.Width = mClientWidth; depthStencilDesc.Height = mClientHeight; depthStencilDesc.MipLevels = 1; depthStencilDesc.ArraySize = 1; depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;// 要使用 4X MSAA? if (mEnable4xMsaa) {depthStencilDesc.SampleDesc.Count = 4;depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1; } else {depthStencilDesc.SampleDesc.Count = 1;depthStencilDesc.SampleDesc.Quality = 0; }depthStencilDesc.Usage = D3D11_USAGE_DEFAULT; depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthStencilDesc.CPUAccessFlags = 0; depthStencilDesc.MiscFlags = 0;HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));創建深度/模板視圖
有了深度/模板緩沖區后,就可以通過ID3D11Device::CreateDepthStencilView方法將創建好的2D紋理綁定到新建的深度/模板視圖:
HRESULT ID3D11Device::CreateDepthStencilView( ID3D11Resource *pResource, // [In] 需要綁定的資源const D3D11_DEPTH_STENCIL_VIEW_DESC *pDesc, // [In] 深度緩沖區描述,這里忽略ID3D11DepthStencilView **ppDepthStencilView); // [Out] 獲取到的深度/模板視圖演示如下:
HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));為渲染管線的輸出合并階段設置渲染目標
ID3D11DeviceContext::OMSetRenderTargets方法要求同時提供渲染目標視圖和深度/模板視圖,不過這時我們都已經準備好了:
void ID3D11DeviceContext::OMSetRenderTargets( UINT NumViews, // [In] 視圖數目ID3D11RenderTargetView *const *ppRenderTargetViews, // [In] 渲染目標視圖數組ID3D11DepthStencilView *pDepthStencilView) = 0; // [In] 深度/模板視圖因此這里同樣也是一句話的事情:
m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());視口設置
最終我們還需要決定將整個視圖輸出到窗口特定的范圍。我們需要使用D3D11_VIEWPORT來設置視口
typedef struct D3D11_VIEWPORT {FLOAT TopLeftX; // 屏幕左上角起始位置XFLOAT TopLeftY; // 屏幕左上角起始位置YFLOAT Width; // 寬度FLOAT Height; // 高度FLOAT MinDepth; // 最小深度,必須為0.0fFLOAT MaxDepth; // 最大深度,必須為1.0f } D3D11_VIEWPORT;ID3D11DeviceContext::RSSetViewports方法將設置1個或多個視口:
void ID3D11DeviceContext::RSSetViewports(UINT NumViewports, // 視口數目const D3D11_VIEWPORT *pViewports); // 視口數組將視圖輸出到整個屏幕需要按下面的方式進行填充:
m_ScreenViewport.TopLeftX = 0; m_ScreenViewport.TopLeftY = 0; m_ScreenViewport.Width = static_cast<float>(mClientWidth); m_ScreenViewport.Height = static_cast<float>(mClientHeight); m_ScreenViewport.MinDepth = 0.0f; m_ScreenViewport.MaxDepth = 1.0f;m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport);完成了這六個步驟后,基本的初始化就完成了。但是,如果涉及到窗口大小變化的情況,那么前面提到的后備緩沖區、深度/模板緩沖區、視口都需要重新調整大小。
D3DApp::OnResize方法
已知深度模板緩沖區和視口都可以直接重新創建一份來進行替換。至于后備緩沖區,我們可以通過IDXGISwapChain::ResizeBuffers來重新調整后備緩沖區的大小:
HRESULT IDXGISwapChain::ResizeBuffers(UINT BufferCount, // [In]緩沖區數目UINT Width, // [In]緩沖區寬度UINT Height, // [In]緩沖區高度DXGI_FORMAT NewFormat, // [In]DXGI格式UINT SwapChainFlags // [In]忽略 );下面的方法演示了在窗口大小發生改變后,以及初次調用時進行的操作:
void D3DApp::OnResize() {assert(m_pd3dImmediateContext);assert(m_pd3dDevice);assert(m_pSwapChain);if (m_pd3dDevice1 != nullptr){assert(m_pd3dImmediateContext1);assert(m_pd3dDevice1);assert(m_pSwapChain1);}// 釋放交換鏈的相關資源m_pRenderTargetView.Reset();m_pDepthStencilView.Reset();m_pDepthStencilBuffer.Reset();// 重設交換鏈并且重新創建渲染目標視圖ComPtr<ID3D11Texture2D> backBuffer;HR(m_pSwapChain->ResizeBuffers(1, m_ClientWidth, m_ClientHeight, DXGI_FORMAT_R8G8B8A8_UNORM, 0));HR(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(backBuffer.GetAddressOf())));HR(m_pd3dDevice->CreateRenderTargetView(backBuffer.Get(), nullptr, m_pRenderTargetView.GetAddressOf()));backBuffer.Reset();D3D11_TEXTURE2D_DESC depthStencilDesc;depthStencilDesc.Width = m_ClientWidth;depthStencilDesc.Height = m_ClientHeight;depthStencilDesc.MipLevels = 1;depthStencilDesc.ArraySize = 1;depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;// 要使用 4X MSAA? --需要給交換鏈設置MASS參數if (m_Enable4xMsaa){depthStencilDesc.SampleDesc.Count = 4;depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality - 1;}else{depthStencilDesc.SampleDesc.Count = 1;depthStencilDesc.SampleDesc.Quality = 0;}depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;depthStencilDesc.CPUAccessFlags = 0;depthStencilDesc.MiscFlags = 0;// 創建深度緩沖區以及深度模板視圖HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc, nullptr, m_pDepthStencilBuffer.GetAddressOf()));HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(), nullptr, m_pDepthStencilView.GetAddressOf()));// 將渲染目標視圖和深度/模板緩沖區結合到管線m_pd3dImmediateContext->OMSetRenderTargets(1, m_pRenderTargetView.GetAddressOf(), m_pDepthStencilView.Get());// 設置視口變換m_ScreenViewport.TopLeftX = 0;m_ScreenViewport.TopLeftY = 0;m_ScreenViewport.Width = static_cast<float>(m_ClientWidth);m_ScreenViewport.Height = static_cast<float>(m_ClientHeight);m_ScreenViewport.MinDepth = 0.0f;m_ScreenViewport.MaxDepth = 1.0f;m_pd3dImmediateContext->RSSetViewports(1, &m_ScreenViewport); }在后續的部分,該框架的代碼基本上不會有什么太大的變動。因此后續代碼的添加主要在GameApp類實現。如果現在對上面的一些過程不理解,也是正常的,可以在后續學習到視圖相關的知識后再來回看這一整個過程。
GameApp類
對于一個初始化應用程序來說,目前GameApp類的非常簡單:
class GameApp : public D3DApp { public:GameApp(HINSTANCE hInstance);~GameApp();bool Init();void OnResize();void UpdateScene(float dt);void DrawScene(); };GameApp::DrawScene方法--每幀畫面的繪制
ID3D11DeviceContext::ClearRenderTargetView方法--清空需要繪制的緩沖區
在每一幀畫面繪制的操作中,我們需要清理一遍渲染目標視圖綁定的緩沖區
void ID3D11DeviceContext::ClearRenderTargetView(ID3D11RenderTargetView *pRenderTargetView, // [In]渲染目標視圖const FLOAT ColorRGBA[4]); // [In]指定覆蓋顏色這里的顏色值范圍都是0.0f到1.0f
比如我們要對后備緩沖區(R8G8B8A8)使用藍色進行清空,可以這樣寫:
float blue[4] = {0.0f, 0.0f, 1.0f, 1.0f} m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);ID3D11DeviceContext::ClearDepthStencilView方法--清空深度/模板緩沖區
同樣在進行渲染之前,我們也要清理一遍深度/模板緩沖區
void ID3D11DeviceContext::ClearDepthStencilView(ID3D11DepthStencilView *pDepthStencilView, // [In]深度/模板視圖UINT ClearFlags, // [In]D3D11_CLEAR_FLAG枚舉FLOAT Depth, // [In]深度UINT8 Stencil); // [In]模板初始值若要清空深度緩沖區,則需要指定D3D11_CLEAR_DEPTH,模板緩沖區則是D3D11_CLEAR_STENCIL。
每一次清空我們需要將深度值設為1.0f,模板值設為0.0f。其中深度值1.0f表示距離最遠處:
m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);IDXGISwapChain::Present方法--前后臺緩沖區交換并呈現
完成一切繪制操作后就可以調用該方法了
HRESULT ID3D11DeviceContext::Present( UINT SyncInterval, // [In]通常為0UINT Flags); // [In]通常為0GameApp::DrawScene的實現如下:
void GameApp::DrawScene() {assert(m_pd3dImmediateContext);assert(m_pSwapChain);static float blue[4] = { 0.0f, 0.0f, 1.0f, 1.0f }; // RGBA = (0,0,255,255)m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), blue);m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);HR(m_pSwapChain->Present(0, 0)); }最終繪制的效果應該如下:
程序退出后的清理
因為之前我們用的是智能指針,所以D3DApp的析構函數十分簡單,只需要通過ID3D11DeviceContext::ClearState方法來恢復D3D設備上下文到默認狀態,卸下所有綁定的資源即可。剩下的事情就交給COM智能指針完成:
D3DApp::~D3DApp() {// 恢復所有默認設定if (m_pd3dImmediateContext)m_pd3dImmediateContext->ClearState(); }練習題
粗體字為自定義題目
DirectX11 With Windows SDK完整目錄
Github項目源碼
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。
posted on 2019-05-05 09:46 NET未來之路 閱讀(...) 評論(...) 編輯 收藏轉載于:https://www.cnblogs.com/lonelyxmas/p/10811308.html
總結
以上是生活随笔為你收集整理的DirectX11 With Windows SDK--01 DirectX11初始化的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shell 下执行mysql 命令
- 下一篇: windows下使用pip安装Pytho