主頁 >  其他 > DirectX11 With Windows SDK--33 曲面細分階段(Tessellation)

DirectX11 With Windows SDK--33 曲面細分階段(Tessellation)

2020-09-13 21:42:54 其他

前言

曲面細分是Direct3D 11帶來的其中一項重要的新功能,它引入了兩個可編程著色器階段以及一個固定的鑲嵌處理程序,簡單來說,曲面細分技術可以將幾何體細分為更小的三角形,并以某種方式把這些新生成的頂點偏移到合適的位置,從而以增加三角形數量的方式豐富網格細節,但為什么不在創建網格之初就直接賦予它高模(high-poly,高面數多邊形)的細節呢?以下是使用曲面細分的3個理由:

  1. 基于GPU實作動態LOD(Level of Detail,細節級別),可以根據網格與攝像機的距離或依據其他因素來調整其細節,比如說,若網格離攝像機過遠,則按高模的規格對它進行渲染將是一種浪費,因為在那個距離我們根本看不清網格的所有細節,隨著物體與攝像機之間距離的拉緊,我們就能連續地對它鑲嵌細分,以增加物體的細節,
  2. 物理模擬與影片特效,我們可以在低模(low-poly,低面數多邊形)網格上執行物理模擬與影片特效的相關計算,再以鑲嵌畫處理手段來獲取細節上更加豐富的網格,這種降低物理模擬與影片特效計算量的做法能夠節省不少的計算資源,
  3. 節約記憶體,我們可以在各種存盤器(磁盤、RAM、VRAM)中保存低模網格,再根據需求用GPU動態地對網格進行鑲嵌細分,

曲面細分技術涉及到的三個階段都是可選的,但如果要使用曲面細分,這三個階段都是必須要經歷的,

學習目標:

  1. 了解曲面細分所用的面片圖元型別,
  2. 理解曲面細分階段中的每個步驟都做了什么,它們所需的輸入及輸出又分別是哪種資料
  3. 通過撰寫外殼著色器與域著色器程式來對幾何圖形進行鑲嵌化細分
  4. 熟悉不同的細分策略,以便于在鑲嵌化處理的時候選擇出最適當的方案,除此之外,還需要了解硬體曲面細分的性能
  5. 學習貝塞爾曲線與貝塞爾曲面的數學描述,并在曲面細分階段將它們予以實作

DirectX11 With Windows SDK完整目錄

Github專案原始碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報,

曲面細分的圖元型別

在進行曲面細分時,我們并不向IA(輸入裝配)階段提交三角形,而是提交具有若干控制點面片,Direct3D支持具有1~32個控制點的面片,并以下列圖元型別進行描述:

D3D_PRIMITIVE_TOPOLOGY_1_CONTROL_POINT_PATCHLIST	= 33,
D3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST	= 34,
D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST	= 35,
...
D3D_PRIMITIVE_TOPOLOGY_31_CONTROL_POINT_PATCHLIST	= 63,
D3D_PRIMITIVE_TOPOLOGY_32_CONTROL_POINT_PATCHLIST	= 64,

由于可以將三角形看作是擁有3個控制點的三角形面片(D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST),所以我們依然可以提交需要鑲嵌化處理的普通三角形網格,對于簡單的四邊形面片而言,則只需要提交4個控制點的面片(D3D_PRIMITIVE_4_CONTROL_POINT_PATCH)即可,這些面片最終也會在曲面細分階段經過鑲嵌化處理而分解為多個三角形,

注意:D3D_PRIMITIVE_TOPOLOGY列舉項描述輸入裝配階段中的頂點型別,而D3D_PRIMITIVE列舉項則描述的是外殼著色器的輸入圖元型別,

那么,具有更多控制點的面片又有什么用處呢?控制點的概念來自于特定種類數學角度上特定曲線或曲面的構造程序,如果在類似于Adobe Illustrator這樣的繪圖程式中使用過貝塞爾曲線工具,那讀者一定會知道要通過控制點才能描繪出曲線形狀,在數學上,可以利用貝塞爾曲線來生成貝塞爾曲面,舉個例子,我們可以用9個控制點或16個控制點來創建一個貝塞爾四邊形面片,所用的控制點越多,我們對面片形狀的控制也就越隨心所欲,因此,這一切圖元控制型別都是為了給這些不同種類的曲線、曲面的繪制提供支持,

曲面細分與頂點著色器

在我們向渲染管線提交了面片的控制點后,它們就會被推送至頂點著色器,這樣一來,在開始曲面細分的時候,頂點著色器就徹底淪為“處理控制點的著色器”,正因為如此,我們還能在曲面細分開始之前,對控制點進行一些調整,一般來說,影片與物理模擬的計算作業都會在對幾何體進行鑲嵌化處理之前的頂點著色器中以較低的頻次進行(鑲嵌化處理之后,頂點增多,處理的頻次也將隨之增加),

外殼著色器

外殼著色器是由兩種著色器共同組成的:常量外殼著色器(Constant Hull Shader)控制點外殼著色器(Control Point Hull Shader)

常量外殼著色器

常量外殼著色器會針對每個面片統一進行處理(即每處理一個面片就被呼叫一次),它的任務是輸出當前網格曲面細分因子,而且必須要輸出,曲面細分因子指示了在曲面細分階段中將面片鑲嵌處理后的份數,以及怎么進行細分,它由兩個輸出系統值所表示:SV_TessFactorSV_InsideTessFactor,這兩個系統值屬于float或float陣列的型別,具體取決于輸入裝配階段定義的圖元型別,常量外殼著色器的輸出被限制在128個標量(如32個4D單精度浮點向量),這意味著除了系統值,你還可以額外添加輸出資訊供每個面片所使用,下面是一個具有3個控制點的四邊形面片示例,我們通過常量緩沖區來為其設定各個方面的細分程度:

struct QuadPatchTess
{
    float EdgeTess[4] : SV_TessFactor;
    float InsideTess[2] : SV_InsideTessFactor;
    
    // 可以在下面為每個面片附加所需的額外資訊
};

QuadPatchTess QuadConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
    QuadPatchTess pt;
    
    pt.EdgeTess[0] = g_QuadEdgeTess[0];			// 四邊形面片的左側邊緣
    pt.EdgeTess[1] = g_QuadEdgeTess[1];			// 四邊形面片的上側邊緣
    pt.EdgeTess[2] = g_QuadEdgeTess[2];			// 四邊形面片的右側邊緣
    pt.EdgeTess[3] = g_QuadEdgeTess[3];			// 四邊形面片的下冊邊緣
    pt.InsideTess[0] = g_QuadInsideTess[0];		// u軸(四邊形內部細分的列數)
    pt.InsideTess[1] = g_QuadInsideTess[1];		// v軸(四邊形內部細分的行數)
    
    return pt;
}

其中InputPatch<VertexOut, 4>定義了控制點的數目和資訊,前面提到,控制點首先會傳至頂點著色器,因此它們的型別由頂點著色器的輸出型別VertexOut來確定,在此例中,我們的面片擁有4個控制點,所以就將InputPatch模板第二個引數指定為4,系統還通過SV_PrimitiveID語意提供了面片的ID值,此ID唯一地標識了繪制呼叫程序中的各個面片,我們可以根據具體的需求來運用它,

但按左上右下的順序來控制邊緣細分是建立在使用下面的頂點擺放順序而言的:

XMFLOAT3 quadVertices[4] = {
    XMFLOAT3(-0.54f, 0.72f, 0.0f),	// 左上角
    XMFLOAT3(0.54f, 0.72f, 0.0f),	// 右上角
    XMFLOAT3(-0.54f, -0.72f, 0.0f),	// 左下角
    XMFLOAT3(0.54f, -0.72f, 0.0f)	// 右下角
};

四邊形面片(quad)進行鑲嵌化處理的程序由兩個構成:

  1. 4個邊緣曲面細分因子控制著對應邊緣鑲嵌后的份數
  2. 兩個內部曲面細分因子指示了如何來對該四邊形面片的內部進行鑲嵌化處理(其中一個針對四邊形的橫向維度,另一個則作用于四邊形的縱向維度)

三角形面片(tri)進行鑲嵌化處理的程序同樣分為兩部分:

  1. 3個邊緣曲面細分因子控制著對應邊緣鑲嵌后的份數
  2. 一個內部曲面細分因子指示著三角形面片內部的鑲嵌份數,

等值線(isoline)進行鑲嵌化處理的程序如下:

  1. 2個邊緣細分因子控制著等值線如何進行鑲嵌,第一個值暫時不知道作用(忽略),第二個用于控制兩個相鄰控制點之間分成多少段,

Direct3D 11硬體所支持的最大曲面細分因子為64(D3D11_TESSELLATOR_MAX_TESSELLATION_FACTOR).如果把所有的曲面細分因子都設定為0,則該面片會被后續的處理階段所丟棄,這就使得我們能夠以每個面片為基準來實作如視錐體剔除與背面剔除這類優化,

  1. 如果面片根本沒有出現在視錐體范圍內,那么就能將它從后續的處理中丟棄(倘若已經對該面片進行了鑲嵌化處理,那么其細分后的各三角形將在三角形裁剪期間被拋棄)
  2. 如果面片是背面朝向的,那么就能將其從后面的處理程序中丟棄(如果該面片已經過了鑲嵌化處理,則其細分后的所有三角形會在光柵化階段的背面剔除程序中被拋棄)

一個問題自然而然地復現出來:到底應該執行幾次鑲嵌化處理才合適?前面提到,曲面細分的基本想法就是為了豐富網格的細節,但是,如果用戶對此無感,我們就不需要對它增添細節了,以下是一些確定鑲嵌次數的常用衡量標準,

  1. 根據與攝像機之間的距離:物體與攝像機的距離越遠,能分辨的細節就越少,因此,我們在兩者距離較遠的時候渲染物體的低模版本,并隨著兩者逐漸接近而逐步對物體進行更加細致的鑲嵌化細分,
  2. 根據占用螢屏的范圍:可以先估算出物體覆寫螢屏的像素個數,如果數量比較少,則渲染物體的低模版本,隨著物體占用螢屏范圍的增加,我們便可以逐漸增大鑲嵌化細分因子,
  3. 根據三角形的朝向:三角形相對于觀察者的朝向也被納入考慮的范疇之中,位于物體輪廓邊線上的三角形勢必比其他位置的三角形擁有更多的細節,
  4. 根據粗糙程度:粗糙不平的表面較光滑的表面需要進行更為細致的曲面細分處理,通過對表面紋理進行檢測可以預算出相應的粗糙度資料,繼而來決定鑲嵌化處理的次數,

[Story10(可點擊)]給出了以下幾點關于性能的建議,

  1. 如果曲面細分因子為1(這個數字意味著該面片不必細分),那么就考慮在渲染此面片時不對它進行細分處理;否則,便會在曲面細分階段白白浪費GPU資源,因為在此階段并不對其執行任何操作,
  2. 考慮到性能又涉及GPU對曲面細分的具體實作,所以不要對小于8個像素這種過小的三角形進行鑲嵌化處理,
  3. 使用曲面細分技術時要采用批繪制呼叫(batch draw call,即盡量將曲面細分任務集中執行)(在繪制呼叫之間往復開啟、關閉曲面細分功能的代價極其高昂),

控制點外殼著色器

控制點外殼著色器以大量的控制點作為輸入與輸出,頂點著色器每輸出一個控制點,此著色器都會被呼叫一次,控制點外殼著色器的應用之一是改變曲面的表示方式,比如把一個普通的三角形(向渲染管線提交的3個控制點)轉換為3次貝塞爾三角形面片,例如,假設我們像平常那樣利用三角形對網格進行建模,就可以通過控制點外殼著色器,把這些三角形轉換為具有10個控制點的高階三次貝塞爾三角形面片,新增的控制點不僅會帶來更加豐富的細節,而且能將三角形面片鑲嵌細分為用戶所期望的份數,這一策略被稱之為N-patches方法(法線—面片方法,normal-patches scheme)或PN三角形方法(即(曲面)點—法線三角形方法,point-normal triangles,簡寫為PN triangles scheme)[Vlachos],由于這種方案只需用曲面細分技術來改進存在的三角形網格,且無需改動美術制作流程,所以實作起來比較方便,對于本章前面兩個演示案例來說,控制點外殼著色器僅充當一個簡單的傳遞著色器,它不會對控制點進行任何的修改,

注意:驅動程式可能會對傳遞著色器進行檢測與優化,

struct VertexOut
{
    float3 PosL : POSITION;
};

typedef VertexOut HullOut;

// Tessellation_Quad_Integer_HS.hlsl

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("QuadConstantHS")]
[maxtessfactor(64.0f)]
float3 HS(InputPatch<VertexOut, 4> patch, uint i : SV_OutputControlPointID, uint patchId : SV_PrimitiveID) : POSITION
{
    return patch[i].PosL;
}

通過InputPatch引數可以將面片的所有控制點都傳至外殼著色器中,系統值SV_OutputControlPointID索引的是正在被外殼著色器處理的輸出控制點,值得注意的是,輸入的控制點數量與輸出的控制點數量未必相同,例如,輸入的面片可能僅含有4個控制點,而輸出的面片卻能夠擁有16個控制點;這些多出來的控制點可以由輸入的4個控制點所衍生,

上面的控制點外殼著色器還用到了以下幾種屬性,

  1. domain:面片的型別,可選用的引數有tri(三角形面片)、quad(四邊形面片)或isoline(等值線)

  2. partioning:指定了曲面細分的細分模式,

    • integer:新頂點的添加或移除依據的是上取整的函式,例如我們將細分值設為3.25f時,實際上它將會細分為4份,這樣一來,在網格隨著曲面細分級別而改變時,會容易發生明顯的躍變,
    • 非整型曲面細分(fractional_even/fractional_odd):新頂點的增加或移除取決于曲面細分因子的整數部分,但是細微的漸變“過渡”調整就要根據細分因子的小數部分,當我們希望將粗糙的網格經曲面細分而平滑地過渡到具有更加細節的網格時,該引數就派上用場了,
    • pow2:目前測驗的時候行為和integer一致,不知道什么原因,這里暫時不講述,
  3. outputtopology:通過細分所創的三角形的繞序

    • triangle_cw:順時針方向的繞序
    • triangle_ccw:逆時針方向的繞序
    • line:針對線段的曲面細分
  4. outputcontrolpoints:外殼著色器執行的次數,每次執行都輸出1個控制點,系統值SV_OutputControlPointID給出的索引標明了當前正在作業的外殼著色器所輸出的控制點,

  5. patchconstantfunc:指定常量外殼著色器函式名稱的字串

  6. maxtessfactor:告知驅動程式,用戶在著色器中所用的曲面細分因子的最大值,如果硬體知道了此上限,就可以了解曲面細分所需的資源,繼而在后臺對此進行優化,Direct3D 11硬體支持的曲面細分因子最大值為64

鑲嵌器階段

程式員無法對鑲嵌器這一階段進行任何控制,因為這一步的操作全權交給硬體處理,此環節會基于常量外殼著色器程式所輸出的曲面細分因子,對面片進行鑲嵌化處理,

四邊形面片的曲面細分示例

  1. integer模式

  1. fractional_odd模式:

  1. fractional_even模式:

三角形面片的曲面細分示例

域著色器

鑲嵌器階段會輸出新建的所有頂點與三角形,在此階段所創建的頂點,都會逐一呼叫域著色器進行后續處理,隨著曲面細分功能的開啟,頂點著色器便化身為“處理每個控制點的頂點著色器”,而外殼著色器的本質則為“針對已經鑲嵌化的面片進行處理的頂點著色器”,特別是,我們可以在此將經過鑲嵌化處理的面片頂點投射到齊次裁剪空間,

首先是三角形面片,域著色器以曲面細分因子(還有一些來自常量外殼著色器所輸出的每個面片的附加資訊)、控制點外殼著色器所輸出的所有面片控制點、鑲嵌化處理后的頂點位置引數(以重心坐標系(alpha, beta, gamma)的形式表示)作為輸入,注意,域著色器給出的并不是鑲嵌化處理后的實際頂點位置,而是這些點位于面片域空間內的引數坐標,是否利用這些引數坐標及控制點來求取真正的3D頂點位置,完全取決于用戶自己,下面展示了前面的例子顯示的三角形所用到的域著色器代碼:

struct VertexOut
{
    float3 PosL : POSITION;
};

typedef VertexOut HullOut;

// Tessellation_Triangle_DS.hlsl

[domain("tri")]
float4 DS(TriPatchTess patchTess,
    float3 weights : SV_DomainLocation,
    const OutputPatch<HullOut, 3> tri) : SV_POSITION
{
    // 重心坐標系插值
    float3 pos = tri[0].PosL * weights[0] +
        tri[1].PosL * weights[1] +
        tri[2].PosL * weights[2];
    
    return float4(pos, 1.0f);
}

將三角形面片以重心坐標系作為輸出的原因,很可能是因為貝塞爾三角形面片都是用重心坐標來定義所導致的,

而四邊形面片的頂點位置引數以(u, v)的形式表示,前面例子的四邊形所用的域著色器代碼如下:

struct VertexOut
{
    float3 PosL : POSITION;
};

typedef VertexOut HullOut;

// Tessellation_Quad_DS.hlsl

[domain("quad")]
float4 DS(QuadPatchTess patchTess,
    float2 uv : SV_DomainLocation,
    const OutputPatch<HullOut, 4> quad) : SV_POSITION
{
    // 雙線性插值
    float3 v1 = lerp(quad[0].PosL, quad[1].PosL, uv.x);
    float3 v2 = lerp(quad[2].PosL, quad[3].PosL, uv.x);
    float3 p = lerp(v1, v2, uv.y);
    
    return float4(p, 1.0f);
}

貝塞爾曲線(Bézier Curves)

這部分借用GAMES101來說明,并且這里講的貝塞爾曲線所用的演算法是由Pierre Bézier和Paul de Casteljau所提出的,

現在我們先從二階貝塞爾曲線開始,下面有3個非共線的控制點b0b1b2

接下來我們用線性插值的方式,從b0b1方向的線段使用引數t來確定其中一點,記為\(\mathbf{b_{0}^{1}}\)

然后從b1b2方向的線段使用同樣的引數t來確定另一點,記為\(\mathbf{b_{1}^{1}}\)

\(b_{0}^{1}\)\(b_{1}^{1}\)連接起來,問題降級為一階貝塞爾曲線(直線),我們對其再使用一次引數t的線性插值即可得到在引數t下該貝塞爾曲線的對應一點\(\mathbf{b_{2}^{0}}\)

我們將t[0, 1]的所有情況都求出來,就可以得到一條光滑的貝塞爾曲線,

三階的貝塞爾曲線需要用到4個控制點,但做法也是類似的,首先求出b0b1b1b2b2到b3的線段在t時刻下的插值點\(\mathbf{b_{0}^{1}}, \mathbf{b_{1}^{1}}, \mathbf{b_{2}^{1}}\),此時問題就被轉化成了這三個控制點下的二階貝塞爾曲線,不斷降階最終算出目標點即可,

可以看到,三階貝塞爾曲線需要進行6次插值運算,二階貝塞爾曲線則需要進行3次插值運算,以此類推,我們可以知道n階貝塞爾曲線需要n(n+1)/2次運算

計算程序

下圖闡述了二階貝塞爾曲線的計算程序

觀察b0b1b2的各項系數,可以發現它們滿足二項式定理,我們也可以用下面的一個金字塔來描述

從底端的這些控制點選取其中一個然后不斷往上走,最終走到頂端點的程序中,如果走過一段朝著右上方向的路徑,則給該控制點乘上因子(1-t),而朝著左上方向的路徑則給該控制點乘上因子t,比如從b0走到頂端的項為\(t^3 b_{0}\),我們將這所有8條路徑都加起來就可以得到最后的結果:

\[\mathbf{b_0^3} = (1-t)^3\mathbf{b_0} + 3t(1-t)^2\mathbf{b_1} + 3t^2 (1-t)\mathbf{b_2} + t^3\mathbf{b_3} \]

更抽象的,我們可以用伯恩斯坦函式的形式來表示:

\[\mathbf{b^n}(t)=\sum_{i=0}^{n} C_{n}^{i}t^{i}(1-t)^{n-i}\cdot\mathbf{b_i} \]

對于三階貝塞爾曲線而言,曲線端點為:

\[\mathbf{b^3}(0)=\mathbf{b_0};\;\;\mathbf{b^3}(1)=\mathbf{b_3} \]

而三次伯恩斯坦函式的導數為:

\[\begin{align} {B_{0}^{3}}'(t)&=-3(1-t)^2\\ {B_{1}^{3}}'(t)&=3(1-t)^2-6t(1-t)\\ {B_{2}^{3}}'(t)&=6t(1-t)-3t^2\\ {B_{3}^{3}}'(t)&=3t^2\\ \end{align} \]

因此,對3次貝塞爾曲線求導的結果為:

\[\mathbf{{b^3}'(t)}=-3(1-t)^2\mathbf{b_0}+[3(1-t)^2-6t(1-t)]\mathbf{b_1}+[6t(1-t)-3t^2]\mathbf{b_2}+3t^2\mathbf{b_3} \]

通過這些導數就可以很方便地計算出曲線上某點處的切向量,

連續性

對于復雜曲線,如果我們使用高階曲線的話,觀察伯恩斯坦函式形式的頂點式會發現計算量呈平方級別增長,為此我們可以考慮將該曲線分段,然后這些曲線都使用較低階的貝塞爾曲線去擬合,以此來減少計算量,

假設我們現在有兩條三階貝塞爾曲線ab,那么當曲線a的最后一個控制點與曲線b的第一個控制點位置相同時,我們稱這兩條曲線滿足C0連續,下圖的紅點為控制點,觀察中間的控制點處顯然滿足這一性質,但是可以看到左右兩端曲線的過渡并不是平緩的,

而如果在滿足C0連續的基礎上,曲線a在最后一個控制點處的導數還與曲線b在第一個控制點處的導數相等,則此時我們說這兩條曲線滿足C1連續,

而滿足C1連續的控制點必然滿足:

\[\mathbf{a_n}=\mathbf{b_0}=\frac{1}{2}(\mathbf{a_{n-1}+b_{1}}) \]

即曲線ab的連接點為曲線a倒數第二個控制點與曲線b第二個控制點的中點,

當然還有更高級別的C2連續,即滿足二階導數相等,這里不再深入討論,

HLSL代碼實作

繪制貝塞爾曲線的外殼著色器和域著色器代碼如下:

// Tessellation.hlsli

float4 BernsteinBasis(float t)
{
    float invT = 1.0f - t;
    
    return float4(
        invT * invT * invT,         // B_{0}^{3}(t)= (1-t)^3
        3.0f * t * invT * invT,     // B_{1}^{3}(t)= 3t(1-t)^2
        3.0f * t * t * invT,        // B_{2}^{3}(t)= 3t^2(1-t)
        t * t * t);                 // B_{3}^{3}(t)= t^3
}

float4 dBernsteinBasis(float t)
{
    float invT = 1.0f - t;
    
    return float4(
        -3 * invT * invT,                   // B_{0}^{3}'(t)= -3(1-t)^2
        3.0f * invT * invT - 6 * t * invT,  // B_{1}^{3}'(t)= 3(1-t)^2 - 6t(1-t)
        6 * t * invT - 3 * t * t,           // B_{2}^{3}'(t)= 6t(1-t) - 3t^2
        3 * t * t);                         // B_{3}^{3}'(t)= 3t^2
}
// Tessellation_Isoline_HS.hlsl
IsolinePatchTess IsolineConstantHS(InputPatch<VertexOut, 4> patch, uint patchID : SV_PrimitiveID)
{
    IsolinePatchTess pt;
    
    pt.EdgeTess[0] = g_IsolineEdgeTess[0];  // 未知
    pt.EdgeTess[1] = g_IsolineEdgeTess[1];  // 段數
    
    return pt;
}

[domain("isoline")]
[partitioning("integer")]
[outputtopology("line")]
[outputcontrolpoints(4)]
[patchconstantfunc("IsolineConstantHS")]
[maxtessfactor(64.0f)]
float3 HS(InputPatch<VertexOut, 4> patch, uint i : SV_OutputControlPointID, uint patchId : SV_PrimitiveID) : POSITION
{
    return patch[i].PosL;
}

// Tessellation_BezierCurve_DS.hlsl
#include "Tessellation.hlsli"

[domain("isoline")]
float4 DS(IsolinePatchTess patchTess,
    float t : SV_DomainLocation,
    const OutputPatch<HullOut, 4> bezPatch) : SV_POSITION
{
    float4 basisU = BernsteinBasis(t);
    
    // 貝塞爾曲線插值
    float3 sum = basisU.x * bezPatch[0].PosL +
        basisU.y * bezPatch[1].PosL +
        basisU.z * bezPatch[2].PosL +
        basisU.w * bezPatch[3].PosL;
    
    float4 posH = mul(float4(sum, 1.0f), g_WorldViewProj);
    
    return posH;
}

貝塞爾曲線示例

下面的動圖展示了貝塞爾曲線的控制和細分

貝塞爾曲面

三階貝塞爾曲線有4個控制點,而三階貝塞爾曲面自然就有4x4控制點了,我們可以將其看做4條三次貝塞爾曲線,

這里就不再列出復雜的公式來暈人了,在一維情況下,貝塞爾曲線使用范圍在[0, 1]的引數t來表示曲線上一點,那么在二維情況下,我們可以使用范圍在[0, 1]的引數(u, v)來表示曲面上一點,

首先對于這4條橫向的貝塞爾曲線,我們分別使用引數u代入來求得四個點,這四個頂點按列順序構成新的控制點,然后問題就轉化成了在這四個控制點構成的貝塞爾曲線中求其中一點,然后我們再用引數v代入就可以求得最終在曲面上的一點,

HLSL代碼實作

貝塞爾曲面的著色器代碼實作如下:

// Tessellation.hlsli

// 計算以4x4控制點為基礎的三階貝塞爾曲面在(u, v)下的一點
float3 CubicBezierSum(const OutputPatch<HullOut, 16> bezPatch,
    float4 basisU, float4 basisV)
{
    float3 sum = float3(0.0f, 0.0f, 0.0f);
    sum = basisV.x * (basisU.x * bezPatch[0].PosL +
        basisU.y * bezPatch[1].PosL +
        basisU.z * bezPatch[2].PosL +
        basisU.w * bezPatch[3].PosL);
    
    sum += basisV.y * (basisU.x * bezPatch[4].PosL +
        basisU.y * bezPatch[5].PosL +
        basisU.z * bezPatch[6].PosL +
        basisU.w * bezPatch[7].PosL);
    
    sum += basisV.z * (basisU.x * bezPatch[8].PosL +
        basisU.y * bezPatch[9].PosL +
        basisU.z * bezPatch[10].PosL +
        basisU.w * bezPatch[11].PosL);
    
    sum += basisV.w * (basisU.x * bezPatch[12].PosL +
        basisU.y * bezPatch[13].PosL +
        basisU.z * bezPatch[14].PosL +
        basisU.w * bezPatch[15].PosL);
    
    return sum;
}


上面的函式不僅能用來計算\(\mathbf{p}(u, v)\),還能夠求它的偏導數:

float4 basisU = BernsteinBasis(uv.x);
float4 basisV = BernsteinBasis(uv.y);
    
// p(u, v)
float3 p = CubicBezierSum(bezPatch, basisU, basisV);


float4 dBasisU = dBernsteinBasis(uv.x);
float4 dBasisV = dBernsteinBasis(uv.y);
// p(u, v)對u的偏導
float3 dpdu = CubicBezierSum(bezPatch, dbasisU, basisV);
// p(u, v)對v的偏導
float3 dpdv = CubicBezierSum(bezPatch, basisU, dbasisV);

注意:可以發現,我們把基函式的計算結果傳入了CubicBezierSum函式,由于p(u, v)與其偏導數的求和形式相同,僅基函式不同,因此CubicBezierSum函式不僅能用來計算p(u, v),還能用于求其偏導數,

// Tessellation_BezierSurface_HS.hlsl
#include "Tessellation.hlsli"

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(16)]
[patchconstantfunc("QuadPatchConstantHS")]
[maxtessfactor(64.0f)]
float3 HS(InputPatch<VertexOut, 16> patch, uint i : SV_OutputControlPointID, uint patchId : SV_PrimitiveID) : POSITION
{
    return patch[i].PosL;
}

// Tessellation_BezierSurface_DS.hlsl
#include "Tessellation.hlsli"

[domain("quad")]
float4 DS(QuadPatchTess patchTess,
    float2 uv : SV_DomainLocation,
    const OutputPatch<HullOut, 16> bezPatch) : SV_POSITION
{
    float4 basisU = BernsteinBasis(uv.x);
    float4 basisV = BernsteinBasis(uv.y);
    
    // 貝塞爾曲面插值
    float3 p = CubicBezierSum(bezPatch, basisU, basisV);
    
    float4 posH = mul(float4(p, 1.0f), g_WorldViewProj);
    
    return posH;
}

幾何體定義

下面的代碼定義了16個控制點:

XMFLOAT3 surfaceVertices[16] = {
// 行 0
XMFLOAT3(-10.0f, -10.0f, +15.0f),
XMFLOAT3(-5.0f,  0.0f, +15.0f),
XMFLOAT3(+5.0f,  0.0f, +15.0f),
XMFLOAT3(+10.0f, 0.0f, +15.0f),

// 行 1
XMFLOAT3(-15.0f, 0.0f, +5.0f),
XMFLOAT3(-5.0f,  0.0f, +5.0f),
XMFLOAT3(+5.0f,  20.0f, +5.0f),
XMFLOAT3(+15.0f, 0.0f, +5.0f),

// 行 2
XMFLOAT3(-15.0f, 0.0f, -5.0f),
XMFLOAT3(-5.0f,  0.0f, -5.0f),
XMFLOAT3(+5.0f,  0.0f, -5.0f),
XMFLOAT3(+15.0f, 0.0f, -5.0f),

// 行 3
XMFLOAT3(-10.0f, 10.0f, -15.0f),
XMFLOAT3(-5.0f,  0.0f, -15.0f),
XMFLOAT3(+5.0f,  0.0f, -15.0f),
XMFLOAT3(+25.0f, 10.0f, -15.0f)
};

注意:這里并沒有嚴格地限定控制點一定要按等距排列為均勻的網格,

貝塞爾曲面示例

下圖展示了貝塞爾曲面

練習題

  1. 參考16章,只用6個頂點構成的八面體,并根據其與觀察點的距離關系將其鑲嵌細分為一個球體(距離越近,鑲嵌程度越大)
  2. 嘗試修改本演示程式中的控制點來改變其中的貝塞爾曲面
  3. 修改本演示程式,利用光照使得其中的貝塞爾曲面表現出明暗變化,為此,我們需要在域著色器中計算頂點法線,而位于頂點處的法線可以用此曲面點處坐標的偏導數的叉積求得,

DirectX11 With Windows SDK完整目錄

Github專案原始碼

歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/27760.html

標籤:其他

上一篇:Unity論壇問答-如何裁剪一個多邊形

下一篇:Unity周記: 2020.07.06-07.12

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more