分割阶段

Direct3D 11 运行时支持实现分割的三个新阶段,该阶段将低细节细分图面转换为 GPU 上的更详细基元。 分割图块(或分解)高阶图面成适合呈现的结构。

通过在硬件中实现分割,图形管道可以评估较低的详细信息(较低的多边形计数)模型并更详细地呈现。 虽然可以完成软件分割,但硬件实现的分割可以生成令人难以置信的视觉细节(包括对位移映射的支持),而无需将视觉细节添加到模型大小和瘫痪刷新率。

分割优势

棋盘形布置:

  • 节省大量内存和带宽,这样应用程序就可以从低分辨率模型呈现更详细的图面。 Direct3D 11 管道中实现的分割技术还支持排量映射,从而产生惊人的表面细节。
  • 支持可缩放的呈现技术,例如可动态计算的连续或视图依赖的详细信息级别。
  • 通过以较低的频率执行昂贵的计算来提高性能(对较低细节模型执行计算)。 这可能包括使用混合形状或平滑目标进行混合计算,以便进行逼真的动画或物理计算,以便进行碰撞检测或软体动态。

Direct3D 11 管道在硬件中实现细化,将工作从 CPU 卸载到 GPU。 如果应用程序实现大量平滑目标和/或更复杂的外观/变形模型,这可能会导致性能提升非常大。 若要访问新的分割功能,必须了解一些新的管道阶段。

新管道阶段

分割使用 GPU 从从四面补丁、三角形补丁或等线构造的图面中计算更详细的图面。 为了近似于高排序图面,每个补丁都使用细化因子细分为三角形、点或线条。 Direct3D 11 管道使用三个新的管道阶段实现分割:

  • Hull-Shader 阶段 - 一个可编程着色器阶段,它生成对应于每个输入补丁(四角、三角形或线条)的几何补丁(和补丁常量)。
  • 细化器阶段 - 一个固定函数管道阶段,用于创建表示几何图形修补程序的域的采样模式,并生成连接这些样本的一组较小的对象(三角形、点或线条)。
  • Domain-Shader 阶段 - 一个可编程着色器阶段,用于计算对应于每个域样本的顶点位置。

下图突出显示了 Direct3D 11 管道的新阶段。

direct3d 11 管道的 图,其中突出显示了外壳着色器、细化器和域着色器阶段

下图显示了细化阶段的进度。 进度从低细节细分图面开始。 接下来的进展突出显示了输入修补程序,其中包含连接这些样本的相应几何图形修补程序、域示例和三角形。 进度最终突出显示了对应于这些样本的顶点。

细化进度图

Hull-Shader 阶段

外壳着色器(每个补丁调用一次)将定义低序表面的输入控制点转换为构成修补程序的控制点。 它还对每个补丁计算执行一些作,为分割阶段和域阶段提供数据。 在最简单的黑盒级别,外壳着色器阶段看起来类似于下图。

外壳着色器阶段图

外壳着色器是使用 HLSL 函数实现的,具有以下属性:

  • 着色器输入介于 1 到 32 个控制点之间。
  • 着色器输出介于 1 到 32 个控制点之间,而不考虑分割因子的数量。 域着色器阶段可以使用外壳着色器的控制点输出。 域着色器可以使用修补常量数据;域着色器和分割阶段可以使用细化因子。
  • 分割因素确定每个修补程序的细分量。
  • 着色器声明分割器阶段所需的状态。 这包括控制点数、修补人脸的类型以及分割时要使用的分区类型等信息。 此信息通常显示为着色器代码前面的声明。
  • 如果外壳着色器阶段将任何边缘细化因子设置为 = 0 或 NaN,则会剔除补丁。 因此,分割器阶段可能或可能不会运行,域着色器将不会运行,并且不会为该修补程序生成任何可见输出。

在更深层次上,外壳着色器实际上在两个阶段运行:控制点阶段和补丁常量阶段,硬件并行运行。 HLSL 编译器提取外壳着色器中的并行度,并将其编码为驱动硬件的字节码。

  • 控制点阶段针对每个控制点运行一次,读取修补程序的控制点,并生成一个输出控制点(由 ControlPointID 标识)。
  • 修补常量阶段每修补程序运行一次,以生成边缘分割因子和其他每修补程序常量。 在内部,许多修补程序常量阶段可能同时运行。 修补常量阶段对所有输入和输出控制点具有只读访问权限。

下面是外壳着色器的示例:

[patchsize(12)]
[patchconstantfunc(MyPatchConstantFunc)]
MyOutPoint main(uint Id : SV_ControlPointID,
     InputPatch<MyInPoint, 12> InPts)
{
     MyOutPoint result;
     
     ...
     
     result = TransformControlPoint( InPts[Id] );

     return result;
}

有关创建外壳着色器的示例,请参阅 如何:创建外壳着色器

细化器阶段

细化器是通过将外壳着色器绑定到管道来初始化的固定函数阶段(请参阅 如何:初始化细化器阶段)。 分割器阶段的目的是将域(象限、三维或线条)细分为许多较小的对象(三角形、点或线条)。 细化器在规范化(零对一)坐标系中平铺规范域。 例如,四边形域被分割为单位方块。

细化器使用细化因子(指定如何细化域)和分区类型(指定用于切片修补程序的算法)使用分割因子(指定从外壳着色器阶段传入的修补程序的算法)为每个补丁运行一次。 分割器将 uv((可选)坐标和表面拓扑输出到域着色器阶段。

在内部,分割器以两个阶段运行:

  • 第一阶段使用 32 位浮点算术处理细化因子、修复舍入问题、处理非常小的因素、减少和组合因子。
  • 第二阶段根据所选分区的类型生成点或拓扑列表。 这是分割器阶段的核心任务,使用具有固定点算术的 16 位分数。 固定点算术允许硬件加速,同时保持可接受的精度。 例如,给定 64 米宽的补丁,此精度可以放置 2 毫米分辨率的点。
分区类型 范围
fractional_odd [1...63]
fractional_even TessFactor 范围:[2..64]
整数 TessFactor 范围:[1..64]
pow2 TessFactor 范围:[1..64]

Domain-Shader 阶段

域着色器计算输出修补程序中细分点的顶点位置。 域着色器按细化器阶段输出点运行一次,并且对细化器阶段输出 UV 坐标、外壳着色器输出补丁和外壳着色器输出修补程序常量具有只读访问权限,如下图所示。

域着色器阶段关系图

域着色器的属性包括:

  • 每个输出坐标从分割器阶段调用一次域着色器。
  • 域着色器使用外壳着色器阶段的输出控制点。
  • 域着色器输出顶点的位置。
  • 输入是外壳着色器输出,包括控制点、修补常量数据和分割因子。 分割因子可以包括固定函数分割器使用的值以及原始值(例如,按整数分割进行舍入前),从而促进地貌。

域着色器完成后,分割完成,管道数据将继续下一个管道阶段(几何着色器、像素着色器等)。 当分割处于活动状态时,需要具有相邻基元(例如,每个三角形 6 个顶点)的几何着色器无效(这会导致未定义的行为,调试层将抱怨)。

下面是域着色器的示例:

void main( out    MyDSOutput result, 
           float2 myInputUV : SV_DomainPoint, 
           MyDSInput DSInputs,
           OutputPatch<MyOutPoint, 12> ControlPts, 
           MyTessFactors tessFactors)
{
     ...

     result.Position = EvaluateSurfaceUV(ControlPoints, myInputUV);
}

用于初始化细化阶段的 API

分割是通过两个新的可编程着色器阶段实现的:外壳着色器和域着色器。 这些新的着色器阶段使用着色器模型 5 中定义的 HLSL 代码进行编程。 新的着色器目标为:hs_5_0和ds_5_0。 与所有可编程着色器阶段一样,当着色器使用 DSSetShaderHSSetShader等 API 将着色器绑定到管道时,硬件的代码将从编译的着色器提取到运行时。 但首先,必须使用 CreateHullShaderCreateDomainShader等 API 创建着色器。

通过创建外壳着色器并将其绑定到外壳着色器阶段(这会自动设置分割器阶段),来启用分割。 若要从细化修补程序生成最终顶点位置,还需要创建域着色器并将其绑定到域着色器阶段。 启用分割后,输入汇编程序阶段的数据输入必须是修补数据。 也就是说,输入汇编程序拓扑必须是具有 IASetPrimitiveTopologyD3D11_PRIMITIVE_TOPOLOGY 集的修补常量拓扑。

若要禁用分割,请将外壳着色器和域着色器设置为 NULL。 几何着色器阶段和流输出阶段都不能读取外壳着色器输出控制点或修补数据。

作方法:

本文档还包含初始化分割阶段的示例。

项目 描述
如何:创建外壳着色器
创建外壳着色器。
如何:设计外壳着色器
设计外壳着色器。
如何:初始化细化器阶段
初始化分割阶段。
如何:创建域着色器
创建域着色器。
如何:设计域着色器
创建域着色器。

图形管道