(注:【D3D11遊戲編程】學習筆記系列由CSDN作者BonChoix所寫,轉載請註明出處:http://blog.csdn.net/BonChoix,謝謝~)
初次使用D3D11,先從它的初始化開始。不過在使用D3D之前,需要了解幾個重要的概念:
1. 硬件能力:Hardware Capacity
熟悉D3D9的會很清楚,在初始化d3d9的一開始需要做的就是檢測硬件的能力,以瞭解該用戶機器支持哪些d3d特性,哪些不支持,以在運行期合理的調用API。除非使用d3d自帶的“軟件渲染引擎”,否則試圖使用硬件不支持的特性是會出錯的。但是在d3d11中,檢測硬件能力這一步不再有必要。這對程序員來說省了一點功夫,但是額外的要求是,該硬件必須支持d3d11的全部特性!因此實際上,d3d11應用程序對硬件的要求更苛刻了。
2. 數據格式
D3D應用程序中,無論是紋理圖片,還是創建的緩衝區,都有着特定的數據格式。D3D11支持有限的數據格式,以枚舉變量形式存在,如下幾種:
DXGI_FORMAT_R32G32B32_FLOAT: 3個32位單精度符點數組成,比如用於代表三維空間座標,以及24位顏色;
DXGI_FORMAT_R16G16B16A16_UNORM: 4個16位數組成,每個成員位於[0,1.0f]之間,UNORM意指:unsigned normalized,即無符號,且歸一化的;
DXGI_FORMAT_R32G32_UINT:2個32位數組成,每個成員爲無符號整型(unsigned int);
DXGI_FORMAT_R8G8B8A8_UNORM:4個8位數組成,每個成員爲[0,1.f]之間;
DXGI_FORMAT_R8G8B8A8_SNORM:4個8位數組成,每個成員爲[-1.0f, 1.0f]之間,SNORM意指:signed normalized;
DXGI_FORMAT_R8G8B8A8_SINT:4個8位數組成,每個成員爲有符號整型;
此外還有很多其他類型的數據格式,熟悉各部分的意義後對任何類型可以很快從名字中得知其意思,也很容易寫出指定意義的數據格式對應的枚舉變量。數據格式在程序中使用相當頻繁,因此很有必要提前熟悉該枚舉類型變量的特點。
3. DXGI
DXGI,即DirectX Graphics Infrastructure,是從d3d10開始出現的,封裝了一些d3d底層、基礎的機制,包括數據格式定義、交換鏈(SwapChain),枚舉設備類型及檢測設備信息等。由於這些機制適合於任何一代的D3D接口,而跟特定的d3d特性無關,更新遠不及其他d3d特性頻繁,因此微軟把這些從direct3d中單獨分離了出來。這部分對應的枚舉變量及函數等都以DXGI開頭。
4. 交換鏈:SwapChain
交換鏈不是d3d11特有的性質,而是任何2D、3D計算機動畫最基本的機制之一。爲了實現平滑的動畫,至少需要兩個緩衝區,一個前緩衝區用於顯示,一個後緩衝區用於下一幀的繪製,每次繪製完一幀後通過交換前、後緩衝區對應的指針來顯示新一幀,並在之前的前緩衝區(當前的後緩衝區)上開始繼續繪製下一幀。交換鏈可以有3個或者更多緩衝歐,但絕大多數情況下,2個已經足夠了。在d3d11中交換鏈對應的接口爲IDXGISwapChain。
5. 深度/模板緩衝區:Depth/Stencil Buffer
深度緩衝區是與交換鏈緩衝區大小完全一樣的一塊顯存區域,即每個像素在深度緩衝區中對應相應的位置。在渲染管線的最終的混合階段(Output Merger Stage),每個片段(Fragment)都有一個深度值z,與深度緩衝區對應位置上的深度相比較,如果該片段z更小,則繪製該片段,並覆蓋當前的嘗試值,否則拋棄該片段。該緩衝區主要用於實現投影在屏幕上同一位置、遠近不同的物體之間相同的遮擋效果。此外,靈活配置嘗試緩衝區,可以實現很多種高級特效。
模板緩衝區與深度緩衝區共享同一塊區域,每個深度值處對應一個模板值,模板值主要用於實現一些高級的特效,比如平面反射、陰影等。利用深度、模板緩衝區的不同的configuration實現各種特效在後面會有詳細介紹的。
6. 視圖(View)
視圖是D3D11中的最重要的概念之一。以紋理爲例,一個紋理可以被綁定到3D渲染管線的不同階段以實現不同的用途。比如常見的紋理圖片(d3d11中叫Shader Resource),另一方面也可用於渲染對象(Render Target),充當交換鏈中的緩衝區,用於繪製場景。同一個紋理可以同時被綁定到多個管線階段(Dynamic Cube Mapping就是一個例子,後面會專門介紹),但在創建該紋理資源時需要指定其綁定的類型(Bind Flags),比如上述例子,綁定類型要指定爲:D3D11_BIND_RENDER_TARGET | D3D11_SHADER_RESOURCE。
實際上,在d3d11中,綁定到管線某階段的並不是紋理資源本身,而是對應的“視圖”。對於使用該紋理的每一種方式,我們分別創建相應的視圖,然後把視圖綁定到管線特定的階段。D3D11中針對每種視圖相應的接口爲:ID3D11ShaderResourceView和ID3D11RenderTargetView、ID3D11DepthStencilView。使用視圖的具體細節在後面的過程中會慢慢體會到。
7. 多重採樣抗鋸齒:Multisampling Atialiasing
針對光柵化顯示器抗鋸齒的方法有多種,在d3d中採用的多重採樣方法。即在每個像素點內部,設置多個採樣點,繪製多邊形邊緣時,針對每個採樣點判斷是否被多邊形覆蓋,最終的顏色值從採樣點中取均值,以對多邊形的邊緣進行“模糊化",從而減輕鋸齒效果。如下圖所示,這是一個4重採樣的例子,該像素最終的顏色值是多邊形本身顏色值的3/4:
支持d3d11的硬件全部支持4重採樣,因此我們在後面的程序中將普遍使用4個採樣點。在d3d11中通過結構DXGI_SAMPLE_DESC來設置多重採樣,其定義如下:
typedef struct DXGI_SAMPLE_DESC {
UINT Count;
UINT Quality;
} DXGI_SAMPLE_DESC, *LPDXGI_SAMPLE_DESC;
Count爲我們設置的採樣的個數,Quality爲機器支持的不同的等級,初始化過程中我們會對Quality進行檢測。
注意Multisampling區別於super sampling的關鍵點:不同採樣點是分別計算顏色值(super sampling)還是共享同一顏色值(Multisampling)。詳細情況有很多參考書介紹。
8. 特徵等級:Feature Level
特徵等級定義了一系列支持不同d3d功能的相應的等級,用意即如果一個用戶的硬件不支持某一特徵等級,程序可以選擇較低的等級,以確保正確地運行。d3d11中定義瞭如下幾個等級以代表不同的d3d版本:
typedef enum D3D_FEATURE_LEVEL {
D3D_FEATURE_LEVEL_9_1 = 0x9100,
D3D_FEATURE_LEVEL_9_2 = 0x9200,
D3D_FEATURE_LEVEL_9_3 = 0x9300,
D3D_FEATURE_LEVEL_10_0 = 0xa000,
D3D_FEATURE_LEVEL_10_1 = 0xa100,
D3D_FEATURE_LEVEL_11_0 = 0xb000
} D3D_FEATURE_LEVEL;
在初始化過程中,我們可以提供一組不同的特徵等級,程序會從第一個開始逐個檢測,碰到第一個合適的來創建設備。因此我們在數組中從高到低放置特徵等級提供給初始化程序。
OK,重要的概念就是這些,現在開始初始化D3D~
D3D11的初始化主要有以下幾個步驟:
1. 創建設備ID3D11Device和設備上下文ID3D11DeviceContext;
2. 檢測多重採樣支持的等級:CheckMultisampleQualityLevels
3. 創建交換鏈
4. 創建RenderTargetView
5. 創建DepthStencilView
6. 把上述兩個視圖綁定到渲染管線相應的階段
7. 設置Viewport
下面從第一步開始:
1. 創建設備及上下文:
函數原型如下:
HRESULT D3D11CreateDevice(
__in IDXGIAdapter *pAdapter,
__in D3D_DRIVER_TYPE DriverType,
__in HMODULE Software,
__in UINT Flags,
__in const D3D_FEATURE_LEVEL *pFeatureLevels,
__in UINT FeatureLevels,
__in UINT SDKVersion,
__out ID3D11Device **ppDevice,
__out D3D_FEATURE_LEVEL *pFeatureLevel,
__out 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爲設備上下文指針的地址,要求同設備指針。
創建設備代碼如下:
ID3D11Device *d3dDevice(NULL);
ID3D11DeviceContext *deviceContext(NULL);
D3D_FEATURE_LEVEL featureLevels[6] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
D3D_FEATURE_LEVEL_9_3,
D3D_FEATURE_LEVEL_9_2,
D3D_FEATURE_LEVEL_9_1};
D3D_FEATURE_LEVEL curLevel;
D3D11CreateDevice(
0, //默認適配器
D3D_DEVICE_TYPE_HARDWARE, //硬件加速設備類型
0,
0,
featureLevels, 6,
D3D11_SDK_VERSION,
&d3dDevice,
&curLevel,
&deviceContext);
創建完即可檢測多重採樣(我們只用4重採樣)的等級:
m_d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM,4,&x4MsaaQuality);
2. 創建交換鏈
交換鏈性質通過如下結構指定:
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_DESC;
BufferDesc指定後緩衝區有關特性;
SampleDesc指定多重採樣,前面說過;
BufferUsage,對於交換鏈,爲DXGI_USAGE_RENDER_TARGET_OUTPUT;
BufferCount:我們只創建一個後緩衝區(雙緩衝),因此爲1;
OutputWindow:指定窗口句柄,Win32程序初始化完創建的主窗口;
Windowed:是否全屏;
DXGI_SWAP_EFFECT:通常爲DXGI_SWAP_EFFECT_DISCARD;
Flags:可選,我們設爲0;
DXGI_MODE_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, *LPDXGI_MODE_DESC;
Width、Height爲緩衝區大小,一般設爲主窗口大小;
Format爲緩衝區類型,一般作爲渲染對象緩衝區類型爲DXGI_FORMAT_R8G8B8A8_UNORM;
其他參數一般爲固定的,參見代碼。
此外,創建交換鏈需要獲得接口IDXGIFactory,過程是固定的,創建交換鏈代碼如下:
DXGI_SWAP_CHAIN_DESC scDesc = {0}; //填充結構,設置交換鏈相當屬性
scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //緩衝區數據格式
scDesc.BufferDesc.Width = 640; //緩衝區大小
scDesc.BufferDesc.Height = 480;
scDesc.BufferDesc.RefreshRate.Numerator = 60; //刷新率,一般這樣設定即可
scDesc.BufferDesc.RefreshRate.Denominator = 1;
scDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; //固定參數
scDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; //固定參數
scDesc.BufferCount = 1; //緩衝區個數
scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; //Usage爲Render Target Output
scDesc.Flags = 0;
scDesc.OutputWindow = m_hWnd; //主窗口句柄
scDesc.SampleDesc.Count = 4; //4個採樣點
scDesc.SampleDesc.Quality = x4MsaaQuality-1; //4重採樣支持等級
scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; //常用參數
scDesc.Windowed = true; //窗口模式
//通過如下三步獲得接口IDXGIFactory,來創建交換鏈
IDXGIDevice *pDxgiDevice(NULL);
m_d3dDevice->QueryInterface(__uuidof(IDXGIDevice),reinterpret_cast<void**>(&pDxgiDevice));
IDXGIAdapter *pDxgiAdapter(NULL);
pDxgiDevice->GetParent(__uuidof(IDXGIAdapter),reinterpret_cast<void**>(&pDxgiAdapter));
IDXGIFactory *pDxgiFactory(NULL);
pDxgiAdapter->GetParent(__uuidof(IDXGIFactory),reinterpret_cast<void**>(&pDxgiFactory));
pDxgiFactory->CreateSwapChain(d3dDevice,&scDesc,&swapChain);
//釋放接口
SafeRelease(pDxgiFactory);
SafeRelease(pDxgiAdapter);
SafeRelease(pDxgiDevice);
3. 創建RenderTargetView
D3D11中創建視圖需要對應的資源,這裏先獲取後緩衝區地址,作爲參數創建相應的視圖:
ID3D11Texture2D *backBuffer(NULL);
//獲取後緩衝區地址
swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&backBuffer));
//創建視圖
d3dDevice->CreateRenderTargetView(backBuffer,0,&m_renderTargetView);
//釋放後緩衝區引用
backBuffer->Release();
CreateRenderTargetView函數原型如下:
HRESULT CreateRenderTargetView(
ID3D11Resource *pResource, //視圖對應資源
const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, //視圖描述
ID3D11RenderTargetView **ppRTView //要創建的視圖(指針的地址)
);
d3d11中創建視圖時需要用到視圖的描述(第二個參數),但如果視圖對應的資源類型已知,則一般不再需要描述,設爲NULL。這裏後緩衝區在創建時類型已給出(DXGI_FORMAT_R8G8B8A8_UNORM),因此這裏不需要視圖描述。資源創建時也可以指定爲無類型(TYPELESS),在不同階段以不同的類型進行解釋,這時創建視圖時就需要視圖描述明確說明類型。
4. 創建深度、模板緩衝區及對應視圖
創建緩衝區要即創建一個2維紋理,ID3D11Texture2D,創建它需要先給出描述D3D11_TEXTURE2D_DESC。定義如下:
typedef struct D3D11_TEXTURE2D_DESC {
UINT Width;
UINT Height;
UINT MipLevels; //這裏不需要mipmap,設爲1
UINT ArraySize; //紋理數組才用,這裏爲1
DXGI_FORMAT Format; //數據格式,一般爲DXGI_FORMAT_D24_UNORM_S8_UINT,24位用於深度,8位用於模板
DXGI_SAMPLE_DESC SampleDesc; //多重採樣,如前,前後務必保持一致!
D3D11_USAGE Usage; //Usage,對於只讓GPU讀、寫,應爲D3D11_USAGE_DEFAULT
UINT BindFlags; //綁定類型,爲D3D11_BIND_DEPTH_STENCIL
UINT CPUAccessFlags; //CPU不可訪問,設爲0
UINT MiscFlags; //設爲0
} D3D11_TEXTURE2D_DESC;
創建視圖類型DepthStencilView,代碼如下:
D3D11_TEXTURE2D_DESC dsDesc;
dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
dsDesc.Width = 640;
dsDesc.Height = 480;
dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
dsDesc.MipLevels = 1;
dsDesc.ArraySize = 1;
dsDesc.CPUAccessFlags = 0;
dsDesc.SampleDesc.Count = 4;
dsDesc.SampleDesc.Quality = x4MsaaQuality-1;
dsDesc.MiscFlags = 0;
dsDesc.Usage = D3D11_USAGE_DEFAULT;
d3dDevice->CreateTexture2D(&dsDesc,0,&depthStencilBuffer);
d3dDevice->CreateDepthStencilView(depthStencilBuffer,0,&depthStencilView);
創建完兩個視圖後當然就要綁定到渲染管線上去啦:
deviceContext->OMSetRenderTargets(1,&renderTargetView,depthStencilView);
該函數原型如下:
void OMSetRenderTargets(
[in] UINT NumViews, //RenderTarget個數,我們一般只用一個
[in] ID3D10RenderTargetView *const *ppRenderTargetViews, //RenderTarget數組,只一個,所以直接傳其地址即可
[in] ID3D10DepthStencilView *pDepthStencilView //DepthStencil view
);
最後設置viewport,很簡單,D3D11_VIEWPORT定義如下:
typedef struct D3D11_VIEWPORT {
FLOAT TopLeftX; //視口左上角在屏幕上x座標,一般視口占滿屏幕的,所以爲0
FLOAT TopLeftY; //y座標,同上
FLOAT Width; //視口寬度,一般與後緩衝區一致,以保持圖像不變形
FLOAT Height; //高度,同上
FLOAT MinDepth; //最小深度值:0.0f
FLOAT MaxDepth; //最大深度值:1.0f
} D3D11_VIEWPORT;
設置視口代碼:
D3D11_VIEWPORT viewPort;
viewPort.Width = static_cast<FLOAT>(clientWidth);
viewPort.Height = static_cast<FLOAT>(clientHeight);
viewPort.MaxDepth = 1.f;
viewPort.MinDepth = 0.f;
viewPort.TopLeftX = 0.f;
viewPort.TopLeftY = 0.f;
deviceContext->RSSetViewports(1,&viewPort);
Voila,初始化過程全部完成!
下面就可以進入主循環渲染函數繪製場景了,這次我們暫時什麼也不畫,只是簡單地把屏幕清空爲隨意指定的顏色:
XMVECTORF32 color = {0.f, 1.f, 0.f, 1.6f};
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);
每幀開始渲染時,先清空RenderTargetView和深度緩衝區,然後就是全屏渲染內容了。繪製完整個場景後,最後調用讓交換鏈Present來交換前後緩衝區,以顯示新的一幀。清空RenderTarget很簡單,把相應視圖傳進來,並給定任意背景顏色(4個float的數組,這裏適合用XMVECTORF32)即可。對於清空深度、模板緩衝區,第一個參數一目瞭然,第二個用來指定清空的內容參數,一般我們要同時清空深度和模板,因此用兩個枚舉變量求並,最後兩個參數分別指定清空深度和模板的默認值,深度清空爲1.0f(最大),模板一般清空爲0,就這樣。
整個過程就結束啦,配合上次中的Win32程序,現在已經是一個完整的、可工作的d3d程序了,顯示的即爲一個空白的指定顏色的窗口。初始化程序就是這樣,一個最簡單的d3d應用程序。以後慢慢開始渲染更加炫麗的場景~
這次完整的初始化程序源代碼如下: