Motivation:Tessellation Shaders

Motivation:Tessellation Shaders

從DirectX 11開始,Direct3D中引入了硬件實現tessellation的功能。Tessellation是指對錶面進行細分(subdivision of surfaces),進而增加一個表面的geometry數量來提高精度。這種方法支持把low-ploy models(表示模型具有較少的primitives)傳遞到GPU中而不會導致high-poly渲染質量的損失。在圖形渲染程序中,CPU和GPU之前的傳輸總線經常成爲性能的瓶頸,因此減少該總線上傳輸的數據量是一個非常好的優化方法。另外,在沒有硬件tessellation功能的情況下,經常需要根據一個特定的level of detail(LOD),對同一個3D模型創建多種不同的分辨率大小(比如,low,medium,high),該LOD是相對於某個參照量的不同而不同(比如使用object到camera的距離作爲參照度量)。這意味着需要美術設計人員針對LOD做一些額外的工作,或者在content pipeline(詳見第15章)中增加一個額外的處理步驟(用於自動產生LODs),同時還會佔用更多的內存和處理器資源。而使用硬件tessellation的情況下,僅僅使用模型的一個low-poly版本就可以實現動態的LODs。

在DirectX 11增加tessellation管線階段之前,一般是使用DirectX 10的geometry shader(注意:DirectX 10中增加了geometry階段,DirectX 11又增加了tessellation階段)處理表面細分,現在已經有了專門處理表面細分的tessellation階段:hull-shader階段,tessellation階段,以及domain-shader階段。如圖21.3所示的圖形管線,tessellation階段介於vertex和geometry shader兩個階段之間。接下來每一節講解tessellation的一個階段,並提供相應的演示程序。


圖21.3 The Direct3D 11 graphics pipeline.


The Hull Shader Stage

在啓用管線的tessellation階段的情況下,vertex shader不再是處理傳統的point,line,triangle primitives,而是處理control poing patch列表。一個patch表示一個表面,該表面的形狀由對應的control points確定。Direct3D 11支持每個patch中所包含的control points數量範圍爲1到32.含有3個control points的patch表示一個三角形,含有4個control points則表示一個四邊形。Patch中所包含的control points超過4個表示由Bezier curves(貝塞爾曲線)構成的表面。在hull shader階段主要是,接收一個含有control points數據的patch參數,並輸出一組用於下一個管線階段的control points,tessellation factors以及任何額外的數據。其中tessellation factors指定了每一個patch的細分數量。列表21.5列出一個用於處理triangle pathes的hull shader示例代碼。

列表21.5 A Hull Shader for Triangle Patches

struct VS_OUTPUT
{
    float4 ObjectPosition : POSITION;
};

struct HS_CONSTANT_OUTPUT
{
    float EdgeFactors[3] : SV_TessFactor;
    float InsideFactor : SV_InsideTessFactor;
};

struct HS_OUTPUT
{
    float4 ObjectPosition : POSITION;
};

[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("constant_hull_shader")]
HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, 3> patch, uint controlPointID : SV_OutputControlPointID)
{
    HS_OUTPUT OUT = (HS_OUTPUT)0;

    OUT.ObjectPosition = patch[controlPointID].ObjectPosition;

    return OUT;
}


該hull shader示例代碼類似於HLSL中其他shaders的代碼,但是該語法形式只適用於hull shaders。Hull shader中第一個參數的類型爲InputPatch<T, n>,其中T表示vertex shader的輸出數據類型,n表示輸入的patch中包含的control points數量。第二個參數表示每一個輸出control point對應的唯一標識符。在hull shaer階段可以重新定義patch的topology,使得輸出的control point數量多於或少於輸入的point數量。Hull shader是針對每一個output control point執行一次,並且outputcontrolpoints屬性值指定了output point的總數量。在該示例中,使用HS_OUTPUT結構類型定義每一個hull shader的輸出數據。

在hull shader中關聯了多個屬性對象值,第一個屬性對象爲domain,對應的屬性值指定了hull shader中處理的tessellation primitive類型。該屬性值可以爲tri(triangles三角形),quad(rectangles矩形)或者isoline(組成矩陣的線段包含多個control points)。第二個屬性partitioning指定tessellation因子小數部分的使用方式。屬性值爲integer表示對tessellation因子的小數部分進行四捨五入,而屬性值pow2表示對tessellation因子進行non-power-of-two(無二次冪限制)處理以得到下一個可接受的數值。此外,partitioning的屬性值還可以爲fractional-event(向奇數取整)或fractional-odd(向偶數取整),這兩個屬性值並不會替換tessellation的小數部分,只是用於在兩個tesselloation因子之間更平滑的過渡。第三個屬性對象outputtopology定義了tessellator(細分操作)產生的primitive類型,可以爲point,line,triangle_cw或者triangle_ccw。最後一個屬性對象patchconstantfunc定義了用於計算patch constant data(即tessellation因子)的函數,也就是constant hull shader。列表21.6中列出一個constant hull shader的示例代碼。

列表21.6 A Constant Hull Shader

cbuffer CBufferPerFrame
{
    float TessellationEdgeFactors[3];
    float TessellationInsideFactor;
}

struct HS_CONSTANT_OUTPUT
{
    float EdgeFactors[3] : SV_TessFactor;
    float InsideFactor : SV_InsideTessFactor;
};

HS_CONSTANT_OUTPUT constant_hull_shader(InputPatch<VS_OUTPUT, 3> patch)
{
    HS_CONSTANT_OUTPUT OUT = (HS_CONSTANT_OUTPUT)0;

    [unroll]
    for (int i = 0; i < 3; i++)
    {
        OUT.EdgeFactors[i] = TessellationEdgeFactors[i];
    }

    OUT.InsideFactor = TessellationInsideFactor;

    return OUT;
}


與hull shader不同的是,constant hull shader是針對每一個patch執行一次,並且輸出以SV_TessFactor和SV_InsideTessFactor semantic標記的變量值。其中,SV_TessFactor semantic表示patch邊緣(edge)的tessellation因子,即針對每一條邊的細分數量。而SV_InsiderTessFactorsemantic則表示在patch內部(inside)進行細分的數量。並且邊緣和內部的tessellation因子必須與patch的topology匹配。例如,三角形patches含有3個邊緣tessellation因子以及1個內部因子。四邊形則含有4個邊緣因子和兩個內部因子(一個水平細分以及一個垂直細分)。每一個tessellation因子都介於範圍[0, 64]內,並且這些值不需要保持一致。如果任何一個tessllation因子值爲0,就不會進一步處理該patch。Tessellation因子值爲1表示不進行細分。在列表21.6所示的shader中使用從CPU端應用程序輸入的tessellatin因子值。該shader用於第一個完整的tessellation演示程序中,在該示例中可以動態改變三角形和四邊形patches的邊緣和內部tessellation因子。


The Tessellation Stage

接下來討論hull shader的下一個階段tessellation階段,該階段是不可編程階段。在tessellation階段根據hull shader階段的輸出數據,執行真正的表面細分操作。圖21.4和21.5中分別顯示了一個三角形和一個四邊形使用各種不同的邊緣和內部細分因子產生的輸出結果。


圖21.4 A tessellated triangle with various tessellation factors.


圖21.5 A tessellated quad with various tessellation factors.


The Domain Shader Stage

Domain shader是針對由tessellation階段傳遞過來的每一個vertex執行一次,並輸出變換到homogeneous clip space(齊次座標裁剪空間)的vertices。從domain shader中輸出數據會傳遞到geometry shader(如果存在的話)中或者直接傳遞到pixel shader(如果禁用了geometry shader)中。因此,domain shader輸出類型中通常會包含一個使用SV_Positon semantic標記的成員,類似與不使用tessellation情況下vertex shader的輸出數據。列表21.7中列出一個domain shader的示例代碼。

列表21.7 A Domain Shader

struct DS_OUTPUT
{
    float4 Position : SV_Position;
};

[domain("tri")]
DS_OUTPUT domain_shader(HS_CONSTANT_OUTPUT IN, float3 uvw : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 3> patch)
{
    DS_OUTPUT OUT = (DS_OUTPUT)0;

    float3 objectPosition = uvw.x * patch[0].ObjectPosition.xyz + uvw.y * patch[1].ObjectPosition.xyz + uvw.z * patch[2].ObjectPosition.xyz;

    OUT.Position = mul(float4(objectPosition, 1.0f), WorldViewProjection);

    return OUT;
}


與hull shader一樣,domain shader中的domain屬性也表示patch topology。Domain shader的第一個參數表示的輸入是constant hull shader的輸出數據,而第三個參數則對應於hull shader輸出類型的OutputPatch<T, n>類型。另外,第二個參數描述了vertex位於patch space的座標位置。在三角形patches中,patch space表示barycentric(重心)座標。簡單地說,重心座標支持使用三角形的3個vertices的權重和表示三角形內部的任意點。具體的計算公式爲:


對於四邊形,domain shader的第二個參數是一個二維座標,類似於紋理座標空間(範圍爲[0, 1];u表示水平軸;v表示垂直軸)。計算該vertex的座標位置需要進行線程插值。具體的公式爲:


把列表21.5,21.6和21.7中的大部分代碼段合併到一起就是一個細分三角形的基本tessellation示例。列表21.8中列出細分四邊形的完整shader代碼(另外,本書的配套網站上提供了這兩個shader的全部代碼)。

列表21.8 A Quad Tessellation Shader

/************* Resources *************/
static const float4 ColorWheat = { 0.961f, 0.871f, 0.702f, 1.0f };

cbuffer CBufferPerFrame
{
	float TessellationEdgeFactors[4];
	float TessellationInsideFactors[2];
}

cbuffer CBufferPerObject
{
	float4x4 WorldViewProjection;
}

/************* Data Structures *************/

struct VS_INPUT
{
	float4 ObjectPosition : POSITION;
};

struct VS_OUTPUT
{
	float4 ObjectPosition : POSITION;
};

struct HS_CONSTANT_OUTPUT
{
	float EdgeFactors[4] : SV_TessFactor;
	float InsideFactors[2] : SV_InsideTessFactor;
};

struct HS_OUTPUT
{
	float4 ObjectPosition : POSITION;
};

struct DS_OUTPUT
{
	float4 Position : SV_Position;
};

/************* Vertex Shader *************/

VS_OUTPUT vertex_shader(VS_INPUT IN)
{
	VS_OUTPUT OUT = (VS_OUTPUT)0;

	OUT.ObjectPosition = IN.ObjectPosition;

	return OUT;
}

/************* Hull Shaders *************/

HS_CONSTANT_OUTPUT constant_hull_shader(InputPatch<VS_OUTPUT, 4> patch, uint patchID : SV_PrimitiveID)
{
	HS_CONSTANT_OUTPUT OUT = (HS_CONSTANT_OUTPUT)0;

	[unroll]
	for (int i = 0; i < 4; i++)
	{
		OUT.EdgeFactors[i] = TessellationEdgeFactors[i];
	}

	OUT.InsideFactors[0] = TessellationInsideFactors[0];
	OUT.InsideFactors[1] = TessellationInsideFactors[1];

	return OUT;
}

[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(4)]
[patchconstantfunc("constant_hull_shader")]
HS_OUTPUT hull_shader(InputPatch<VS_OUTPUT, 4> patch, uint controlPointID : SV_OutputControlPointID)
{
	HS_OUTPUT OUT = (HS_OUTPUT)0;

	OUT.ObjectPosition = patch[controlPointID].ObjectPosition;

	return OUT;
}

/************* Domain Shader *************/

[domain("quad")]
DS_OUTPUT domain_shader(HS_CONSTANT_OUTPUT IN, float2 uv : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 4> patch)
{
	DS_OUTPUT OUT;

	float4 v0 = lerp(patch[0].ObjectPosition, patch[1].ObjectPosition, uv.x);
	float4 v1 = lerp(patch[2].ObjectPosition, patch[3].ObjectPosition, uv.x);
	float4 objectPosition = lerp(v0, v1, uv.y);

	OUT.Position = mul(float4(objectPosition.xyz, 1.0f), WorldViewProjection);

	return OUT;
}

/************* Pixel Shader *************/

float4 pixel_shader(DS_OUTPUT IN) : SV_Target
{
	return ColorWheat;
}

/************* Techniques *************/

technique11 main11
{
	pass p0
	{
		SetVertexShader(CompileShader(vs_5_0, vertex_shader()));
		SetHullShader(CompileShader(hs_5_0, hull_shader()));
		SetDomainShader(CompileShader(ds_5_0, domain_shader()));
		SetPixelShader(CompileShader(ps_5_0, pixel_shader()));
	}
}


在該shader代碼中,vertex和hull shader階段僅僅是傳遞了未做任何修改的control points。然後在constant hull shader階段使用由CPU端應用程序輸入的tessellation因子對相應的輸出變量賦值。接下來,在domain shader中計算細分後的座標位置並把該位置變換到齊次裁剪空間中。最後,在pixel shader中對每一個pixel輸出一個固定的顏色值(因爲示例程序中是以wireframe線框模式渲染模型)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章