轉載請注明出處:http://www.cnblogs.com/Ray1024
一、概述
做完一系列的准備工作之后,我們就正式進入Direct3D11的學習了。我們就從Direct3D11的初始化工作開始我們的學習之路。
這篇文章主要介紹了在一個空的Win32程序中,從頭開始D3D11的初始化過程。
二、D3D11的初始化步驟
2.1 創建設備(Device)和上下文(Context)
要初始化D3D11,首先需要創建D3D11設備(ID3D11Device)和上下文(ID3D11DeviceContext)。它們是是最重要的DD接口,可以被看成是物理圖形設備硬件的軟控制器;也就是說,我們可以通過該接口與硬件進行交互,命令硬件完成一些工作(比如:在顯存中分配資源、清空后台緩沖區、將資源綁定到各種管線階段、繪制幾何體)。具體而言:
a.ID3D11Device接口用於檢測顯示適配器功能和分配資源。
b.ID3D11DeviceContext接口用於設置管線狀態、將資源綁定到圖形管線和生成渲染命令。
設備和上下文可用如下函數創建:
HRESULT D3D11CreateDevice (
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software ,
UINT Flags ,
CONST D3D_FEATURE_LEVEL *pFeatureLevels ,
UINT FeatureLevels ,
UINT SDKVersion,
ID3D11Device **ppDevice ,
D3D_FEATURE_LEVE L *pFeatureLevel,
ID3D11DeviceContext **ppImmediateContext
);
參數分析:
pAdapter 指定要為哪個物理顯卡創建設備對象。為NULL時,表示使用主顯卡;
DriverType 設置驅動類型,一般選擇硬件加速D3D_DRIVER_TYPE_HARDWARE,此時下一個參數就是NULL;
Flags 為可選參數,一般為NULL,可以設為D3D11_CREATE_DEVICE_DEBUG或D3D11_CREATE_DEVICE_SINGLETHREADED,
或兩者一起,前者讓要用於調試時收集信息,后者在確定程序只在單線程下運行時設置為它,可以提高性能;
pFeatureLevels 為我們提供給程序的特征等級的一個數組,下一個參數為數組中元素個數;
SDKVersion 恆定為D3D11_SDK_VERSION;
ppDevice 為設備指針的地址,注意設備是指針類型,這里傳遞的是指針的地址(二維指針,d3d程序中所有的接口都聲明為指針類型!);
pFeatureLevel 為最后程序選中的特征等級,我們定義相應的變量,傳遞它的地址進來;
ppImmediateContext 為設備上下文指針的地址,要求同設備指針。
使用此函數創建設備的代碼示例:
UINT createDeviceFlags = 0;
#if defined(DEBUG)||defined(_DEBUG)
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
ID3D11Device * md3dDevice;
ID3D11Device Context* md3dImmediate Context;
HRESULT hr = D3D11CreateDevice(
0, // 默認顯示適配器
D3D_DRIVER_TYPE_HARDWARE ,
0, // 不使用軟件設備
createDeviceFlags ,
0, 0, // 默認的特征等級數組
D3D11_SDK_VERSION,
& md3dDevice ,
& featureLevel,
& md3dImmediateContext);
if(FAILED(hr) )
{
MessageBox(0, L"D3D11CreateDevice Failed.", 0, 0);
return false ;
}
if(featureLevel != D3D_FEATURE_LEVEL_11_0)
{
MessageBox(0, L"Direct3D FeatureLevel 11 unsupported.", 0, 0);
return false;
}
上下文有兩種:立即執行上下文(immediate context)和延遲執行上下文(ID3D11Device::CreateDeferredContext),這里我們使用立即執行上下文。延遲執行上下文主要用於D3D11支持的多線程編程。
2.2 檢測4X多重采樣質量支持
創建了設備后,我們就可以檢查4X多重采樣質量等級了。所有支持D3D11的設備都支持所有渲染目標格式的4X MSAA(支持的質量等級可能並不相同)。
UINT m4xMsaaQuality; HR(md3dDevice ->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, & m4xMsaaQuality)); assert(m4xMsaaQuality > 0);
因為4X MSAA總是被支持的,所以返回的質量等級總是大於0。
2.3 描述交換鏈
下一步是創建交換鏈,首先需要填充一個DXGI_SWAP_CHAIN_DESC結構體來描述我們將要創建的交換鏈的特性。該結構體的定義如下:
typedef struct DXGI_MODE_DESC
{
UINT Width; // 后台緩沖區寬度
UINT Height; // 后台緩沖區高度
DXGI_RATIONAL RefreshRate; // 顯示刷新率
DXGI_FORMAT Format; // 后台緩沖區像素格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // display scanline mode
DXGI_MODE_SCALING Scaling; // display scaling mode
} DXGI_MODE_DESC;
typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc; // 上面介紹的結構體類型,描述了我們所要創建的后台緩沖區的屬性
DXGI_SAMPLE_DESC SampleDesc; // 多重采樣數量和質量級別
DXGI_USAGE BufferUsage; // 對於交換鏈,為DXGI_USAGE_RENDER_TARGET_OUTPUT
UINT BufferCount; // 交換鏈中的后台緩沖區數量;我們一般只用一個后台緩沖區來實現雙緩存。當然,使用兩個后台緩沖區就可以實現三緩存
HWND OutputWindow; // 將要渲染到的窗口的句柄
BOOL Windowed; // 當設為true時,程序以窗口模式運行;當設為false時,程序以全屏(full-screen)模式運行
DXGI_SWAP_EFFECT SwapEffect; // 設為DXGI_SWAP_EFFECT_DISCARD,讓顯卡驅動程序選擇最高效的顯示模式
UINT Flags; // 可選,通常設為0
} DXGI_SWAP_CHAIN_DESC;
描述交換鏈的DXGI_SWAP_CHAIN_DESC結構體示例代碼:
DXGI_SWAP_CHAIN_DESC sd;
sd.BufferDesc.Width = mClientWidth; // 使用窗口客戶區寬度
sd.BufferDesc.Height = mClientHeight;
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;
// 是否使用4X MSAA?
if(mEnable4xMsaa)
{
sd.SampleDesc.Count = 4;
// m4xMsaaQuality是通過CheckMultisampleQualityLevels()方法獲得的
sd.SampleDesc.Quality = m4xMsaaQuality-1;
}
// NoMSAA
else
{
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
}
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 1;
sd.OutputWindow = mhMainWnd;
sd.Windowed = true;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
sd.Flags = 0;
注意:如果需要在運行時改變多重采樣的設置,那么必須銷毀然后重新創建交換鏈。
注意:因為大多數顯示器不支持超過24位以上的顏色,再多的顏色也是浪費,所以我們將后台緩沖區的像素格式設置為DXGI_FORMAT_R8G8B8A8_UNORM(紅、綠、藍、alpha各8位)。額外的8位alpha並不會輸出在顯示器上,但在后台緩沖區中可以用於特定的用途。
2.4 創建交換鏈
交換鏈(IDXGISwapChain)是通過IDXGIFactory實例的IDXGIFactory::CreateSwapChain方法創建的:
HRESULT IDXGIFactory::CreateSwapChain(
IUnknown *pDevice , // 指向ID3D11Device的指針
DXGI_SWAP_CHAIN_DESC *pDesc, // 指向一個交換鏈描述的指針
IDXGISwapChain **ppSwapChain); // 返回創建后的交換鏈
我們可以通過CreateDXGIFactory(需要鏈接dxgi.lib)獲取指向一個IDXGIFactory實例的指針。但是使用這種方法獲取IDXGIFactory實例,並調用IDXGIFactory::CreateSwapChain方法后,會出現如下的錯誤信息:
DXGI Warning: IDXGIFactory::CreateSwapChain: This function is being called with a device from a different IDXGIFactory.
要避免這個錯誤,我們需要使用創建設備的那個IDXGIFactory實例,要獲得這個實例,必須使用下面的COM查詢(具體解釋可參見IDXGIFactory的文檔):
IDXGIDevice * dxgiDevice = 0;
HR(md3dDevice ->QueryInterface(__uuidof(IDXGIDevice),
(void**)&dxgiDevice ));
IDXGIAdapter* dxgiAdapter = 0;
HR(dxgiDevice ->GetParent(__uuidof(IDXGIAdapter),
(void**))&dxgiAdapte r ));
// 獲得IDXGIFactory 接口
IDXGIFactory* dxgiFactory = 0;
HR(dxgiAdapter->GetParent(__uuid of(IDXGIFactory),
(void**))&dxgiFactor y));
// 現在,創建交換鏈
IDXGISwapChain* mSwapChain;
HR(dxgiFactory->CreateSwapChain(md3dDevice, &sd , &mSw ap Chain);
// 釋放COM接口
ReleaseCOM (dxgiDevice ;
ReleaseCOM (dxgiAdapter);
ReleaseCOM (dxgiFactory);
注意:也可以使用D3D11CreateDeviceAndSwapChain方法同時創建設備、設備上下文和交換鏈。
注意:DXGI(DirectX Graphics Inf rastructure)是獨立於Direct3D的API,用於處理與圖形關聯的東西,例如交換鏈等。DXGI與Direct3D分離的目的在於其他圖形API(例如Direct2D)也需要交換鏈、圖形硬件枚舉、在窗口和全屏模式之間切換,通過這種設計,多個圖形API都能使用DXGI API。
2.5 創建渲染目標視圖
我們必須為資源創建資源視圖,然后把資源視圖綁定到不同的管線階段。尤其是在把后台緩沖區綁定到管線的輸出合並器階段時(使Direct3D可以在后台緩沖區上執行渲染工作),我們必須為后台緩沖區創建一個渲染目標視圖(render target view)。下面的代碼說明了創建渲染目標視圖過程:
ID3D11RenderTargetView* mRenderTargetView; ID3D11Texture2D* backBuffer; // 獲取一個交換鏈的后台緩沖區指針 mSwapChain->GetBuffer(0,__uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&backBuffer)); // 創建渲染目標視圖 md3dDevice->CreateRenderTargetView(backBuffer, 0, &mRenderTargetView); // 每調用一次GetBuffer方法,后台緩沖區的COM引用計數就會遞增一次。我們需要在使用完之后釋放它 ReleaseCOM(backBuffer);
2.6 創建深度/模板緩沖區及視圖
創建緩沖區要即創建一個2維紋理,ID3D11Texture2D,創建它需要先給出描述D3D11_TEXTURE2D_DESC。定義如下:
typedef struct D3D11_TEXTURE2D_DESC {
UINT Width; // 紋理的寬度,單位為紋理元素(texel)
UINT Height; // 紋理的高度,單位為紋理元素(texel)
UINT MipLevels; // 多級漸近紋理層(mipmap level)的數量。對於深度/模板緩沖區來說,只需要一個多級漸近紋理層
UINT ArraySize; // 在紋理數組中的紋理數量。對於深度/模板緩沖區來說,我們只需要一個紋理
DXGI_FORMAT Format; // 數據格式。一般為DXGI_FORMAT_D24_UNORM_S8_UINT,24位用於深度,8位用於模板
DXGI_SAMPLE_DESC SampleDesc; // 多重采樣數量和質量級別
D3D10_USAGE Usage; // 表示紋理用途的D3D11_USAGE枚舉類型成員。默認為D3D11_USAGE_DEFAULT,表示GPU對資源進行讀寫操作
UINT BindFlags; // 指定該資源將會綁定到管線的哪個階段。對於深度/模板緩沖區,該參數應設為D3D11_BIND_DEPTH_STENCIL
UINT CPUAccessFlags; // 指定CPU對資源的訪問權限。對於深度/模板緩沖區來說,該參數設為0
UINT MiscFlags; // 可選的標志值,與深度/模板緩沖區無關,所以設為0
} D3D11_TEXTURE2D_DESC;
在本書中,我們會看到以各種不同選項來創建資源的例子;例如,使用不同的Usage標志值、綁定標志值和CPU訪問權限標志值。但就目前來說,我們只需要關心那些與創建深度/模板緩沖區有關的標志值即可,其他選項可以以后再說。
另外,在使用深度/模板緩沖區之前,我們必須為它創建一個綁定到管線上的深度/模板視圖。過程與創建渲染目標視圖的過程相似。下面的代碼示范了如何創建深度/模板紋理以及與它對應的深度/模板視圖:
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?——必須與交換鏈的MSAA的值匹配
if( mEnable4xMsaa)
{
depthStencilDesc.SampleDesc.Count = 4;
depthStencilDesc.SampleDesc.Quality= m4xMsaaQuality-1;
}
// 不使用MSAA
else
{
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
}
depthStencilDesc.Usage = D3D10_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ID3D10Texture2D* mDepthStencilBuffer;
ID3D10DepthStencilView* mDepthStencilView;
HR(md3dDevice->CreateTexture2D(
&depthStencilDesc,
0, // 一個指向初始化數據的指針,用來填充紋理。對於深度/模板緩沖區,不需要為它填充任何初始化數據
// 當執行深度緩存和模板操作時,Direct3D會自動向深度/模板緩沖區寫入數據
&mDepthStencilBuffer
));
HR(md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer,
0, // 描述資源元素數據類型(格式)。如果資源是一個有類型的格式(非typeless),這個參數可以為空值,
// 表示創建一個資源的第一個mipmap等級的視圖(深度/模板緩沖也只能使用一個 mipmap等級)。因為我們指定了深度/模板緩沖的格式,所以將這個參數設置為空值。
&mDepthStencilView
));
2.7 將視圖綁定到輸出合並器階段
現在我們已經為后台緩沖區和深度緩沖區創建了視圖,就可以將些視圖綁定到管線的輸出合並器階段(output merger stage),使些資源成為管線的渲染目標和深度/模板緩沖區:
md3dImmediateContext->OMSetRenderTargets(
1,&mRenderTargetView,mDepthStencilView);
第一個參數是我們將要綁定的渲染目標的數量;我們在這里僅綁定了一個渲染目標,不過該參數可以為着色器同時綁定多個渲染目標(是一項高級技術)。第二個參數是我們將要綁定的渲染目標視圖數組中的第一個元素的指針。第三個參數是將要綁定到管線的深度/模板視圖。
注意:我們可以設置一組渲染目標視圖,但是只能設置一個深度/模板視圖。使用多個渲染目標是一項高級技術,會在之后加以介紹。
2.8 設置視口
通常我們會把3D場景渲染到整個后台緩沖區上,但也可以把3D場景渲染到后台緩沖區的一個子矩形區域中。
我們將后台緩沖區的子矩形區域稱為視口(viewport),它由如下結構體描述:
typedef struct D3D11_VIEWPORT {
FLOAT TopLeftX; // 視口左上角坐標x值
FLOAT TopLeftY; // 視口左上角坐標y值
FLOAT Width; // 視口寬度
FLOAT Height; // 視口高度
FLOAT MinDepth; // 深度緩沖區最小值(0.f~1.f),默認0.f
FLOAT MaxDepth; // 深度緩沖區最大值(0.f~1.f),默認1.f
} D3D11_VIEWPORT;
在填充了D3D11_VIEWPORT結構體之后,我們可以使用ID3D11Device::RSSetViewports方法設置Direct3D的視口。下面的例子創建和設置了一個視口,該視口與整個后台緩沖區的大小相同:
D3D11_VIEWPORT vp; vp.TopLeftX = 0; vp.TopLeftY = 0; vp.Width = static_cast<float>(mClientWidth); vp.Height = static_cast<float>(mClientHeight); vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; md3dImmediateContext-->RSSetViewports( 1, // 綁定的視圖的數量(可以使用超過1的數量用於高級的效果) &vp // 指向一個viewports的數組 );
2.9 繪制
到了這個位置,D3D11初始化工作就完成了。在窗口繪制函數中,使用D3D11的函數繪制窗口背景。
代碼如下:
void Render()
{
// 繪制青色背景
XMVECTORF32 color = {0.f, 1.f, 1.f, 1.0f};
g_deviceContext->ClearRenderTargetView(g_renderTargetView,reinterpret_cast<float*>(&color));
g_deviceContext->ClearDepthStencilView(g_depthStencilView,D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.f,0);
// 正式的場景繪制工作
// 顯示
g_swapChain->Present(0,0);
}
顯示效果如下:

在這里完整代碼代碼就不貼出了,有興趣的朋友可以點擊此處下載Demo源碼,Demo源碼是1_D3DInit文件。
三、結語
D3D11的整個初始化過程就結束了。
我們在這篇文章中,我們從一個空的Win32項目開始,將D3D11的整個初始化過程放到了程序中,一步一步搭建出來一個空白窗口程序。這就是一個最簡單的、完整的D3D程序了。
在之后的學習中,我們將在這個最簡單的D3D窗口程序中,一步一步添加新代碼,由簡入繁地實現越來越漂亮的場景。
