1. 程式人生 > >Direct11 學習筆記(初始化Direct3D)

Direct11 學習筆記(初始化Direct3D)

建立Direct3D 11裝置

首先建立一個視窗,當顯示了一個視窗後,下面繼續建立一個Direct3D 11裝置,這個裝置用於繪製3D場景。首先必須建立三個物件,包括裝置裝置上下文交換鏈

當顯示了一個視窗後,下面繼續建立一個Direct3D 11裝置,這個裝置用於繪製3D場景。首先必須建立三個物件:一個裝置、一個立即執行上下文(immediate context)(裝置上下文的一種)和一個交換鏈(Swap Chain),立即執行上下文物件是Direct3D 11中新新增的。

裝置:用於建立資源。

立即執行上下文:立即執行上下文用於將內容繪製到快取。

交換鏈:交換連結串列示對緩衝(即裝置繪製的和顯示在螢幕上的內容)的操作。交換鏈包含兩個或兩個以上的緩衝,主要是前緩衝和後備緩衝,它們就是裝置繪製形成的紋理,用於顯示在螢幕上。前緩衝(front buffer)

就是當前顯示在螢幕上的內容,這個緩衝是隻讀的,無法修改。後備緩衝(back buffer)是裝置將要繪製的渲染目標,一旦它完成了繪製操作,交換鏈就會通過交換前緩衝和後備緩衝,將後備緩衝的內容顯示在螢幕上,此時後備緩衝就變成了前緩衝。

建立裝置,裝置上下文,交換鏈

要建立裝置,裝置上下文,交換鏈,可以使用D3D11CreateDeviceAndSwapChain()函式

/**
 * 建立裝置及上下文
 * @param pAdapter   	         選擇相應的圖形介面卡,設為NULL以選擇預設的介面卡
 * @param DriverType 	 	 設定驅動型別,一般毫無疑問選擇硬體加速,即D3D_DRIVER_TYPE_HARDWARE,此時下一個引數就是NULL
 * @param Software		 DriverType選擇硬體加速,當DriverType引數選擇D3D_DRIVER_TYPE_HARDWARE時,該引數為NULL
 * @param Flags		 	 可選引數,一般為NULL,可以設為D3D11_CREATE_DEVICE_DEBUG、D3D11_CREATE_DEVICE_SINGLETHREADED,或兩者一起,前者讓要用於除錯時收集資訊,後者在確定程式只在單執行緒下執行時設定為它,可以提高效能
 * @param pFeatureLevels 	 我們提供給程式的特徵等級的一個數組
 * @param FeatureLevels   	 陣列中元素個數
 * @param SDKVersion     	 恆定為D3D11_SDK_VERSION
 * @param pSwapChainDesc	 這個指標指向一個交換連結描述(DXGI_SWAP_CHAIN_DESC結構)
 * @param ppSwapChain		 返回一個IDXGISwapChain物件的指標地址,這個交換連結用於渲染。
 * @param ppDevice		 裝置指標的地址,注意裝置是指標型別,這裡傳遞的是指標的地址(二維指標,d3d程式中所有的介面都宣告為指標型別!)
 * @param pFeatureLevel  	 最後程式選中的特徵等級,我們定義相應的變數,傳遞它的地址進來
 * @param ppImmediateContext     立即執行上下文指標的地址,要求同裝置指標。
 */
HRESULT WINAPI D3D11CreateDeviceAndSwapChain(
    _In_opt_ IDXGIAdapter* pAdapter,
    D3D_DRIVER_TYPE DriverType,
    HMODULE Software,
    UINT Flags,
    _In_reads_opt_( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
    UINT FeatureLevels,
    UINT SDKVersion,
    _In_opt_ CONST DXGI_SWAP_CHAIN_DESC* pSwapChainDesc,
    _Out_opt_ IDXGISwapChain** ppSwapChain,
    _Out_opt_ ID3D11Device** ppDevice,
    _Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
    _Out_opt_ ID3D11DeviceContext** ppImmediateContext 
);

從函式中我們可以看出,我們建立的裝置上下文是一個立即執行上下文

關於裝置上下文

我們建立的立即執行上下文是裝置上下文的一種,還有一種是延遲執行上下文(DeferredContext),使用    ID3D11Device::CreateDeferredContext函式建立。該上下文主要用於Direct3D 11支援的多執行緒程式。

1.在主執行緒中使用立即執行上下文。

2.在工作執行緒總使用延遲執行上下文。

(a)每個工作執行緒可以將圖形指令記錄在一個命令列表(ID3D11CommandList)中。

(b)隨後,每個工作執行緒中的命令列表可以在主渲染執行緒中加以執行。

在多核系統中,可並行處理命令列表中的指令,這樣可以縮短編譯複雜圖形所需的時間。

函式的第八個引數是一個指向DXGI_SWAPCHAIN_DESC結構體的指標,所以要建立交換鏈,我們需要設定一個DXGI_SWAPCHAIN_DESC結構體。

typedef struct DXGI_SWAP_CHAIN_DESC {
  DXGI_MODE_DESC   BufferDesc;    //一個DXGI_MODE_DESC結構體,用來描述後臺快取顯示模式。
  DXGI_SAMPLE_DESC SampleDesc;    //用來開啟多重取樣(multi-sampling)。SampleDesc的Count設定為1,Quality設定為0,這樣就禁用了多重取樣。
  DXGI_USAGE       BufferUsage;   //使用後備緩衝區的方式。將BackBufferUsage設定為DXGI_USAGE_RENDER_TARGET_OUTPUT,表示將繪製到後備緩衝。(描述後臺快取的表面用法和CPU訪問設定。後臺快取能使用渲染輸入或渲染目標輸出) 
  UINT             BufferCount;   //這個值用於描述交換鏈的快取數量,包含前置快取。(即若有一個前置快取和一個後備快取,則該值為2)
  HWND             OutputWindow;  //表示交換鏈將使用的視窗,在這個視窗上我們顯示影象。
  BOOL             Windowed;      //如果值為true將會以視窗模式顯示;全屏則為false。
  DXGI_SWAP_EFFECT SwapEffect;    //描述處理目前的快取和目前的表面的設定。
  UINT             Flags;         //描述交換連結行為設定。
} DXGI_SWAP_CHAIN_DESC;

其中第一個元素BufferDesc也是一個結構體,型別為DXGI_MODE_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;

補充:可以使用IDXGIFactory::CreateSwapChain()函式建立交換鏈,也可以使用D3D11CreateDevice()函式建立裝置和裝置上下文。

檢測多重取樣質量

檢測多重取樣質量是為以後做抗鋸齒做鋪墊。我們使用ID3D11Device::CheckMultisampleQualityLevels函式檢測多重取樣質量。

/** ID3D11Device::CheckMultisampleQualityLevels
 * 獲取多重取樣中可用質量等級
 * @param Format 貼圖的格式
 * @param SampleCount 多重取樣的樣本數(比如用四重取樣,則該引數填4)
 * @param pNumQualityLevels 介面卡支援的質量級別
 */
HRESULT CheckMultisampleQualityLevels(
  DXGI_FORMAT Format,
  UINT SampleCount,
  UINT *pNumQualityLevels
);

建立渲染目標檢視

下一步需要建立一個渲染目標檢視(render target view)。資源不能被直接繫結到一個管線階段,我們必須為資源建立資源檢視(resource view),然後把資源檢視繫結到不同的管線階段。渲染目標檢視是Direct3D 11中一種資源檢視。

因為我們需要將交換鏈中的後備緩衝繫結為一個渲染目標,所以需要建立一個渲染目標檢視,這樣Direct3D 11就可以在其上進行繪製了。我們首先呼叫  GetBuffer()   方法獲取後備緩衝物件。我們可以使用一個D3D11_RENDERTARGETVIEW_DESC結構體表示要建立的渲染目標檢視,這個結構體通常是CreateRenderTargetView()方法的第二個引數。但是,在本教程中,預設的渲染目標檢視就能滿足需要,所以第二個引數為NULL表示使用預設的渲染目標檢視。建立了渲染目標檢視後,我們就可以呼叫   OMSetRenderTargets()  方法將它繫結到圖形管線,這樣管線的繪製輸出被寫到了後備緩衝中。建立並設定渲染目標檢視的程式碼如下:

// Create a render target view
ID3D11Texture2D *pBackBuffer;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (LPVOID* )&pBackBuffer );
if( FAILED( hr ) )
    return hr;
 
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
pBackBuffer->Release();
if( FAILED( hr ) )
    return hr;
 
g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );

建立深度模版緩衝區及其檢視

我們現在需要建立深度/模板緩衝區。深度緩衝區只是一個儲存深度資訊的2D紋理(如果使用模板,則模板資訊也在該緩衝區中)。要建立紋理,我們必須填充一個D3D11_TEXTURE2D_DESC結構體來描述所要建立的紋理,然後再呼叫ID3D11Device::CreateTexture2D方法。該結構體的定義如下:

typedef struct D3D11_TEXTURE2D_DESC {
    UINT Width;                    //紋理的寬度,單位為紋理元素(texel)
    UINT Height;                   //紋理的高度,單位為紋理元素(texel)
    UINT MipLevels;                //多級漸近紋理層(mipmap level)的數量。對於深度/模板緩衝區來說,我們的紋理只需要一個多級漸近紋理層
    UINT ArraySize;                //在紋理陣列中的紋理數量。對於深度/模板緩衝區來說,我們只需要一個紋理
    DXGI_FORMAT Format;            //一個DXGI_FORMAT列舉型別成員,它指定了紋理元素的格式。對於深度/模板緩衝區來說,它必須是4.1.5節列出的格式之一
    DXGI_SAMPLE_DESC SampleDesc;   //多重取樣數量和質量級別
    D3D10_USAGE Usage;             //表示紋理用途的D3D11_USAGE列舉型別成員
    UINT BindFlags;                //指定該資源將會繫結到管線的哪個階段。對於深度/模板緩衝區,該引數應設為D3D11_BIND_DEPTH_STENCIL
    UINT CPUAccessFlags;           //指定CPU對資源的訪問許可權,對於深度/模板緩衝區來說,該引數應設為0
    UINT MiscFlags;                //可選的標誌值,與深度/模板緩衝區無關,所以設為0
} D3D11_TEXTURE2D_DESC;

第七個元素Usage有四個可選值,分別為:

  • D3D11_USAGE_DEFAULT:表示GPU(graphics processing unit,圖形處理器)會對資源執行讀寫操作。CPU不能讀寫這種資源。對於深度/模板緩衝區,我們使用D3D11_USAGE_DEFAULT標誌值,因為GPU會執行所有讀寫深度/模板緩衝區的操作。
  • D3D10_USAGE_IMMUTABLE:表示在建立資源後,資源中的內容不會改變。這樣可以獲得一些內部優化,因為GPU會以只讀方式訪問這種資源。除了在建立資源時CPU會寫入初始化資料外,其他任何時候CPU都不會對這種資源執行任何讀寫操作。
  • D3D10_USAGE_DYNAMIC:表示應用程式(CPU)會頻繁更新資源中的資料內容(例如,每幀更新一次)。GPU可以從這種資源中讀取資料,而CPU可以向這種資源中寫入資料。
  • D3D10_USAGE_STAGING:表示應用程式(CPU)會讀取該資源的一個副本(即,該資源支援從視訊記憶體到系統記憶體的資料複製操作)。

第八個元素BindFlags對於深度/模板緩衝區,該引數應設為D3D11_BIND_DEPTH_STENCIL。其他可用於紋理的繫結標誌值還有:

  • D3D11_BIND_RENDER_TARGET:將紋理作為一個渲染目標繫結到管線上。
  • D3D11_BIND_SHADER_RESOURCE:將紋理作為一個著色器資源繫結到管線上。

第九個元素CPUAccessFlags:如果CPU需要向資源寫入資料,則應指定D3D11_CPU_ACCESS_WRITE。具有寫訪問許可權的資源的Usage引數應設為D3D11_USAGE_DYNAMIC或D3D11_USAGE_STAGING。如果CPU需要從資源讀取資料,則應指定D3D11_CPU_ACCESS_READ。具有讀訪問許可權的資源的Usage引數應設為D3D11_USAGE_STAGING。對於深度/模板緩衝區來說,只有GPU會執行讀寫操作;所以,我們將該引數設為0,因為CPU不會在深度/模板緩衝區上執行讀寫操作。

注意:推薦避免使用D3D11_USAGE_DYNAMIC和D3D11_USAGE_STAGING,因為有效能損失。要獲得最佳效能,我們應建立所有的資源並將它們上傳到GPU並保留其上,只有GPU在讀取或寫入這些資源。但是,在某些程式中必須有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, &mDepthStencilBuffer));

HR(md3dDevice->CreateDepthStencilView(
    mDepthStencilBuffer, 0, &mDepthStencilView));

CreateTexture2D的第二個引數是一個指向初始化資料的指標,這些初始化資料用來填充紋理。不過,由於個紋理被用作深度/模板緩衝區,所以我們不需要為它填充任何初始化資料。當執行深度快取和模板操作時,Direct3D會自動向深度/模板緩衝區寫入資料。所以,我們在這裡將第二個引數指定為空值。

CreateDepthStencilView的第二個引數是一個指向D3D11_DEPTH_STENCIL_VIEW_DESC的指標。這個結構體描述了資源中這個元素資料型別(格式)。如果資源是一個有型別的格式(非typeless),這個引數可以為空值,表示建立一個資源的第一個mipmap等級的檢視(深度/模板緩衝也只能使用一個 mipmap等級)。因為我們指定了深度/模板緩衝的格式,所以將這個引數設定為空值。

將檢視繫結到輸出合併器階段 

現在我們已經為後臺緩衝區和深度緩衝區建立了檢視,就可以將些檢視繫結到管線的輸出合併器階段(output merger stage),使些資源成為管線的渲染目標和深度/模板緩衝區,我們使用OMSetRenderTargets函式

/** ID3D11DeviceContext::OMSetRenderTargets()
 * 繫結一個或更多渲染目標和深度模板緩衝區到管線的輸出合併器階段
 * @param NumViews            將要繫結的渲染目標的數量
 * @param ppRenderTargetViews 將要繫結的渲染目標檢視陣列中的第一個元素的指標
 * @param pDepthStencilView   將要繫結到管線的深度/模板檢視
 */
 void OMSetRenderTargets(
  UINT NumViews,
  ID3D11RenderTargetView * const *ppRenderTargetViews,
  ID3D11DepthStencilView *pDepthStencilView
);

注意:我們可以設定一組渲染目標檢視,但是隻能設定一個深度/模板檢視。使用多個渲染目標是一項高階技術。

設定視口

通常我們會把3D場景渲染到整個後臺緩衝區上。不過,有時我們只希望把3D場景渲染到後臺緩衝區的一個子矩形區域中,如圖所示:

我們將後臺緩衝區的子矩形區域稱為視口(viewport),它由如下結構體描述:

typedef struct D3D11_VIEWPORT {
    FLOAT TopLeftX; //定義了相對於視窗客戶區的視口矩形範圍
    FLOAT TopLeftY; //定義了相對於視窗客戶區的視口矩形範圍
    FLOAT Width;    //定義了相對於視窗客戶區的視口矩形範圍
    FLOAT Height;   //定義了相對於視窗客戶區的視口矩形範圍
    FLOAT MinDepth; //表示深度緩衝區的最小值
    FLOAT MaxDepth; //表示深度緩衝區的最大值
} D3D11_VIEWPORT;

Direct3D使用的深度緩衝區取值範圍是0到1,除非你想要得到一些特殊效果,否則應將MinDepth和MaxDepth分別設為0和1。在填充了D3D11_VIEWPORT結構體之後,我們可以使用ID3D11Device::RSSetViewports方法設定Direct3D的視口。:

/** ID3D11DeviceContext::RSSetViewports 
 * 設定Direct3D的視口
 * @param NumViewports 繫結的檢視的數量(可以使用超過1的數量用於高階的效果,如雙人遊戲中的分屏)
 * @param pViewports   指向一個viewports的陣列
 */
void RSSetViewports(
  UINT NumViewports,
  const D3D11_VIEWPORT *pViewports
);

關於第一個引數,可以用來實現雙人遊戲模式中的分屏效果。建立兩個視口,各佔螢幕的一半,一個居左,另一個居右。然後在左視口中以第一個玩家的視角渲染3D場景,在右視口中以第二個玩家的視角渲染3D場景。你也可以只繪製到螢幕的一個子矩形中,而在其他區域保留諸如按鈕、列表框之類的UI控制元件。

總結:

D3D11的初始化主要有以下幾個步驟:

       1. 建立裝置ID3D11Device和裝置上下文ID3D11DeviceContext,交換鏈Swap Chain

       2. 檢測多重取樣支援的等級:CheckMultisampleQualityLevels

       4. 建立渲染目標檢視RenderTargetView

       5. 建立深度模板檢視DepthStencilView

       6. 把上述兩個檢視繫結到渲染管線相應的階段

       7. 設定Viewport