`

tessellation(曲面细分)

 
阅读更多

在实时渲染的时候,通常我们会因为带宽,效率等原因,不会采样面数非常多的高精模型建模。但是又想渲染出细节非常多的场景来怎么办?各种办法,动态纹理,法向量纹理,曲面细分等技术是比较常用的技术。这里主要说曲面细分,在DX11渲染管道用了三个stage(阶段)来实现,曲面细分就是在GPU内部使用硬件加速的方式,动态生成顶点。其中:

Hull shader :相当细分前的预处理工作,定制一些细分规则和生成规则。

Tessellator :执行细分操作,根据Hull shader预置的细分规则。

Domain shader :在Tessellator 会生成对应的顶点和线,但是这些个顶点它是相对于控制点的,使用的是(U,V,W)坐标,这一步就是对顶点做一些处理来的。

(或许是涉及到硬件的非通用计算的原因,致使这样一个功能需要整出三个阶段,2个shader,如果只有一个API调用多好)

如果有一天,你在tess的时候失败了,请第一时间思考,是否有别的地方没有使用tess,这是首先需要排查的。

这个图很清晰的描述了整个曲面细分的过程。

 

1.HS input :第一步不是HS,而是HS之前的input操作,这里有个patch control points(补丁控制点)它是在原语里面设定的,IASetPrimitiveTopology方法D3D11_PRIMITIVE_TOPOLOGY枚举,里面有很多PATCHLIST为1-32。这说明在我们需要进行曲面细分的时候,我们会把原语定义为PATCHLIST系列类型,这个时候传入到vs里面的已经不是普通的顶点,而是补丁控制点,1-32对应的是控制点的个数,一般我们网格是三角形,三个顶点,所以会选择3。

 

2.HS(Hull Shader) 阶段 :这个阶段会并且的做两件事:1.根据输入的补丁控制点生成新的控制点,然后输出,输出时调用的是DS(Domain Shader),每个点调用一次。2.设置参数,计算的次数方式生成的结果等,参数又如下:

SV_TessFactor :这是个系统语义,定义Tessellation(曲面细分)的每个边缘上的一个补丁,它确定的是边。它的类型有三种:

(1) float[2] :生成的拓扑图为等值线。

 

float edges[2] : SV_TessFactor;

output.edges[0] = tessAmount; // 第一个值在SV_TessFactor的线密度镶嵌的因素
output.edges[1] = tessAmount; // 第二个值是行了详细的Tessellation(曲面细分)系数

 

(2)float[3] :拓扑图为三角形。

 

float edges[3] : SV_TessFactor;

output.edges[0] = tessAmount; // 第一个组件提供为U = 0边镶嵌因素的补丁
output.edges[1] = tessAmount; // 第二部分提供了镶嵌系数为v = 0边的补丁
output.edges[2] = tessAmount; // 第三部分提供了镶嵌系数为w = 0边的补丁

 

(3)float[4] :拓扑图为四边形。

 

float edges[4] : SV_TessFactor; // 为顺时针方向的边缘的排序,从u == 0的边缘,这是的补丁的左侧开始,并从v == 0边缘,这是顶部的补丁。(意思是(0,0)在左上方,u为纵轴,V为横轴)

output.edges[0] = tessAmount; // 第一个组件提供为U = 0边镶嵌因素的补丁
output.edges[1] = tessAmount; // 第二部分提供了镶嵌系数为v = 0边的补丁
output.edges[2] = tessAmount; //第三个组成部分为U = 1的边缘镶嵌因素的补丁
output.edges[3] = tessAmount; // 第四部分提供了镶嵌系数为v == 1边缘的补丁

 

注意:这里必须使用数组float edges[4] ,不能使用float4 edges,上面的一样。另外就是三角形时,用到了W轴,四边形的时候只用到了U,V轴。等值线这个暂时不懂。tessAmount值的区间为[1,8]

 

SV_InsideTessFactor :这个也是系统语义,定义的是内部的细分规则,等值线不需要设置这个值。

float[2] :拓扑图为四边形。

float :拓扑图为三角形。

 

[domain("tri")] :这个指定细分的类型。值:quad,tri,isoline

[partitioning("integer")] :// 指定TS阶段细分是按照什么规则细分,一个有四种类型:Fractional_odd(1,3..63),Fractional_even(2,4...64),integer(1,2,3...64),Pow2。有这四种类型的主要原因是奇数生成的图形一致,偶数的一致,但是奇数和偶数的规则不一致。

[outputtopology("triangle_cw")] :指定拓扑类型,这些拓扑信息会被传输到PA block中去。值:line, triangle_cw(逆时针方向三角形), triangle_ccw(???)。

[outputcontrolpoints(3)] // 输出的控制点的数目。

[patchconstantfunc("ColorPatchConstantFunction")] // 当前HS中使用的常量函数,这个函数是自己定义的,引号里面是名字。

 

////////////////////////////////////////////////////////////////////////////////
// Patch 常量函数,决定tessellation因子,每个patch执行一次,所以是per patch的,不是per 控制点的
////////////////////////////////////////////////////////////////////////////////
ConstantOutputType ColorPatchConstantFunction(InputPatch<HullInputType, 3> inputPatch, uint patchId : SV_PrimitiveID)
{    
    ConstantOutputType output;


	// Set the tessellation factors for the three edges of the triangle.
    output.edges[0] = tessellationAmount;
    output.edges[1] = tessellationAmount;
    output.edges[2] = tessellationAmount;

	// Set the tessellation factor for tessallating inside the triangle.
    output.inside = tessellationAmount;

    return output;
}

3.Tessellator :这个阶段就是生成点的阶段,上面已经描述清楚了,照着做就行了,这个阶段不需要做任何事情。

4.DS(Domain Shader):H和T阶段的每个补丁点都会调用一次DS,在DS里面输出最终的点。由于曲面细分会生成很多新的点,所以如果要使用这些shader,最好在DS里面对顶点的坐标进行转换,就是把VS里面的事情拿到DS里面来做。

 

 代码:

////////////////////////////////////////////////////////////////////////////////
// Filename: color.hs
////////////////////////////////////////////////////////////////////////////////

/////////////
// GLOBALS //
/////////////
cbuffer TessellationBuffer
{
    float tessAmount;
    float3 padding;
};


//////////////
// TYPEDEFS //
//////////////
struct HullInputType
{
    float3 hiPosition : POSITION;
    float3 hiNormal: NORMAL;
    float3 hiTangent : TANGENT;
    float3 hiBinormal : BINORMAL;
    float2 hiTexture : TEXCOORD0; //纹理坐标
};

struct ConstantOutputType
{
    float edges[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
};

struct DomainInputType
{
    float3 diPosition : POSITION;
    float3 diNormal: NORMAL;
    float3 diTangent : TANGENT;
    float3 diBinormal : BINORMAL;
    float2 diTexture : TEXCOORD0; //纹理坐标
};


////////////////////////////////////////////////////////////////////////////////
// Patch 常量函数,决定tessellation因子,每个patch执行一次,所以是per patch的,不是per 控制点的
////////////////////////////////////////////////////////////////////////////////
ConstantOutputType ColorPatchConstantFunction(InputPatch<HullInputType, 3> inputPatch, uint patchId : SV_PrimitiveID)
{    
    ConstantOutputType output;


	// Set the tessellation factors for the three edges of the triangle.
    output.edges[0] = tessAmount;
    output.edges[1] = tessAmount;
    output.edges[2] = tessAmount;

	// Set the tessellation factor for tessallating inside the triangle.
    output.inside = tessAmount;

    return output;
}


////////////////////////////////////////////////////////////////////////////////
// Hull Shader
////////////////////////////////////////////////////////////////////////////////
[domain("tri")] // 这个指定细分的类型,三角形,四边形神马的
[partitioning("integer")] // 指定TS阶段细分是按照什么规则细分,一个有四种类型:Fractional_odd(1,3..63),Fractional_even(2,4...64),integer(1,2,3...64),Pow2
[outputtopology("triangle_cw")] // 细分后的输出语义是逆时针方向三角形,这些拓扑信息会被传输到PA block中去
[outputcontrolpoints(3)] // 输出的控制点的数目,三角形是3个,也是hull shader被调用的次数
[patchconstantfunc("ColorPatchConstantFunction")] // 当前HS中使用的常量函数 上面的那个函数

DomainInputType PortHullShader(InputPatch<HullInputType, 3> patch, uint pointId : SV_OutputControlPointID, uint patchId : SV_PrimitiveID)
{
    DomainInputType output;

	output.diPosition = patch[pointId].hiPosition;
	output.diNormal = patch[pointId].hiNormal;
	output.diTangent = patch[pointId].hiTangent;
	output.diBinormal = patch[pointId].hiBinormal;
	output.diTexture = patch[pointId].hiTexture;

    return output;
}

 

////////////////////////////////////////////////////////////////////////////////
// Filename: color.ds
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////

cbuffer MatrixBuffer
{
    matrix mbWorld;
    matrix mbTransform;
};


//////////////
// TYPEDEFS //
//////////////
struct ConstantOutputType
{
    float edges[3] : SV_TessFactor;
    float inside : SV_InsideTessFactor;
};

struct DomainInputType
{
    float3 diPosition : POSITION;
    float3 diNormal: NORMAL;
    float3 diTangent : TANGENT;
    float3 diBinormal : BINORMAL;
    float2 diTexture : TEXCOORD0; //纹理坐标
};

struct PixelInputType
{
    float4 piPosition : SV_POSITION;
    //float4 color : COLOR;
};


////////////////////////////////////////////////////////////////////////////////
// Domain Shader
////////////////////////////////////////////////////////////////////////////////
//对三角形u, v, w表示细分点相对于三个控制点的u,v, w坐标,或者说是重心坐标,
//我们可以根据这三个值计算出细分点的位置,然后转化为世界坐标系中点,
//输出颜色也是用三个控制点的颜色根据u, v, w差值得到。(这个u和v是不是纹理坐标呢?)

[domain("tri")]

PixelInputType PortDomainShader(ConstantOutputType input, float3 uvwCoord : SV_DomainLocation, const OutputPatch<DomainInputType, 3> patch)
{
	float3 vertexPosition;
	PixelInputType output;
 
 
 	// Determine the position of the new vertex.
	//Baricentric Interpolation to find each position the generated vertices
	//基于重心坐标的顶点生成
	vertexPosition = uvwCoord.x * patch[0].diPosition + uvwCoord.y * patch[1].diPosition + uvwCoord.z * patch[2].diPosition;
    
	// Calculate the position of the new vertex against the world, view, and projection matrices.
    output.piPosition = mul(float4(vertexPosition, 1.0f), mbTransform);


	//新生成顶点颜色也为各个控制点颜色组合
	//output.color = uvwCoord.x * patch[0].color + uvwCoord.y * patch[1].color + uvwCoord.z * patch[2].color; 

    return output;
}

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics