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的一个阶段,并提供相应的演示程序。

Motivation:Tessellation Shaders_第1张图片

图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 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表示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 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中分别显示了一个三角形和一个四边形使用各种不同的边缘和内部细分因子产生的输出结果。

Motivation:Tessellation Shaders_第2张图片

图21.4 A tessellated triangle with various tessellation factors.

Motivation:Tessellation Shaders_第3张图片

图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 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类型。另外,第二个参数描述了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 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 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 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线框模式渲染模型)。

你可能感兴趣的:(Rendering,with,DirectX,&&,HLSL)