Chapter13 The Tessellation Stages (Introduction to 3D Game Programming with DirectX 11)笔记

The Tessellation Stages是指rendering pipeline中的镶嵌细分geometry的三个阶段。简单来说,包括 细分几何体到更小的三角形,然后偏移offse这些新生成出来的顶点到正确的位置。这么做的原因是为了给mesh添加更多的细节,而不直接制作一个高精度模型的原因有以下几点:

  1. 在GPU做动态LOD。可以根据到相机的距离或者其他因素动态的控制mesh的细节程度。如果很远,就不需要那么多细节。
  2. Physics和Animation的效率。可以根据低面数的网格做物理和动画模拟,然后曲面细分到高面数的模型上去,从而提升效率。
  3. 节省内存。可以在memory中存储低面数的mesh,而通过GPU镶嵌为高面数的。

TS阶段位于VS和GS之间,是可选的。

13.1 TESSELLATION PRIMITIVE TYPES

当渲染tessellation时,不会提交triangle到 IA stage。而是提交 带有许多控制点的patches。 Dx支持patches带有 1~32 的控制点,它们定义的类型如下:

D3D11_PRIMITIVE_1_CONTROL_POINT_PATCH = 8,
D3D11_PRIMITIVE_2_CONTROL_POINT_PATCH = 9,
D3D11_PRIMITIVE_3_CONTROL_POINT_PATCH = 10,
D3D11_PRIMITIVE_4_CONTROL_POINT_PATCH = 11,
...
D3D11_PRIMITIVE_31_CONTROL_POINT_PATCH = 38,
D3D11_PRIMITIVE_32_CONTROL_POINT_PATCH = 39

一个triangle 可以被认为是 一个带有 3个 控制点的triangle patch(D3D11_PRIMITIVE_3_CONTROL_POINT_PATCH),所以普通的 triangle 也可以正常提交去细分。 一个 quad patch 可以提交带有 4个控制点(D3D11_PRIMITIVE_4_CONTROL_POINT_PATCH)。在TS阶段,这些patches 最终都会被细分成 triangles。

当控制点更多时,这些控制点被用来构建各种数学曲线和表面。通过控制点生成贝塞尔曲线或曲面,越多的控制点,能够控制的细节更多。

13.1.1 Tessellation and the Vertex Shader

由于我们提交 patch control point到渲染管线,因此vs处理的是 control points。因此一旦应用了曲面细分,vs更像是一个 针对控制点的vs,所以我们会在镶嵌之前处理控制点的工作。

13.2 THE HULL SHADER

hull shader有两种类型:1. Constant Hull Shader  2. Control Point Hull Shader。

13.2.1 Constant Hull Shader

constant hull buffer 每个patch都会执行,任务是 输出 mesh 的 tessellation factors。这个因子会通知TS阶段 镶嵌多少到这个patch。如下例子里 是一个有4个控制点的quad patch,所有边缘内部都镶嵌3次。

struct PatchTess
{
    float EdgeTess[4] : SV_TessFactor;
    float InsideTess[2] : SV_InsideTessFactor;
    // Additional info you want associated per patch.
};
PatchTess ConstantHS(InputPatch<VertexOut, 4> patch,
uint patchID : SV_PrimitiveID)
{
    PatchTess pt;
    // Uniformly tessellate the patch 3 times.
    pt.EdgeTess[0] = 3; // Left edge
    pt.EdgeTess[1] = 3; // Top edge
    pt.EdgeTess[2] = 3; // Right edge
    pt.EdgeTess[3] = 3; // Bottom edge
    pt.InsideTess[0] = 3; // u-axis (columns)
    pt.InsideTess[1] = 3; // v-axis (rows)
    return pt;
}

constant hull shader 输入是所有patch的控制点,定义是 InputPatch<VertexOut, 4>。这里之前说过 控制点会先经过vs,所以他们的类型,是被vs的输出 VertexOut决定的。在这个例子里,一个patch有4个控制点,所有 InputPatch的第二个模板参数是4,然后是给每个patch一个patch ID,系统语义 SV——PrimitiveID。在一个draw call,ID唯一对应一个patch。

constant hull shader 输出必须是tessellation factors,而因子取决于patch的拓扑。除了 tessellation factors (SV_TessFactor and SV_InsideTessFactor),也可以输出其他的patch信息,domain shader会以constant hull shader 的输出作为输入,可以使用这些额外的信息。

细分一个quad patch包括两部分:13.3.1 展示如何应用在TS阶段

  1. 四个边缘的tessellation factors控制 在每个边缘处细分多少。
  2. 两个内部的tessellation factors控制如何细分quad patch,一个控制水平,一个控制垂直。

细分一个triangle patch也包括两部分:13.3.2 展示如何应用在TS阶段

  1. 三个边缘的tessellation factors控制 在每个边缘处细分多少。
  2. 一个内部的tessellation factors控制如何细分triangle patch。

最大的tessellation factor在Dx中是64。如果所有的factors都是0,这个patch在之后的处理中就会被丢弃掉。这可以用于基于每个patch 做视椎体裁剪 或者背面剔除。

  1. 如果一个patch 在视椎体中是不可见的,那就可以直接丢弃掉,如果执行了细分,细分的triangle也会在 三角面剔除中被丢弃掉。
  2. 如果一个patch 是背面的,也可以直接丢弃掉,如果执行了细分,细分的triangle也会在光栅化的背面剔除中被丢弃掉。

一般这里会问的一个问题是,一般我们细分多少。所以首先 tessellation的主要目的是为了给mesh增加细节,然后我们并不需要增加user不需要的细节,因此细分多少主要从以下几个方面考虑:

  1. 距离相机的距离:越远细节需要的越少
  2. 屏幕占比:如果屏幕占比很小,可以之渲染低面数的模型
  3. 朝向:triangle相对于眼睛的朝向,沿轮廓边的triangle需要更精细一点
  4. 粗糙度:粗糙的表面相比于光滑的需要更多的细节。粗糙度的值可以预先计算好在表面贴图里,可以用于细分多少。

[Story10] 给了如下关于性能的建议:

  1. 如果tessellation factors是1(这代表不会真的细分),这种情况下考虑这个patch不要细分,否则会浪费GPU性能而不做什么事。
  2. 在GPU实现过程中,不要细分占比小于 8个pixel 的triangle。
  3. 集中batch处理tessellation,在draw call中切换细分开关是非常耗的。

13.2.2 Control Point Hull Shader

control point hull shader 输入一系列 control points,输出 一系列control points。每个control point都会调用一次。Hull shader的一个应用是为了改变表面的表现,从一个普通triangle(3个控制点) 可以变成 cubic贝塞尔triangle(10个控制点)。这种方式被叫做 N-patches scheme or PN triangles scheme。

如下第一个例子是一个简单的 pass-through shader,只传递 控制点 而不改变,实际的driver会优化这类只是pass-through的shader。

struct HullOut
{
    float3 PosL : POSITION;
};
[domain(“quad”)]
[partitioning(“integer”)]
[outputtopology(“triangle_cw”)]
[outputcontrolpoints(4)]
[patchconstantfunc(“ConstantHS”)]
[maxtessfactor(64.0f)]
HullOut HS(InputPatch<VertexOut, 4> p,
    uint i : SV_OutputControlPointID,
    uint patchId : SV_PrimitiveID)
{
    HullOut hout;
    hout.PosL = p[i].PosL;
    return hout;
}

这个hull shader 输入所有patch的控制点通过 InputPatch 参数。系统语义 SV_OutputControlPointID 给予每个控制点一个 index标示。注意这里 input的控制点数量不需要和 输出的个数匹配。

control point hull shader 有一系列属性:

  1. domain:标示patch的类型。可选的有 tri, quad, or isoline
  2. partitioning:指定tessellation的 subdivision mode。可选有“integer”:新添加或者删除的顶点,只跟factor的整数部分有关,小数部分忽略,这可能会引起突然的“popping”突变。Fractional tessellation“fractional_even/fractional_odd”:新添加或者删除的顶点,根据factor的整数部分计算,但根据小数部分做逐渐的slide偏移,这会让mesh的过度很平滑
  3. outputtopology:细分创建的triangle的旋转方向。triangle_cw:顺时针,triangle_ccw:逆时针,line:直线细分
  4. outputcontrolpoints:hull shader执行的次数,每执行一次输出一个control point。系统语义 SV_OutputControlPointID 给予每个输出的control point一个唯一index
  5. patchconstantfunc:string 指明 constant hull shader的函数名称
  6. maxtessfactor:指明shader能用的最大的factor值。用于硬件做潜在的优化,如果它知道factor的上限,可以先计算细分所需要的资源。在Dx中factor最大是64。

13.3 THE TESSELLATION STAGE

真正的TS阶段,是全部有硬件控制的,它基于factor 通过 constant hull shader 对patches进行细分,如下展示基于factor的不同细分方式。

13.3.1 Quad Patch Tessellation Examples

quad patch的细分基于 4个edge 的 factor和 2个内部的factor。

13.3.2 Triangle Patch Tessellation Examples

triangle patch的细分基于 3个 edge的factor 和一个内部的factor。

13.4 THE DOMAIN SHADER

TS阶段会输出所有新创建的顶点和三角形。TS阶段每个创建的顶点都会在这里调用一次domain shader。

当enable了tessellation,原本的vs更像是一个 vs for each control point,而hull shader 是vs for tessellated patch,因此原本的vs就需要在这里做,包括把所有顶点转到齐次裁剪空间。

对于一个quad patch,domain shader三个输入 factors 以及其他constant hull shader额外定义的 每个patch的额外的信息,tessellated顶点位置参数(u,v),以及control point hullshader输出的所有patch的控制点。值得注意的是domain shader给的并不是真正的细分的顶点位置,而是给的在patch domain space里这些点的uv坐标。如何使用uv坐标和有确定3D顶点位置的控制点取决于你,下面的例子会使用双线性差值(类似于纹理的线性采样)来计算。

细分一个有4个控制点的quad patch,到16个顶点,坐标系[0,1],顺序是逐行的。

struct DomainOut
{
    float4 PosH : SV_POSITION;
};
// The domain shader is called for every vertex created by the tessellator.
// It is like the vertex shader after tessellation.
[domain(“quad”)]
DomainOut DS(PatchTess patchTess,
    float2 uv : SV_DomainLocation,
    const OutputPatch<HullOut, 4> quad)
{
    DomainOut dout;
    // Bilinear interpolation.
    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);
    dout.PosH = mul(float4(p, 1.0f), gWorldViewProj);
    return dout;
}

triangle patch的domain shader是类似的,区别是不是(u,v),而是float3(u,v,w)作为输入(重心点坐标系),这么做的原因是贝塞尔三角patch是用重心点坐标系定义的。

13.5 TESSELLATING A QUAD

这个demo会提交quad patch到渲染管线,基于到相机的距离进行细分,用类似前面"Hills"demo的方式用数学方法替换生成顶点。

13.6 CUBIC BÉZIER QUAD PATCHES

讲述 cubic Bézier quad patches 是如何通过更多数量的控制点来构建平面。

13.6.1 Bézier Curves贝塞尔曲线

考虑三个非共线的顶点,p0, p1, p2三个控制点,可以定义一条贝塞尔曲线。

计算点p(t),先计算p0,p1中经 t 插值,p1,p2经 t 插值得到的点P01 P11

则 p(t)可以表示为

换句话说,通过重复插值就可以得到2维的贝塞尔曲线:

类似,重复插值得到3维的贝塞尔曲线,

第一次,第二次

最后

得到

假设t=0.5,下图展示了几次插值得到的结果

所以总结一般规律可以得到,

比如3维的

另外一个可以使用的贝塞尔曲线的求导结果,可以用于计算切线向量。

13.6.2 Cubic Bézier Surfaces 贝塞尔曲面

考虑一个 4*4 控制点的patch,每一行都有4个控制点,可以定义每一行的贝塞尔曲线为

以上得到4条贝塞尔曲线,分别q0(u),q1(u),q2(u),q3(u)。此时取定一点 u0,可以得到四条线上的四个点,q0(u0),q1(u0),q2(u0),q3(u0),这四个点又可以计算得到一条曲线,得到曲线p(v)。

最终让u,v均可变,就可以得到贝塞尔曲面 p(u,v)。

如图所示,

另外,对uv计算偏导数可以方便得到曲面切线和法线向量:

13.6.3 Cubic Bézier Surface Evaluation Code

这一节展示用代码表示贝塞尔曲面,主要是基于

13.6.4 Defining the Patch Geometry

代码展示 ,定义需要的patch buffer存储控制点,然后提交到IA阶段。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值