在D3D11管線中,新增加了3個stage, Hull shader, Tessellator, Domain shader,用來實現細分操作,就是在gpu中把低細節的表面細分成高細節的體元。在gpu中把低模通過tessellation轉化為高模,在獲得高細節模型的同時,可以有效降低把頂點數據從system memory傳到 video memory的帶寬消耗。
下面我們看看這三個階段到底做些什么,輸入是什么,輸出是什么?先畫一張圖。
1、Hull shader階段
Hull shader階段可以分成兩個獨立的階段,它們是並行執行的。
第一個階段是per control points執行的, 在這個階段對patch中的每個控制點,輸出對應的控制點。所謂patch,簡單理解就是帶控制點的體元,比如一個三角形,它的三個頂點是控制點,那么這個三角形就是有3個控制點的patch。當然,在Hull shader中,我們還可以對輸入的控制點進行轉化操作,生成新的控制點,比如輸入的3個控制點,輸出6個控制點。注意:輸入或者輸出的控制點數量是1~32。
第二個階段就是patch常量階段,這時HullShader會調用一個const data函數,這個函數主要產生tessellation factor,這些factor決定在TS階段如何細分當前的patch。
另外,在Hullshader階段還會指定一些TS階段使用的Tessellation模式,比如細分的patch是三角形(拓撲模式),partition mode(選擇什么細分算法)是HS_PARTITION等。
下面看一段Hull shader的代碼:
struct HullInputType
{
float3 position : POSITION;
float4 color : COLOR;
};
struct ConstantOutputType
{
float edges[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
struct HullOutputType
{
float3 position : POSITION;
float4 color : COLOR;
};
// Patch 常量函數,決定tessellation因子,每個patch執行一次,所以是per patch的,不是per控制點的
ConstantOutputType ColorPatchConstantFunction(InputPatch<HullInputType, 3> inputPatch, uint patchId : SV_PrimitiveID)
{
ConstantOutputType output;
//設置三條邊的細分因子
output.edges[0] = tessellationAmount;
output.edges[1] = tessellationAmount;
output.edges[2] = tessellationAmount;
//設置三角形內的細分因子
output.inside = tessellationAmount;
return output;
}
//注意輸入控制點數量要和 IASetPrimitiveTopology()函數中一致
//本例子中,都為3 INPUT_PATCH_SIZE
// The hull shader is called once per output control point, which is specified with
// outputcontrolpoints. For this sample, we take the control points from the vertex
// shader and pass them directly off to the domain shader. In a more complex scene,
// you might perform a basis conversion from the input control points into a Bezier
// patch, such as the SubD11 Sample of DirectX SDK.
// The input to the hull shader comes from the vertex shader
// The output from the hull shader will go to the domain shader.
// The tessellation factor, topology, and partition mode will go to the fixed function
// tessellator stage to calculate the UVW and domain points
[domain("tri")] //Triangle domain for our shader
[partitioning("integer")] //Partitioning type according to the GUI
[outputtopology("triangle_cw")] //Where the generated triangles should face
[outputcontrolpoints(3)] //Number of times this part of the hull shader will be called for each patch
[patchconstantfunc("ColorPatchConstantFunction")] //The constant hull shader function
HullOutputType ColorHullShader(InputPatch<HullInputType, 3> patch, uint pointId : SV_OutputControlPointID, uint patchId : SV_PrimitiveID)
{
HullOutputType output;
//設置控制點
output.position = patch[pointId].position;
// 輸出顏色為輸入顏色
output.color = patch[pointId].color;
return output;
}
2. Tessellator階段
Tessellator是一個固定管線階段,它的主要功能就是細分一個domain(三角形, 四邊形或線),把它們細分成很多小的物體,比如三角形,四邊形或者線。
在細分時,tessellator會在一個歸一化的坐標系統中處理patch,比如輸入是一個quad(四邊形),但這個quad先要映射到一個單位為1的正方形上,然后tessellator會對這個正方形進行細分操作。
Tessellator是per patch操作的,Hull shader階段傳入的Tess Factor決定細分多少次,而Hull shader階段傳入的partitioning則決定選用何種細分算法。Tessellator輸出為u,v, {w}坐標以及細分后domain的拓撲信息。
3. Domain Shader階段
Domain Shader 階段會根據TS階段生成的u,v , {w}坐標以及HS階段傳入的控制點在patch中生成細分后頂點的位置。
Domain shader是per vertex的,對於TS中每個細分產生的頂點,它都要調用一次。它的輸入參數除了u,v,{w}坐標及控制點以外,還有const data,比如Tess factor等。
下面是一段Domain shader的代碼:
struct ConstantOutputType
{
float edges[3] : SV_TessFactor;
float inside : SV_InsideTessFactor;
};
struct HullOutputType
{
float3 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
//每個細分后的頂點調用一次
[domain("tri")]
PixelInputType ColorDomainShader(ConstantOutputType input, float3 uvwCoord : SV_DomainLocation, const OutputPatch<HullOutputType, 3> patch)
{
float3 vertexPosition;
PixelInputType output;
//基於重心坐標的頂點生成
vertexPosition = uvwCoord.x * patch[0].position + uvwCoord.y * patch[1].position + uvwCoord.z * patch[2].position;
// 計算新的頂點在世界坐標系中的位置
output.position = mul(float4(vertexPosition, 1.0f), worldMatrix);
output.position = mul(output.position, viewMatrix);
output.position = mul(output.position, projectionMatrix);
//新生成頂點顏色也為各個控制點顏色組合
output.color = uvwCoord.x * patch[0].color + uvwCoord.y * patch[1].color + uvwCoord.z * patch[2].color;
return output;
}