【D3D11遊戲編程】學習筆記五:D3D11初始化

       (注:【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應用程序。以後慢慢開始渲染更加炫麗的場景~

       這次完整的初始化程序源代碼如下:

       D3D11完整初始化代碼

      

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章