因爲我的以爲龍書裏面第4章提供的Direct3D 初始化項目封裝得比較好,並且DirectX SDK Samples裏面的初始化程序過於精簡,不適合後續使用,故選擇了以Init Direct3D項目做爲框架,而後還使用了微軟提供的示例項目,二者結合到一塊兒。建議下載項目配合閱讀。html
這一章內容大部分屬於龍書的內容,但仍有一些不一樣的地方。由於後續的全部項目都使用該基礎框架,你也能夠直接使用第一章的項目源碼,而後須要瞭解如下差別部分:git
其中前面兩個部分在下面的連接能夠看到:github
章節 |
---|
ComPtr智能指針 |
HR宏關於dxerr庫的替代方案 |
DirectX11 With Windows SDK完整目錄編程
Github項目源碼windows
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。數組
若是你打算從VS2015開始,則在安裝的時候須要勾選下列選項:緩存
編程語言那一項會自動被勾選。app
而若是是使用VS2017或19的話,則在安裝的時候須要勾選下列選項:框架
注意:VS2015使用的Windows SDK版本爲10.0.14393.0,而VS2017與VS2019使用的Windows SDK版本爲10.0.17763.0編程語言
安裝完成後,新建項目須要從空項目開始。
若是你曾經用過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")
也能夠在項目屬性-連接器-輸入-附加依賴項 添加上面的庫。
在項目屬性頁中能夠直接進行修改。
因爲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.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 11 ComPtr<ID3D11Device> m_pd3dDevice; // D3D11設備 ComPtr<ID3D11DeviceContext> m_pd3dImmediateContext; // D3D11設備上下文 ComPtr<IDXGISwapChain> m_pSwapChain; // D3D11交換鏈 // Direct3D 11.1 ComPtr<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查閱這些函數和結構體的信息。
注意當前項目使用的是d3d11_1.h
頭文件
Direct3D初始化階段首先須要建立D3D設備和D3D設備上下文
D3D設備(ID3D11Device
)包含了建立各類所需資源的方法,最經常使用的有:資源類(ID3D11Resource, 包含紋理和緩衝區),視圖類以及着色器。
D3D設備上下文(ID3D11DeviceContext
)能夠看作是一個渲染管線,負責渲染工做,它須要綁定來自D3D設備建立的各類資源、視圖和着色器才能正常運轉,除此以外,它還可以負責對資源的直接讀寫操做。
而若是支持Direct3D 11.1的話,則對應的接口類爲:ID3D11Device1
、ID3D11DeviceContext1
,它們分別繼承自上面的兩個接口類,區別在於額外提供了少數新的接口,而且接口方法的實現可能會有所區別。
如今,咱們從D3DApp::InitDirect3D
方法開始,一步步進行分析。
建立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_VERSION ID3D11Device** ppDevice, // [Out_Opt]輸出D3D設備 D3D_FEATURE_LEVEL* pFeatureLevel, // [Out_Opt]輸出當前應用D3D特性等級 ID3D11DeviceContext** ppImmediateContext ); //[Out_Opt]輸出D3D設備上下文
pAdapter
(適配器),咱們能夠將它看作是對顯示卡設備的一層封裝,經過該參數,咱們能夠指定須要使用哪一個顯示卡設備。一般該參數咱們設爲nullptr
,這樣就能夠交由上層驅動來幫咱們決定使用哪一個顯卡,或者在NVIDIA控制面板來設置當前程序要使用哪一個顯卡。若是想要在應用層決定,使用IDXGIFactory::EnumAdapters
方法能夠枚舉當前可用的顯示卡設備。在最底下的練習題你將學會如何指定顯示卡設備來建立Direct3D 11.x設備。DriverType
則指定了驅動類型,不過一般大多數狀況都會支持D3D_DRIVER_TYPE_HARDWARE
,以享受硬件加速帶來的效益。如今咱們創建一個驅動數組,而後本身經過for循環的方式進行輪詢:// 驅動類型數組 D3D_DRIVER_TYPE driverTypes[] = { D3D_DRIVER_TYPE_HARDWARE, // 硬件驅動 D3D_DRIVER_TYPE_WARP, // WARP驅動 D3D_DRIVER_TYPE_REFERENCE, // 軟件驅動 }; UINT numDriverTypes = ARRAYSIZE(driverTypes);
若是D3D_DRIVER_TYPE_HARDWARE
不支持,則須要本身經過循環的形式再檢查D3D_DRIVER_TYPE_WARP
是否支持。
關於D3D_DRIVER_TYPE
的詳細描述,能夠去查閱MSDN官方文檔詳細瞭解一下。
Flags
對應的是D3D11_CREATE_DEVICE_FLAG
枚舉值,若是須要D3D設備調試的話(在Debug模式下),能夠指定D3D11_CREATE_DEVICE_DEBUG
枚舉值。指定該值後,能夠在出現程序異常的時候觀察調試輸出窗口的信息。pFeatureLevels
是一個特性等級數組,經過函數內部進行輪詢以檢測所支持的特性等級:// 特性等級數組 D3D_FEATURE_LEVEL featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, }; UINT numFeatureLevels = ARRAYSIZE(featureLevels);
注意:若是你的系統支持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);
注意:
- 支持特性等級
11_0
的顯示適配器必然支持全部渲染目標紋理格式的4倍多重採樣- 即使
m_4xMsaaQuality
的返回值爲1,也不表明無法啓動4倍多重採樣,該成員只是表明模式的種類數目
DXGI交換鏈(IDXGISwapChain
)緩存了一個或多個表面(2D紋理),它們均可以稱做後備緩衝區(backbuffer)。後備緩衝區則是咱們主要進行渲染的場所,咱們能夠將這些緩衝區經過合適的手段成爲渲染管線的輸出對象。在進行呈現(Present)的時候有兩種方法:
注意:考慮到要兼容Win7系統,並且因爲咱們編寫的是Win32應用程序,所以這裏使用的是第一種模型。同時這也是絕大多數教程所使用的。對第二種感興趣的能夠了解下面的連接:
接下來咱們須要瞭解D3D與DXGI各版本的對應關係,這十分重要:
Direct3D API支持版本 | 對應包含DXGI版本 | 對應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
接口的對象,可是爲了拿到該對象還須要經歷一些磨難。
以前在建立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
來進行操做。
若是是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]交換鏈描述1 const 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));
後續咱們還能夠經過該交換鏈來手動指定是否須要全屏
若是是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能夠切換成全屏,若是不想要這種操做,可使用剛纔建立的dxgiFactory1
,按照下面的方式來調用便可:
dxgiFactory1->MakeWindowAssociation(mhMainWnd, DXGI_MWA_NO_ALT_ENTER | DXGI_MWA_NO_WINDOW_CHANGES);
這樣DXGI就不會監聽Windows消息隊列,而且屏蔽掉了對接收到ALT+ENTER消息的處理。
在建立好上述對象後,若是窗口的大小是固定的,則須要經歷下面的步驟:
ID3D11Texture2D
接口對象ID3D11RenderTargetView
ID3D11Texture2D
用做深度/模板緩衝區,要求與後備緩衝區等寬高ID3D11DepthStrenilView
,綁定剛纔建立的2D紋理接下來須要快速瞭解一遍上述步驟所須要用到的API。
因爲此前咱們建立好的交換鏈已經包含1個後備緩衝區了,咱們能夠經過IDXGISwapChain::GetBuffer
方法直接獲取後備緩衝區的ID3D11Texture2D
接口:
HRESULT IDXGISwapChain::GetBuffer( UINT Buffer, // [In]緩衝區索引號,從0到BufferCount - 1 REFIID riid, // [In]緩衝區的接口類型ID void **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()));
除了渲染目標視圖外,咱們還須要建立深度/模板緩衝區用於深度測試。深度/模板緩衝區也是一個2D紋理,要求其寬度和高度必需要和窗口寬高保持一致。
經過D3D設備能夠新建一個2D紋理,但在此以前咱們須要先描述該緩衝區的信息:
typedef struct D3D11_TEXTURE2D_DESC { UINT Width; // 緩衝區寬度 UINT Height; // 緩衝區高度 UINT MipLevels; // Mip等級 UINT ArraySize; // 紋理數組中的紋理數量,默認1 DXGI_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; // 屏幕左上角起始位置X FLOAT TopLeftY; // 屏幕左上角起始位置Y FLOAT Width; // 寬度 FLOAT Height; // 高度 FLOAT MinDepth; // 最小深度,必須爲0.0f FLOAT 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);
完成了這六個步驟後,基本的初始化就完成了。可是,若是涉及到窗口大小變化的狀況,那麼前面提到的後備緩衝區、深度/模板緩衝區、視口都須要從新調整大小。
已知深度模板緩衝區和視口均可以直接從新建立一份來進行替換。至於後備緩衝區,咱們能夠經過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類的很是簡單:
class GameApp : public D3DApp { public: GameApp(HINSTANCE hInstance); ~GameApp(); bool Init(); void OnResize(); void UpdateScene(float dt); void DrawScene(); };
在每一幀畫面繪製的操做中,咱們須要清理一遍渲染目標視圖綁定的緩衝區
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);
一樣在進行渲染以前,咱們也要清理一遍深度/模板緩衝區
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);
完成一切繪製操做後就能夠調用該方法了
HRESULT ID3D11DeviceContext::Present( UINT SyncInterval, // [In]一般爲0 UINT Flags); // [In]一般爲0
GameApp::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(); }
粗體字爲自定義題目
CreateDXGIFactory
建立IDXGIFactory
,而後使用IDXGIFactory::EnumAdapters
來枚舉顯示適配器。嘗試經過這種方式查看你的電腦有多少個顯示適配器(IDXGIAdapter
),並察看它們的信息。IDXGIOutput
),你可使用IDXGIAdapter::EnumOutputs
方法來枚舉出特定的輸出,嘗試觀察它們的信息。IDXGIOutput::GetDisplayModeList
方法觀察全部支持的模式(傳遞DXGI_FORMAT_R8G8B8A8_UNORM
格式進去)。IDXGISwapChain
來動態設置窗口全屏屬性,找到對應的方法並嘗試一下。CreateDXGIFactory
函數來建立IDXGIFactory
,一般它會包含接口IDXGIFactory1
,但有可能它也會包含接口IDXGIFactory2
。在沒有建立D3D設備的狀況下,這種方式就能夠幫助咱們瞭解是否能夠建立出Direct3D 11.1的設備。爲了可以指定顯示適配器來建立D3D11設備,咱們須要將D3D_DRIVER_TYPE
強行設置爲D3D_DRIVER_TYPE_UNKNOWN
,不然在建立設備的時候會獲得以下報錯信息:DX ERROR: D3D11CreateDevice: When creating a device from an existing adapter (i.e. pAdapter is non-NULL), DriverType must be D3D_DRIVER_TYPE_UNKNOWN. [ INITIALIZATION ERROR #3146141: ]
。DirectX11 With Windows SDK完整目錄
歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。