分割阶段

Direct3D 11 运行时支持实现分割的三个新阶段,这些阶段将低细节细分图面转换为 GPU 上的较高细节基元。 分割将高阶图面平铺(或分裂)成合适的结构以进行渲染。

通过在硬件中实施分割,图形管道可评估更低画质(多边形数量更少)模型并以更高画质进行渲染。 在可完成软件分割时,由硬件实施的分割可生成大量的视觉细节(包括位移映射的支持),无需为模型大小添加视觉细节,也不会影响刷新率。

细化优势

镶嵌:

  • 节省大量内存和带宽,使应用程序能够从低分辨率模型呈现更详细的图面。 Direct3D 11 管道中实现的分割技术还支持位移映射,从而产生惊人的表面细节。
  • 支持可缩放的呈现技术,例如可动态计算的连续或视图相关详细信息级别。
  • 通过在较低频率下执行成本高昂的计算, (对细节较低的模型) 执行计算,从而提高性能。 其中包括使用融合变形或变形目标实现逼真动画的混合计算,或者碰撞检测或柔体动力学的物理计算。

Direct3D 11 管道在硬件中实现分割,从而将工作从 CPU 卸载到 GPU。 如果应用程序实施大量的变形目标和/或更加复杂的蒙皮/变形模型,这将能够显著改善性能。 若要访问新的细化功能,必须了解一些新的管道阶段。

新管道阶段

分割使用 GPU 根据构建于四边形修补程序、三角形修补程序或等值线的图面计算更加精细的图面。 为了接近高阶图面,每个修补程序会通过分割因子细分为三角形、点或线。 Direct3D 11 管道使用三个新的管道阶段实现分割:

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

下图突出显示了 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(和可选 w)坐标和表面拓扑输出到域着色器阶段。

在内部,细化器分两个阶段运行:

  • 第一阶段使用 32 位浮点算法处理分割因子、修复制圆问题、处理极小因子、减少并合并因子。
  • 第二阶段根据所选分区类型,生成点或拓扑列表。 这是细化器阶段的核心任务,通过定点算法使用 16 位分数。 定点算法可实现硬件加速,同时维持可接受的精度。 例如,对于 64 米宽修补程序,该精度可实现 2 毫米分辨率的点。
分区类型 范围
fractional_odd [1...63]
fractional_even 分割因子范围:[2..64]
整型 分割因子范围:[1..64]
pow2 分割因子范围:[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 将着色器绑定到管道时,将从传递到运行时的已编译着色器中提取硬件的代码。 但首先,必须使用 CreateHullShader 和 CreateDomainShader 等 API 创建着色

通过创建外壳着色器并将其绑定至外壳着色器阶段(这会自动设置细化器阶段),进而实现分割。 要从细化修补程序生成最终顶点位置,你还需要创建域着色器并将其绑定至域着色器阶段。 启用分割后,输入装配器阶段的数据输入必须是修补数据。 也就是说,输入汇编程序拓扑必须是使用 IASetPrimitiveTopology 设置D3D11_PRIMITIVE_TOPOLOGY的修补常量拓扑。

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

如何:

本文档还包含用于初始化细化阶段的示例。

说明
如何:创建外壳着色器
创建外壳着色器。
如何:设计外壳着色器
设计外壳着色器。
如何:初始化细化器阶段
初始化分割阶段。
如何:创建域着色器
创建域着色器。
如何:设计域着色器
创建域着色器。

图形管道