天天看點

DirectX 12 學習之路(三)

一、圖元拓撲 通過指定圖元拓撲(primitive topology,或稱基元拓撲)來告知Direct3D如何用頂點數來表示幾何圖元。

1、點清單 (D3D_PRIMITIVE_TOPOLOGY_POINTLIST)(point list)

所有的頂點都将在繪制調用的過程中被繪制為一個單獨的點。

2、線條帶(D3D_PRIMITIVE_TOPOLOGY_LINESTRIP)(line strip)

頂點将在繪制調用的過程中被連接配接為一系列的連續線段,若有n+1個頂點就會生成n條線段。

3、線清單(D3D_PRIMITIVE_TOPOLOGY_LINELIST)(line list)

每對頂點繪制調用的過程中都會組成單獨的線段,每2n個頂點就會生成n條線段。

線清單和線條帶的差別在于:線清單中的線段可以彼此分開,而線條帶中的線段則相連的。

4、三角形帶(D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP)(triangle strip)

所繪制的三角形會被連接配接成帶狀,處于中間位置的頂點将被相鄰的三角形所共同使用。利用n個頂點即可生成n-2個三角形。

在三角形帶中,次序為偶數的三角形和為奇數的三角形的繞序(windling order,也譯為環繞順序等,即裝配圖元的頂點順序為逆時針或順時針方向)是不同的,這就是剔除(culling,又稱消隐)問題的由來。為了解決這個問題,GPU内部會對偶數三角形中的後(DirectX 中應該是後,OpenGL是前)兩個頂點的順序進行調換,以此使它們與奇數三角形的繞序保持一緻。

5、三角形清單(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)(triangle list)

在繪制調用的過程中會将每3個頂點裝配成獨立的三角形,是以每3n個頂點會生成n個三角形。

三角形清單與三角形帶的差別是:三角形清單中的三角形可以彼此分離,而三角形帶中的三角形則是相連的。

6、具有鄰接資料的圖元拓撲(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ)

對于存有鄰接資料的三角形清單而言,每個三角形都有3個與之相鄰的鄰接三角形(adjacent triangle)。

為了使幾何着色器可以順利地獲得這些鄰接三角形的資訊,我們就需要借助頂點緩沖區與索引緩沖區将它們随主三角形一并送出至渲染流水線。

注意,鄰接圖元的頂點隻能用作幾何着色器的輸入資料,卻并不會被繪制出來 。即使程式沒有用到幾何着色器,但依舊不會繪制鄰接圖元。

6n個頂點可以生成n個三角形及其鄰接資料,線清單、線條帶和三角形帶也存在有鄰接資料的圖元拓撲。

7、控制點面片清單(D3D_PRIMITIVE_TOPOLOGY_N_CONTROL_POINT_PATCHLIST)

将頂點資料解釋為具有N個控制點(control point)的面片清單( patch list)。此圖元常用于渲染流水線的曲面細分階段(tessellation stage)。

二、在計算三角形法線的方法中,根據觀察者的視角去看,頂點繞序為順時針方向的三角形為正面朝向,而頂點繞序為逆時針方向的三角形為背面朝向。但是,通過對Direct3D渲染狀态的設定,我們也可以将這個約定“颠倒”過來。

三、輸入布局描述(input layout description):向Direct3D提供頂點結構體的描述,是它了解應怎樣來處理結構體中的每個成員。通過語義(semantic)将頂點結構體中的元素與頂點着色器輸入簽名(簽名 signature 可以了解為着色器中輸入或輸出參數清單)中的元素一一映射起來。

四、對于靜态幾何體(static geometry,即每一幀都不會發生改變的幾何體)而言,我們會将其頂點緩沖區置于預設堆(D3D12_HEAP_TYPE_DEFAULT)中來優化性能。一般來說,遊戲中的大多數幾何體(如樹木、建築物、地形和動畫角色)都是如此處理。

除了建立頂點緩沖區資源本身外,我們還需要用D3D12_HEAP_TYPE_UPLOAD這種堆類型來建立一個處于中介位置的上傳緩沖區(uplaod buffer)。通過把資源送出至上傳堆,才得以将資料從CPU複制到GPU顯存中。在建立了上傳緩沖區之後,我們就可以将頂點資料從系統記憶體複制到上傳緩沖區,而後再把頂點資料從上傳緩沖區複制到真正的頂點緩沖區中。

五、為了将頂點緩沖區綁定到渲染流水線上,我們需要給這種資源建立一個頂點緩沖區視圖(vertex buffer view)。和RTV(render texture view)不同的是,我們無須為頂點緩沖區視圖建立描述符堆。而且,頂點緩沖區視圖是由D3D12_VERTEX_BUFFER_VIEW結構體來表示。

在頂點緩沖區及其對應的視圖建立完成後,便可以将它與渲染流水線上的一個輸入槽(input slot)相綁定,這樣一來,我們就可以向流水線中的輸入裝配器階段傳遞資料了。通過ID3D12GraphicsCommandList::IASetVertexBuffers()來完成。(IA input Assembler 輸入裝配器)

将頂點緩沖區設定到輸入槽上并不會對其執行實際的繪制操作,而是僅為頂點資料送至渲染流水線做好準備而已。這最後一步是通過ID3DGraphicsCommandList::DrawInstanced()或ID3DGraphicsCommandList::DrawIndexedInstanced()方法真正地繪制頂點。

六、被繪制為點、線清單還是三角形清單,圖元拓撲狀态實由ID3DGraphicsCommandList::IASetPrimitiveToplogy()方法來設定。

七、需要給索引緩沖區資源建立一個索引緩沖區視圖(index buffer view)。和頂點緩沖區視圖一樣,我們也無需給索引緩沖區視圖建立描述符堆。但是索引緩沖區視圖要由結構體D3D12_INDEX_BUFFER_VIEW來表示。

通過ID3D12GraphicsCommandList::IASetIndexBuffer()(IA input Assembler 輸入裝配器)方法即可将索引緩沖區綁定到輸入裝配器階段。

八、HLSL(High Level Shading Language)進階着色語言。在HLSL中,所有的函數都是内聯(inline)函數。

SV_POSITION語義比較特殊(SV表示系統值,system value),它所修飾的頂點着色器輸出元素存有齊次裁剪空間中的頂點位置資訊。因為,我們必須為輸出位置資訊的參數附上SV_POSITION語義,使GPU可以在進行例如裁剪、深度測試和光栅化等處理之時,借此實作其他屬性所無法介入的有關運算。值得注意的是,對于任何不具有系統輸出參數而言,我們都可以根據需求以合法的語義名修飾它。

如果沒有使用幾何着色器,那麼頂點着色器必須使用SV_POSITION語義來輸出頂點在齊次裁剪空間中的位置,因為(在沒有使用幾何着色器的情況下)執行完頂點着色器之後,硬體期望擷取頂點位于齊次裁剪空間之中的坐标。如果使用了幾何着色器,則可以把輸出頂點在齊次裁剪空間中的位置的工作交給它來處理。

在頂點着色器(或幾何着色器)中是無法進行透視除法的,此階段隻能實作投影矩陣這一環節的運算。而透視除法将在後面交由硬體執行。

九、輸入布局中的元素格式确定的是,在資料未進入着色器寄存器之前,應運用何種資料轉換算法來确定各元素的具體格式。而着色器輸入簽名則定義的是,在不修改輸入寄存器中所存資料的情況下,頂點着色器将如何來解釋這些資料的類型。

十、由于硬體的原因,某些像素片段在移送至像素着色器之前,可能已經被渲染流水線所剔除(例如提前深度剔除, early-z rejection,也有譯為早期深度剔除、早期z剔除等)。然而也有一些對情況能夠禁止提前深度剔除優化。比如說,倘若在像素着色器中有對像素深度值進行修改的操作,那麼像素着色器就必須針對每個像素各執行一次,因為在像素着色器修改像素深度值以前,我們并不知道每個像素的最終深度值。

十一、像素着色器傳回一個4D顔色值,而位于此函數參數清單後的SV_TARGET語義則表示該傳回值的類型應當與渲染目标格式(render target format)相比對(該輸出值會被存于渲染目标之中)。

十二、常量緩沖區(constant buffer)是一種GPU資源(ID3D12Resource),其資料内容可供着色器程式所引用。和頂點緩沖區和索引緩沖區不同的是,常量緩沖區通常由CPU每幀更新一次。是以,我們會把常量緩沖區建立到一個上傳堆而非預設堆中,這樣能夠使我們從CPU端更新常量。

常量緩沖區對硬體也有特别的需求:常量緩沖區的大小必須為硬體最小配置設定空間(256B)的整數倍。

十三、Direct3D 12 不僅保證了Map與Unmap函數在多線程中調用的安全性,還令Map函數可以嵌套使用。第一次調用Map函數時,Direct3D會在CPU端配置設定一塊虛拟記憶體位址範圍,用來映射CPU中的資源。而最後一次調用Unmap函數時,則會釋放這塊CPU虛拟位址範圍。Map函數會在必要的時對CPU緩存執行invalidate操作(标記相關緩存無效,令CPU讀取主存中的資料),以使CPU端可以讀取GPU端對這段位址内容所做的修改;相反地,Unmap函數則會在必要時對CPU緩存進行flush操作(令緩存中的資料寫會主存),以令GPU端可以讀取CPU端對這層位址内容所做的修改。

十四、一般來講,物體的世界矩陣将随其移動/旋轉/縮放而改變,觀察矩陣随虛拟錄影機的移動/旋轉而改變,投影矩陣随視窗大小的調整而改變。

十五、常量緩沖區描述符都存放在以D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV類型所建立的描述符堆裡,這種堆内可以混合存儲常量緩沖區描述符、着色器資源描述符和無序通路描述符。建立這種描述符堆和建立渲染目标和深度/模闆緩沖區這兩種資源描述符堆的過程很相似,但是,有一個重要的差別,那就是在建立供着色器程式通路資源的描述符時,我們需要把标志Flags指定為DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE。

十六、通常來講,在繪制調用開始執行之前,我們應将不同的着色器程式所需的各種類型的資源綁定到渲染流水線上。 實際上,不同類型的資源會被綁定到特定的寄存器槽(register slot)上,以供着色器程式通路。

寄存器槽就是向着色器傳遞資源的手段,register(#)中表示寄存器傳遞的資源類型,可以是t(表示着色器資源視圖)、s(采樣器)、u(無序通路視圖)以及b(常量緩沖區視圖),#則為所用的寄存器編号。

十七、根簽名(root signature)定義的是:在執行繪制指令之前,那些應用程式将綁定到渲染流水線上的資源,它們會被映射到着色器的對應輸入寄存器。根簽名一定要與使用它的着色器相相容(即在繪制開始之前,根簽名一定要為着色器提供其執行期間需要綁定到渲染流水線的所有資源),在建立流水線狀态對象(pipeline state object)時會對此進行驗證。不同的繪制調用可能會用到一組不同的着色器程式,這也就意味着要用到不同的根簽名。

如果我們把着色器程式當作一個函數,而将輸入資源看作着色器的函數參數,那麼根簽名則定義了函數簽名(這也就是根簽名一次的由來)。通過綁定不同的資源作為參數,着色器的輸出也将有所差别。例如,頂點着色器的輸出取決于實際向它輸入的頂點資料以及為它綁定的具體資源。

在Direct3D中,根簽名由ID3D12RootSignature接口來表示,并以一組描述繪制調用過程中着色器所需資源的根參數(root parameter)定義而成。根參數可以是根常量(root constant)、根描述符(root descriptor)或者描述符表(descriptor table)。

Direct3D 12規定,必須先将根簽名的描述布局進行序列化處理(serialize),待其轉換為以ID3DBlob接口表示的序列化資料格式後,才可以将它傳入CreateRootSignature方法,正式建立根簽名。

根簽名隻定義了應用程式要綁定到渲染流水線的資源,卻沒有真正地執行任何資源綁定的操作。隻要率先通過指令清單(Command list)設定好根簽名,我們就能用ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable()方法令描述符表與渲染流水線相綁定。

處于性能考慮,我們應當使根簽名的規模盡可能的小,除此之外,還要試着盡量減少每幀渲染過程中根簽名的修改次數。

十八、光栅化狀态不是可程式設計的,隻能接受配置。由結構體D3D12_RASTERIZER_DESC來表示。

十九、大多數控制圖形流水線狀态的對象被統稱為流水線狀态對象(pipeline state object,PSO),用接口ID3D12PipelineState來表示,要建立PSO,先填寫一份描述其細節的D3D12_GRAPHICS_PIPELINE_STATE_DESC結構體執行個體。

ID3D12PipelineState對象集合了大量的流水線狀态資訊,為了保證性能,我們将所有這些對象都集合在一起,一并送至渲染流水線上。通過這樣一個集合,Direct3D便可以确定所有的狀态是否彼此相容,而驅動程式則能夠據此而提前生成硬體本地指令及其狀态。在Direct3D 11的狀态模型中,這些渲染狀态片段都是要分開配置的,然而這些狀态實際都有一定的聯系,以緻如果其中一個狀态發生改變,那麼驅動程式可能要為了另一個相關的獨立狀态而對硬體重新進行程式設計。由于一些狀态在配置流水線時需要改變,因而硬體狀态也就可能被頻繁地改寫。為了避免這些備援的操作,驅動程式往往會推遲針對硬體狀态的程式設計動作,直到明确整條流水線的狀态發起繪制調用後,才正式生成對應的本地指令與狀态。但是,這種延遲操作需要驅動在運作時進行額外的記錄工作,即追蹤狀态的變化,而後才能在運作時生成改寫硬體狀态的本地代碼。在Direct3D 12的新模型中,驅動程式可以在初始化期間生成對流水線狀态程式設計的全部代碼,這便是我們将大多數的流水線狀态指定為一個集合所帶來的好處。

Direct3D實質上是一種狀态機(state machine),裡面的事物會保持他們各自的狀态,直到我們将其改變。

考慮到性能問題,我們應當盡量減少改變PSO狀态的次數,為此,若能以一個PSO繪制出所有的物體,絕不用第二個PSO。

切記,不要在每次繪制調用時都修改PSO!!!

繼續閱讀