DirectX11 Direct3D基本概念
Direct3D基本概念
1. Direct3D概述
Direct3D是一種底層繪圖API(application programming interface,應用程序接口),它可以讓我們可以通過3D硬件加速繪制3D世界。從本質上講,Direct3D提供的是一組軟件接口,我們可以通過這組接口來控制繪圖硬件。例如,要命令繪圖設備清空渲染目標(例如屏幕),我們可以調用Direct3D的ID3D11DeviceContext::ClearRenderTargetView方法來完成這一工作。Direct3D層位于應用程序和繪圖硬件之間,這樣我們就不必擔心3D硬件的實現細節,只要設備支持Direct3D 11,我們就可以通過Direct3D 11 API來控制3D硬件了。
支持Direct3D 11的設備必須支持Direct3D 11規定的整個功能集合以及少數的額外附加功能(有一些功能,比如多重采樣數量,仍然需要以查詢方式實現,這是因為不同的Direct3D硬件這個值可能并不一樣)。在Direct3D 9中,設備可以只支持Direct3D 9的部分功能;所以,當一個Direct3D 9應用程序要使用某一特性時,應用程序就必須先檢查硬件是否支持該特性。如果要調用的是一個不為硬件支持Direct3D函數,那應用程序就會出錯。而在Direct3D 11中,不需要再做這種設備功能檢查,因為Direct3D 11強制要求設備實現Direct3D 11規定的所有功能特性。
2. 關于COM(組件對象模型)接口
組件對象模型(COM)技術使DirectX獨立于任何編程語言,并具有版本向后兼容的特性。我們經常把COM對象稱為接口,并把它當成一個普通的C++類來使用。當使用C++編寫DirectX程序時,許多COM的底層細節都不必考慮。唯一需要知道的一件事情是,我們必須通過特定的函數或其他的COM接口方法來獲取指向COM接口的指針,而不能用C++的new關鍵字來創建COM接口。另外,當我們不再使用某個接口時,必須調用它的Release方法來釋放它(所有的COM接口都繼承于IUnknown接口,而Release方法是IUnknown接口的成員),而不能用delete語句——COM對象在其自身內部實現所有的內存管理工作。
當然,有關COM的細節還有很多,但是在實際工作中只需知道上述內容就足以有效地使用DirectX了。
注意:COM接口都以大寫字母“I”為前綴。例如,表示2D紋理的接口為ID3D11Texture2D。
3. 紋理和數據資源格式
2D紋理(texture)是一種數據元素矩陣。2D紋理的用途之一是存儲2D圖像數據,在紋理的每個元素中存儲一個像素顏色。但這不是紋理的唯一用途;例如, 有一種稱為法線貼圖映射(normal mapping)的高級技術在紋理元素中存儲的不是顏色,而是3D向量。因此,從通常意義上講,紋理用來存儲圖像數據,但是在實際應用中紋理可以有更廣泛的用途。1D紋理類似于一個1D數據元素數組,3D 紋理類似于一個3D數據元素數組。但是在隨后的章節中我們會講到,紋理不僅僅是一個數據數組;紋理可以帶有多級漸近紋理層(mipmap level),GPU可以在紋理上執行特殊運算,比如使用過濾器(filter)和多重采樣(multisampling)。此外,不是任何類型的數據都能存儲到紋理中的;紋理只支持特定格式的數據存儲,這些格式由DXGI_FORMAT枚舉類型描述。一些常用的格式如下:
DXGI_FORMAT_R32G32B32_FLOAT:每個元素包含3個32位浮點分量。
DXGI_FORMAT_R16G16B16A16_UNORM:每個元素包含4個16位分量,分量的取值范圍在[0,1]區間內。
DXGI_FORMAT_R32G32_UINT:每個元素包含兩個32位無符號整數分量。
DXGI_FORMAT_R8G8B8A8_UNORM:每個元素包含4個8位無符號分量,分量的取值范圍在[0,1]區間內的浮點數。
DXGI_FORMAT_R8G8B8A8_SNORM:每個元素包含4個8位有符號分量,分量的取值范圍在[?1,1] 區間內的浮點數。
DXGI_FORMAT_R8G8B8A8_SINT:每個元素包含4個8位有符號整數分量,分量的取值范圍在[?128, 127] 區間內的整數。
DXGI_FORMAT_R8G8B8A8_UINT:每個元素包含4個8位無符號整數分量,分量的取值范圍在[0, 255]區間內的整數。
注意,字母R、G、B、A分別表示red(紅)、green(綠)、blue(藍)和alpha(透明度)。每種顏色都是由紅、綠、藍三種基本顏色組成的(例如,黃色是由紅色和綠色組成的)。alpha通道(或alpha分量)用于控制透明度。不過,正如我們之前所述,紋理存儲的不一定是顏色信息;例如,格式DXGI_FORMAT_R32G32B32_FLOAT包含3個浮點分量,可以存儲一個使用浮點坐標的3D向量。另外,還有一種弱類型(typeless)格式,可以預先分配內存空間,然后在紋理綁定到管線時再指定如何重新解釋數據內容(這一過程與C++中的數據類型轉換頗為相似);例如,下面的弱類型格式為每個元素預留4個8位分量,且不指定數據類型(例如:整數、浮點數、無符號整數):
DXGI_FORMAT_R8G8B8A8_TYPELESS
4. 交換鏈和頁面交換
為了避免在動畫中出現閃爍,最好的做法是在一個離屏(off-screen)紋理中執行所有的動畫幀繪制工作,這個離屏紋理稱為后臺緩沖區(back buffer)。當我們在后臺緩沖區中完成給定幀的繪制工作后,便可以將后臺緩沖區作為一個完整的幀顯示在屏幕上;使用這種方法,用戶不會察覺到幀的繪制過程,只會看到完整的幀。從理論上講,將一幀顯示到屏幕上所消耗的時間小于屏幕的垂直刷新時間。硬件會自動維護兩個內置的紋理緩沖區來實現這一功能,這兩個緩沖區分別稱為前臺緩沖區(front buffer)和后臺緩沖區。前臺緩沖區存儲了當前顯示在屏幕上的圖像數據,而動畫的下一幀會在后臺緩沖區中執行繪制。當后臺緩沖區的繪圖工作完成之后,前后兩個緩沖區的作用會發生翻轉:后臺緩沖區會變為前臺緩沖區, 而前臺緩沖區會變為后臺緩沖區,為下一幀的繪制工作提前做準備。我們將前后緩沖區功能互換的行為稱做呈現(presenting)。提交是一個運行速度很快的操作,因為它只是將前臺緩沖區的指針和后臺緩沖區的指針做了一個簡單的交換。
(我們首先渲染緩沖區B,它是當前的后臺緩沖區。一旦幀渲染完成,前后緩沖區的指針會相互交換,緩沖區B會變為前臺緩沖區,而緩沖區A會變為新的后臺緩沖區。之后,我們將在緩沖區A中進行下一幀的渲染。一旦幀渲染完成,前后緩沖區的指針會再次進行交換,緩沖區A會變為前臺緩沖區,而緩沖區B會再次變為后臺緩沖區。)
前后緩沖區形成了一個交換鏈(swap chain)。在Direct3D中,交換鏈由IDXGISwapChain接口表示。該接口保存了前后緩沖區紋理,并提供了用于調整緩沖區尺寸的方法(IDXGISwapChain::ResizeBuffers)和呈現方法(IDXGISwapChain::Present)。我們會在4.4節中詳細討論些方法。
使用(前后)兩個緩沖區稱為雙緩沖(double buffering)。緩沖區的數量可多于兩個;比如,當使用三個緩沖區時稱為三緩沖(triple buffering)。不過,兩個緩沖區已經足夠用了。
注意:雖然后臺緩沖區是一個紋理(紋理元素稱為texel),但是我們更習慣于將紋理元素稱為像素(pixel),因為后臺緩沖區存儲的是顏色信息。有時,即使紋理中存儲的不是顏色信息,人們還是會將紋理元素稱為像素(例如,“法線貼圖像素”)。
5. 深度緩沖區
深度緩沖區(depth buffer)是一個不包含圖像數據的紋理對象。在一定程度上,深度信息可以被認為是一種特殊的像素。常見的深度值范圍在0.0到1.0之間,其中0.0表示離觀察者最近的物體,1.0表示離觀察者最遠的物體。深度緩沖區中的每個元素與后臺緩沖區中的每個像素一一對應(即,后臺緩沖區的第ij個元素對應于深度緩沖區的第ij個元素)。所以,當后臺緩沖區的分辨率為1280×1024時,在深度緩沖區中有1280×1024個深度元素。
為了判定物體的哪些像素位于其他物體之前,Direct3D使用了一種稱為深度緩存(depth buffering)或z緩存(z-buffering)的技術。我們所要強調的是在使用深度緩存時,我們不必關心所繪物體的先后順序。
注意:要處理深度的問題,有人可能會建議按照從遠至近的順序繪制場景中的物體。使用這種方法,離得近的物體會覆蓋在離得遠的物體之上,這樣就會產生正確的繪制結果,這也是畫家作畫時用到的方法。但是,這種方法會導致另一個問題——如何將大量的物體和相交的幾何體按從遠到近的方式進行排序?此外,圖形硬件本身就提供了深度緩存供我們使用,因此我們不會采用畫家算法。
為了說明深度緩存的工作方式,讓我們來看一個例子。如圖所示,它展示的是觀察者看到的立體空間(左圖)以及該立體空間的2D側視圖(右圖)。從這個圖中我們可以發現,3個不同的像素會被渲染到視圖窗口的同一個像素點P上。(當然,我們知道只有最近的像素會被渲染到P上,因為它擋住了后面的其他像素,可是計算機不知道這些事情。)首先,在渲染之前,我們必須把后臺緩沖區清空為一個默認顏色(比如黑色或白色),把深度緩沖區清空為默認值——通常設為1.0(像素所具有的最遠深度值)。
(圖窗口相當于從3D場景生成的2D圖像(后臺緩沖區)。我們看到,有3個不同的像素可以被投影到像素P上。直覺告訴我們,P1是P的最終顏色,因為它離觀察者最近,而且遮擋了其他兩個像素。深度緩沖區算法提供了一種可以在計算機上實現的判定過程。注意,我們所說的深度值是相對于觀察坐標系而言的。實際上,當深度值存入深度緩沖區時,它會被規范到[0.0,1.0]區間內。)
現在,假設物體的渲染順序依次為:圓柱體、球體和圓錐體。下面的表格匯總了在繪制些物體時像素P及相關深度值的變化過程;其他像素的處理過程與之類似。
當我們發現某個像素具有更小的深度值時,就更新該像素以及它在深度緩沖區中的相應深度值。通過一方式,在最終得到的渲染結果中只會包含那些離觀察者最近的像素。(如果讀者對此仍有疑慮,那么可以試著交換本例的繪圖順序,看看得到的計算結果是否相同。)
綜上所述,深度緩沖區用于為每個像素計算深度值和實現深度測試。深度測試通過比較像素深度來決定是否將該像素寫入后臺緩沖區的特定像素位置。只有離觀察者最近的像素才會勝出,成為寫入后臺緩沖區的最終像素。這很容易理解,因為離觀察者最近的像素會遮擋它后面的其他像素。
深度緩沖區是一個紋理,所以在創建它時必須指定一種數據格式。用于深度緩存的格式如下:
DXGI_FORMAT_D32_FLOAT_S8X24_UINT:32位浮點深度緩沖區。為模板緩沖區預留8位(無符號整數),每個模板值的取值范圍為[0,255]。其余24位閑置。
DXGI_FORMAT_D32_FLOAT:32位浮點深度緩沖區。
DXGI_FORMAT_D24_UNORM_S8_UINT:無符號24位深度緩沖區,每個深度值的取值范圍為[0,1]。為模板緩沖區預留8位(無符號整數),每個模板值的取值范圍為[0,255]。
DXGI_FORMAT_D16_UNORM:無符號16位深度緩沖區,每個深度值的取值范圍為[0,1]。
注意:模板緩沖區對應用程序來說不是必須的,但是如果用到了模板緩沖區,那么模板緩沖區必定是與深度緩沖區存儲在一起的。例如,32位格式DXGI_FORMAT_D24_UNORM_S8_UINT使用24位用于深度緩沖區,8位用于模板緩沖區。 所以,將深度緩沖區稱為“深度/模板緩沖區”更為合適。模板緩沖區是一個比較高級的主題,我們會在第10章講解模板緩沖區的用法。
6. 紋理資源視圖
紋理可以被綁定到渲染管線(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視圖。只要你試一下就會發現Direct3D會給出如下調試錯誤:
ERROR: ID3D11Device::CreateDepthStencilView:
A DepthStencilView cannot be created of a Resource that did not specify D3D10_BIND_DEPTH_STENCIL.
注意:2009年8月的SDK文檔指出:“當創建資源時,為資源指定強類型(fully-typed)格式,把資源的用途限制在格式規定的范圍內,有利于提高運行時環境對資源的訪問速度……”。所以,你只應該在真正需要弱類型資源時(使用弱類型的優點是可以使用不同的視圖將數據用于不同的用途),才創建弱類型資源;否則,應盡量創建強類型資源。
7. 多重采樣
超級采樣抗鋸齒(Super-Sampling Anti-aliasing,簡稱SSAA)此是早期抗鋸齒方法,比較消耗資源,但簡單直接,先把圖像映射到緩存并把它放大,再用超級采樣把放大后的圖像像素進行采樣,一般選取2個或4個鄰近像素,把這些采樣混合起來后,生成的最終像素,令每個像素擁有鄰近像素的特征,像素與像素之間的過渡色彩,就變得近似,令圖形的邊緣色彩過渡趨于平滑。再把最終像素還原回原來大小的圖像,并保存到幀緩存也就是顯存中,替代原圖像存儲起來,最后輸出到顯示器,顯示出一幀畫面。這樣就等于把一幅模糊的大圖,通過細膩化后再縮小成清晰的小圖。如果每幀都進行抗鋸齒處理,游戲或視頻中的所有畫面都帶有抗鋸齒效果。
多重采樣抗鋸齒(MultiSampling Anti-Aliasing,簡稱MSAA)是一種特殊的超級采樣抗鋸齒(SSAA)。MSAA首先來自于OpenGL。具體是MSAA只對Z緩存(Z-Buffer)和模板緩存(Stencil Buffer)中的數據進行超級采樣抗鋸齒的處理。可以簡單理解為只對多邊形的邊緣進行抗鋸齒處理。這樣的話,相比SSAA對畫面中所有數據進行處理,MSAA對資源的消耗需求大大減弱,不過在畫質上可能稍有不如SSAA。
(如左圖所示,一個像素與多邊形的邊緣相交,像素中心的綠顏色存儲在可見的三個子像素中,而第4個子像素沒有被多邊形覆蓋,因此不會被更新為綠色,它仍保持為原來繪制的幾何體顏色或Clear操作后的顏色。如右圖所示,要獲得最后的像素顏色,我們需要對4個子像素(3個綠色和一個白色)取平均值,獲得淡綠色,通過這個操作,可以減弱多邊形邊緣的階梯效果,實現更平滑的圖像。)
注意:在上圖中,我們用標準的網格圖形表示一個像素的4個子像素,但由于硬件的不同,實際的子像素放置圖形也是不同的,Direct3D并不定義子像素的放置方式,在特定情況下,某些放置方式會優于其他的放置方式。
8. Direct3D中的多重采樣
我們要填充一個DXGI_SAMPLE_DESC結構體。該結構體包含兩個成員,其定義如下:
Count成員用于指定每個像素的采樣數量,Quality成員用于指定希望得到的質量級別(不同硬件的質量級別表示的含義不一定相同)。質量級別越高,占用的系統資源就越多,所以我們必須在質量和速度之間權衡利弊。質量級別的取值范圍由紋理格式和單個像素的采樣數量決定。我們可以使用如下方法,通過指定紋理格式和采樣數量來查詢相應的質量級別:
HRESULT ID3D11Device::CheckMultisampleQualityLevels(DXGI_FORMAT Format, UINT SampleCount, UINT *pNumQualityLevels);如果紋理格式和采樣數量的組合不被設備支持,則該方法返回0。反之,通過pNumQualityLevels參數返回符合給定的質量等級數值。有效的質量級別范圍為0到pNumQualityLevels?1。
采樣的最大數量可以由以下語句定義:
#define D3D11_MAX_MULTISAMPLE_SAMPLE_COUNT(32)采樣數量通常使用4或8,可以兼顧性能和內存消耗。如果你不使用多重采樣,可以將采樣數量設為1,將質量級別設為0。所有符合Direct3D 11功能特性的設備都支持用于所有渲染目標格式的4X多重采樣。
注意:我們需要為交換鏈緩沖區和深度緩沖區各填充一個DXGI_SAMPLE_DESC結構體。當創建后臺緩沖區和深度緩沖區時,必須使用相同的多重采樣設置。
8. 特征級別
Direct3D 11提出了特征等級(feature levels,在代碼中由枚舉類型D3D_FEATURE_LEVEL表示)的概念,對應了定義了d3d11中定義了如下幾個等級以代表不同的d3d版本:
特征等級定義了一系列支持不同d3d功能的相應的等級(每個特征等級支持的功能可參見SDK文檔),用意即如果一個用戶的硬件不支持某一特征等級,程序可以選擇較低的等級。例如,為了支持更多的用戶,應用程序可能需要支持Direct3D 11,10.1,9.3硬件。程序會從最新的硬件一直檢查到最舊的,即首先檢查是否支持Direct3D 11,第二檢查Direct3D 10.1,然后是Direct3D 10,最后是Direct3D 9。要設置測試的順序,可以使用下面的特征等級數組(數組內元素的順序即特征等級測試的順序):
D3D_FEATURE_LEVEL featureLevels [4] = {D3D_FEATURE_LEVEL_11_0, // First check D3D 11 supportD3D_FEATURE_LEVEL_10_1, // Second check D3D 10.1 supportD3D_FEATURE_LEVEL_10_0, // Next,check D3D 10 supportD3D_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的繪制路徑。
總結
以上是生活随笔為你收集整理的DirectX11 Direct3D基本概念的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 国家市场监督管理总局公布《国家标准管理办
- 下一篇: Qt中使用Direct3D