天天看點

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

概述

Direct3D是一種底層繪圖API(application programming interface,應用程式接口),它可以讓我們可以通過3D硬體加速繪制3D世界。從本質上講,Direct3D提供的是一組軟體接口,我們可以通過這組接口來控制繪圖硬體。

在Direct3D 9中,裝置可以隻支援Direct3D 9的部分功能;是以,當一個Direct3D 9應用程式要使用某一特性時,應用程式就必須先檢查硬體是否支援該特性。如果要調用的是一個不為硬體支援Direct3D函數,那應用程式就會出錯。而在Direct3D 11中,不需要再做這種裝置功能檢查,因為Direct3D 11強制要求裝置實作Direct3D 11規定的所有功能特性。

COM

元件對象模型(COM)技術使DirectX獨立于任何程式設計語言,并具有版本向後相容的特性。

我們經常把COM對象稱為接口,并把它當成一個普通的C++類來使用。當使用C++編寫DirectX程式時,許多COM的底層細節都不必考慮。唯一需要知道的一件事情是,我們必須通過特定的函數或其他的COM接口方法來擷取指向COM接口的指針,而不能用C++的new關鍵字來建立COM接口。另外,當我們不再使用某個接口時,必須調用它的Release方法來釋放它(所有的COM接口都繼承于IUnknown接口,而Release方法是IUnknown接口的成員),而不能用delete語句——COM對象在其自身内部實作所有的記憶體管理工作。

COM接口都以大寫字母“I”為字首。例如,表示2D紋理的接口為ID3D11Texture2D。

紋理和資料資源格式

2D紋理(texture)是一種資料元素矩陣。2D紋理的用途之一是存儲2D圖像資料,在紋理的每個元素中存儲一個像素顔色。但這不是紋理的唯一用途;例如, 有一種稱為法線貼圖映射(normal mapping)的進階技術在紋理元素中存儲的不是顔色,而是3D向量。是以,從通常意義上講,紋理用來存儲圖像資料,但是在實際應用中紋理可以有更廣泛的用途。1D紋理類似于一個1D資料元素數組,3D 紋理類似于一個3D資料元素數組。但是在随後的章節中我們會講到,紋理不僅僅是一個資料數組;紋理可以帶有多級漸近紋理層(mipmap level),GPU可以在紋理上執行特殊運算,比如使用過濾器(filter)和多重采樣(multisampling)。此外,不是任何類型的資料都能存儲到紋理中的;紋理隻支援特定格式的資料存儲,這些格式由DXGI_FORMAT枚舉類型描述。

交換鍊和頁面翻轉

為了避免在動畫中出現閃爍,最好的做法是在一個離屏(off-screen)紋理中執行所有的動畫幀繪制工作,這個離屏紋理稱為背景緩沖區(back buffer)。

當我們在背景緩沖區中完成給定幀的繪制工作後,便可以将背景緩沖區作為一個完整的幀顯示在螢幕上;使用這種方法,使用者不會察覺到幀的繪制過程,隻會看到完整的幀。從理論上講,将一幀顯示到螢幕上所消耗的時間小于螢幕的垂直重新整理時間。硬體會自動維護兩個内置的紋理緩沖區來實作這一功能,這兩個緩沖區分别稱為前台緩沖區(front buffer)和背景緩沖區。前台緩沖區存儲了目前顯示在螢幕上的圖像資料,而動畫的下一幀會在背景緩沖區中執行繪制。當背景緩沖區的繪圖工作完成之後,前後兩個緩沖區的作用會發生翻轉:背景緩沖區會變為前台緩沖區, 而前台緩沖區會變為背景緩沖區,為下一幀的繪制工作提前做準備。我們将前後緩沖區功能互換的行為稱做呈現(presenting)。送出是一個運作速度很快的操作,因為它隻是将前台緩沖區的指針和背景緩沖區的指針做了一個簡單的交換。圖4.1說明了這一過程。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

圖4.1:我們首先渲染緩沖區B,它是目前的背景緩沖區。一旦幀渲染完成,前後緩沖區的指針會互相交換,緩沖區B會變為前台緩沖區,而緩沖區A會變為新的背景緩沖區。之後,我們将在緩沖區A中進行下一幀的渲染。一旦幀渲染完成,前後緩沖區的指針會再次進行交換,緩沖區A會變為前台緩沖區,而緩沖區B會再次變為背景緩沖區。

前後緩沖區形成了一個交換鍊(swap chain)。在Direct3D中,交換鍊由IDXGISwapChain接口表示。該接口儲存了前後緩沖區紋理,并提供了用于調整緩沖區尺寸的方法(IDXGISwapChain::ResizeBuffers)和呈現方法(IDXGISwapChain::Present)。

注意:雖然背景緩沖區是一個紋理(紋理元素稱為texel),但是我們更習慣于将紋理元素稱為像素(pixel),因為背景緩沖區存儲的是顔色資訊。有時,即使紋理中存儲的不是顔色資訊,人們還是會将紋理元素稱為像素(例如,“法線貼圖像素”)。

深度緩沖區

深度緩沖區(depth buffer)是一個不包含圖像資料的紋理對象。在一定程度上,深度資訊可以被認為是一種特殊的像素。常見的深度值範圍在0.0到1.0之間,其中0.0表示離觀察者最近的物體,1.0表示離觀察者最遠的物體。深度緩沖區中的每個元素與背景緩沖區中的每個像素一一對應(即,背景緩沖區的第ij個元素對應于深度緩沖區的第ij個元素)。是以,當背景緩沖區的分辨率為1280×1024時,在深度緩沖區中有1280×1024個深度元素。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

圖4.2是一個簡單的場景,其中一些物體擋住了它後面的一些物體的一部分區域。為了判定物體的哪些像素位于其他物體之前,Direct3D使用了一種稱為深度緩存(depth buffering)或z緩存(z-buffering)的技術。我們所要強調的是在使用深度緩存時,我們不必關心所繪物體的先後順序。

注意:要處理深度的問題,有人可能會建議按照從遠至近的順序繪制場景中的物體。使用這種方法,離得近的物體會覆寫在離得遠的物體之上,這樣就會産生正确的繪制結果,這也是畫家作畫時用到的方法。但是,這種方法會導緻另一個問題——如何将大量的物體和相交的幾何體按從遠到近的方式進行排序?此外,圖形硬體本身就提供了深度緩存供我們使用,是以我們不會采用畫家算法。

深度緩存的工作方式

如圖4.3所示,它展示的是觀察者看到的立體空間(左圖)以及該立體空間的2D側視圖(右圖)。從這個圖中我們可以發現,3個不同的像素會被渲染到視圖視窗的同一個像素點P上。(當然,我們知道隻有最近的像素會被渲染到P上,因為它擋住了後面的其他像素,可是計算機不知道這些事情。)首先,在渲染之前,我們必須把背景緩沖區清空為一個預設顔色(比如黑色或白色),把深度緩沖區清空為預設值——通常設為1.0(像素所具有的最遠深度值)。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

圖4.3:視圖視窗相當于從3D場景生成的2D圖像(背景緩沖區)。我們看到,有3個不同的像素可以被投影到像素P上。直覺告訴我們,P1是P的最終顔色,因為它離觀察者最近,而且遮擋了其他兩個像素。深度緩沖區算法提供了一種可以在計算機上實作的判定過程。注意,我們所說的深度值是相對于觀察坐标系而言的。實際上,當深度值存入深度緩沖區時,它會被規範到[0.0,1.0]區間内。

現在,假設物體的渲染順序依次為:圓柱體、球體和圓錐體。下面的表格彙總了在繪制些物體時像素P及相關深度值的變化過程;其他像素的處理過程與之類似。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

從上表可以看到,當我們發現某個像素具有更小的深度值時,就更新該像素以及它在深度緩沖區中的相應深度值。通過一方式,在最終得到的渲染結果中隻會包含那些離觀察者最近的像素。綜上所述,深度緩沖區用于為每個像素計算深度值和實作深度測試。深度測試通過比較像素深度來決定是否将該像素寫入背景緩沖區的特定像素位置。隻有離觀察者最近的像素才會勝出,成為寫入背景緩沖區的最終像素。

但是如果用到了模闆緩沖區,那麼模闆緩沖區必定是與深度緩沖區存儲在一起的。例如,32位格式DXGI_FORMAT_D24_UNORM_S8_UINT使用24位用于深度緩沖區,8位用于模闆緩沖區。 是以,将深度緩沖區稱為“深度/模闆緩沖區”更為合适。

紋理資源視圖

紋理可以被綁定到渲染管線(rendering pipeline)的不同階段(stage);例如,比較常見的情況是将紋理作為渲染目标(即,Direct3D渲染到紋理)或着色器資源(即,在着色器中對紋理進行采樣)。當建立用于這兩種目的的紋理資源時,應使用綁定标志值:

D3D11_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE

指定紋理所要綁定的兩個管線階段。其實,資源不能被直接綁定到一個管線階段;我們隻能把與資源關聯的資源視圖綁定到不同的管線階段。無論以哪種方式使用紋理,Direct3D始終要求我們在初始化時為紋理建立相關的資源視圖(resource view)。這樣有助于提高運作效率,正如SDK文檔指出的那樣:“運作時環境與驅動程式可以在視圖建立執行相應的驗證和映射,減少綁定時的類型檢查”。是以,當把紋理作為一個渲染目标和着色器資源時,我們要為它建立兩種視圖:渲染目标視圖(ID3D11RenderTargetView)和着色器資源視圖(ID3D11ShaderResourceView)。

資源視圖主要有兩個功能:(1)告訴Direct3D如何使用資源(即,指定資源所要綁定的管線階段);(2)如果在建立資源時指定的是弱類型(typeless)格式,那麼在為它建立資源視圖時就必須指定明确的資源類型。對于弱類型格式,紋理元素可能會在一個管線階段中視為浮點數,而在另一個管線階段中視為整數。為了給資源建立一個特定視圖,我們必須在建立資源時使用特定的綁定标志值。例如,如果在建立資源沒有使用D3D11_BIND_DEPTH_STENCIL綁定标志值(該标志值表示紋理将作為一個深度/模闆緩沖區綁定到管線上),那我們就無法為該資源建立ID3D11DepthStencilView視圖。

“當建立資源時,為資源指定強類型(fully-typed)格式,把資源的用途限制在格式規定的範圍内,有利于提高運作時環境對資源的通路速度……”。是以,你隻應該在真正需要弱類型資源時(使用弱類型的優點是可以使用不同的視圖将資料用于不同的用途),才建立弱類型資源;否則,應盡量建立強類型資源。

4.1.7 多重采樣

因為計算機顯示器上的像素分辨率有限,是以當我們繪制一條任意直線時,該直線很難精确地顯示在螢幕上。圖4.4中的第一條直線說明了“階梯”(aliasing,鋸齒)效應,當使用像素矩陣近似地表示一條直線時就會出現這種現象,類似的鋸齒也會發生在三角形的邊緣上。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

圖4.4:我們可以看到,第一條直線帶有明顯的鋸齒(當使用像素矩陣近似地表示一條直線時就會出現階梯效應)。而第二條直線使用了抗鋸齒技術,通過對一個像素周圍的鄰接像素進行采樣得到該像素的最終顔色;這樣可以形成一條較為平滑的直線,使階梯效果得到緩解。

通過提高顯示器的分辨率,縮小像素的尺寸,也可以有效地緩解一問題,使階梯效應明顯降低。

當無法提高顯示器分辨率或分辨率不夠高時,我們可以使用抗鋸齒(antialiasing)技術。其中的一種技術叫做超級采樣(supersampling),它把背景緩沖和深度緩沖的大小提高到螢幕分辨率的4倍。3D場景會以這個更大的分辨率渲染到背景緩存中,當在螢幕上呈現背景緩沖時,背景緩沖會将4個像素的顔色取平均值後得到一個像素的最終顔色。從效果上來說,超級采樣的工作原理就是以軟體的方式提升分辨率。

超級采樣代價昂貴,因為它處理的像素數量和所需的記憶體數量增加為原來的4倍。Direct3D支援另一種稱為多重采樣(multisampling)的抗鋸齒技術,它通過對一個像素的子像素進行采樣計算出該像素的最終顔色,比超級采樣節省資源。假如我們使用的是4X多重采樣(每個像素采樣4個鄰接像素),多重采樣仍然會使用螢幕分辨率4倍大小的背景緩沖和深度緩沖,但是,不像超級采樣那樣計算每個子像素的顔色,而是隻計算像素中心顔色一次,然後基于子像素的可見性(基于子像素的深度/模闆測試)和範圍(子像素中心在多邊形之外還是之内)共享顔色資訊。圖4.5展示了這樣的一個例子。

Introduction to 3D Game Programming with DirectX 11學習筆記 4.1 初識Direct3D

圖4.5:如左圖所示,一個像素與多邊形的邊緣相交,像素中心的綠顔色存儲在可見的三個子像素中,而第4個子像素沒有被多邊形覆寫,是以不會被更新為綠色,它仍保持為原來繪制的幾何體顔色或Clear操作後的顔色。如右圖所示,要獲得最後的像素顔色,我們需要對4個子像素(3個綠色和一個白色)取平均值,獲得淡綠色,通過這個操作,可以減弱多邊形邊緣的階梯效果,實作更平滑的圖像。

注意:supersampling與multisampling的關鍵差別在于:使用supersampling時,圖像的顔色需要通過每個子像素的顔色計算得來,而每個子像素顔色可能不同;使用multisampling(圖4.5)時,每個像素的顔色隻計算一次,這個顔色會填充到所有可見的、被多邊形覆寫的子像素中,即這個顔色是共享的。因為計算圖像的顔色是圖形管線中最昂貴的操作之一,是以multisampling相比supersampling而言節省的資源是相當可觀的。但是,supersampling更為精确,這是multisampling做不到的。

Direct3D中的多重采樣

注意:我們需要為交換鍊緩沖區和深度緩沖區各填充一個DXGI_SAMPLE_DESC結構體。當建立背景緩沖區和深度緩沖區時,必須使用相同的多重采樣設定.

特征等級

d3d11中定義了如下幾個等級以代表不同的d3d版本:

typedef enum D3D_FEATURE_LEVEL {
    D3D_FEATURE_LEVEL_9_1 = ,
    D3D_FEATURE_LEVEL_9_2 = , 
    D3D_FEATURE_LEVEL_9_3 = , 
    D3D_FEATURE_LEVEL_10_0 = ,
    D3D_FEATURE_LEVEL_10_1 = ,
    D3D_FEATURE_LEVEL_11_0 =  
} D3D_FEATURE_LEVEL;
           

特征等級定義了一系列支援不同d3d功能的相應的等級(每個特征等級支援的功能可參見SDK文檔),用意即如果一個使用者的硬體不支援某一特征等級,程式可以選擇較低的等級。例如,為了支援更多的使用者,應用程式可能需要支援Direct3D 11,10.1,9.3硬體。程式會從最新的硬體一直檢查到最舊的,即首先檢查是否支援Direct3D 11,第二檢查Direct3D 10.1,然後是Direct3D 10,最後是Direct3D 9。要設定測試的順序,可以使用下面的特征等級數組(數組内元素的順序即特征等級測試的順序):

D3D_FEATURE_LEVEL featureLevels [] = 
{
    D3D_FEATURE_LEVEL_11_0, // First check D3D 11 support 
    D3D_FEATURE_LEVEL_10_1, // Second check D3D 10.1 support 
    D3D_FEATURE_LEVEL_10_0, // Next,check D3D 10 support 
    D3D_FEATURE_LEVEL_9_3 // Finally,check D3D 9.3 support 
} ;
           

這個數組可以放置在Direct3D初始化方法(4.2.1節)中,方法會輸出數組中第一個可被支援的特征等級。例如,如果Direct3D報告數組中第一個可被支援的特征等級是D3D_FEATURE_LEVEL_10_0,程式就會禁用Direct3D 11和Direct3D 10.1的特征,而使用Direct3D 10的繪制路徑。本書中我們要求必須能支援D3D_FEATURE_LEVEL_11_0。

繼續閱讀