细分
细分器
如果管线包含两个细分着色器(控制和评估),则细分器会消耗每个输入面(在顶点着色之后)并生成一组新的独立图元(点、线或三角形)。这些图元在逻辑上是通过根据细分控制着色器写入的每个面的外部和内部细分级别细分几何图元(矩形或三角形)来生成的。这些级别分别使用内置变量 TessLevelOuter
和 TessLevelInner
来指定。这种细分以依赖于实现的方式执行。如果管线中不存在细分着色器,则禁用细分器,并且传入的图元将不经修改地传递。
细分器执行的细分类型由一个 OpExecutionMode
指令指定,该指令使用 Triangles
、Quads
或 IsoLines
执行模式之一。当使用着色器对象时,此指令必须在细分评估着色器中指定,并且可以在细分控制着色器中指定。当使用管线时,此指令可以在细分评估着色器或细分控制着色器中指定。当使用着色器对象时,需要的与细分相关的模式必须在细分评估着色器中指定,并且可以在细分控制着色器中指定。其他与细分相关的模式可以在细分评估着色器中指定。当使用管线时,其他与细分相关的执行模式可以在细分控制着色器或细分评估着色器中指定。
在细分控制着色器和细分评估着色器中指定的任何与细分相关的模式必须相同。
细分执行模式包括
-
Triangles
、Quads
和IsoLines
。这些控制输出图元的细分类型和拓扑结构。当使用着色器对象时,必须在至少细分评估阶段中设置一个模式。当使用管线时,必须在至少一个细分着色器阶段中设置一个模式。如果启用了VK_KHR_portability_subset
扩展,并且 VkPhysicalDevicePortabilitySubsetFeaturesKHR::tessellationIsolines
为VK_FALSE
,则实现不支持等值线细分,并且在任何细分着色器阶段都不得使用IsoLines
。 -
VertexOrderCw
和VertexOrderCcw
。这些控制细分器生成的三角形的方向。当使用着色器对象时,必须在至少细分评估阶段中设置一个模式。当使用管线时,必须在至少一个细分着色器阶段中设置一个模式。 -
PointMode
。控制点的生成,而不是三角形或直线。此功能默认为禁用,如果任一着色器阶段包含执行模式,则启用此功能。当使用着色器对象时,如果在细分控制阶段中设置了PointMode
,则在细分评估阶段中必须完全相同地设置它。如果启用了VK_KHR_portability_subset
扩展,并且 VkPhysicalDevicePortabilitySubsetFeaturesKHR::tessellationPointMode
为VK_FALSE
,则实现不支持点模式细分,并且在任何细分着色器阶段都不得使用PointMode
。 -
SpacingEqual
、SpacingFractionalEven
和SpacingFractionalOdd
。控制细分图元边缘上的线段间距。当使用着色器对象时,必须在至少细分评估阶段中设置一个模式。当使用管线时,必须在至少一个细分着色器阶段中设置一个模式。 -
OutputVertices
。控制细分控制着色器的输出补丁的大小。当使用着色器对象时,必须至少在细分控制阶段设置一个值。当使用管线时,必须至少在其中一个细分着色器阶段设置一个值。
对于三角形,细分器将三角形图元细分为更小的三角形。对于四边形,细分器将矩形图元细分为更小的三角形。对于等值线,细分器将矩形图元细分为一系列线段,这些线段沿矩形在 u 维度上延伸(即,TessCoord
中的坐标形式为 (0,x) 到 (1,x),适用于共享一条线的所有细分评估着色器调用)。
细分器生成的每个顶点在归一化参数空间中都有一个关联的 (u,v,w) 或 (u,v) 位置,参数值在 [0,1] 范围内,如图 图 15. 细分图元模式的域参数化(左上角原点) 和 图 16. 细分图元模式的域参数化(左下角原点) 所示。域空间可以具有左上角或左下角原点,由 VkPipelineTessellationDomainOriginStateCreateInfo 的 domainOrigin
成员选择。
对于三角形,顶点的位置是重心坐标 (u,v,w),其中 u + v + w = 1.0,表示三角形的三个顶点对顶点位置的相对影响。对于四边形和等值线,位置是 (u,v) 坐标,表示顶点相对于细分矩形的相对水平和垂直位置。细分过程将在后续章节中详细说明。
细分器补丁丢弃
如果任何相关的外部细分级别小于或等于零,细分器将丢弃一个补丁。
如果任何相关的外部细分级别对应于支持 NaN 的实现中的浮点 NaN(非数字),则也会丢弃补丁。
对于丢弃的补丁,不会生成新的图元,也不会执行细分评估着色器。对于 Quads
,所有四个外部级别都相关。对于 Triangles
和 IsoLines
,只有前三个或两个外部级别分别相关。负的内部级别不会导致丢弃补丁;它们将被限制,如下所述。
细分器间距
每个细分级别都用于确定用于细分相应边缘的线段的数量和间距。用于推导线段的数量和间距的方法由细分控制或细分评估着色器中的 OpExecutionMode
指定,使用标识符 SpacingEqual
、SpacingFractionalEven
或 SpacingFractionalOdd
之一。
如果使用 SpacingEqual
,则首先将浮点细分级别钳制到 [1, maxLevel
],其中 maxLevel
是实现相关的最大细分级别 (VkPhysicalDeviceLimits
::maxTessellationGenerationLevel
)。结果向上舍入到最接近的整数 n,并且相应的边缘在 (u,v) 空间中被划分为 n 个相等长度的线段。
如果使用 SpacingFractionalEven
,则首先将细分级别钳制到 [2, maxLevel
],然后向上舍入到最接近的偶数整数 n。如果使用 SpacingFractionalOdd
,则将细分级别钳制到 [1, maxLevel
- 1],然后向上舍入到最接近的奇数整数 n。如果 n 为 1,则不会细分边缘。否则,相应的边缘将被划分为 n - 2 个相等长度的线段,以及另外两个相等长度的线段,这两个线段通常比其他线段短。这两个附加线段的长度相对于其他线段的长度将随着 n - f 而单调递减,其中 f 是钳制的浮点细分级别。当 n - f 为零时,附加线段的长度将与其他线段的长度相等。当 n - f 接近 2.0 时,附加线段的相对长度接近零。这两个附加线段必须对称地放置在细分边缘的相对两侧。这两个线段的相对位置是实现相关的,但对于任何一对具有相同 f 值的细分边缘,必须相同。
当使用具有分数奇数间距的点模式细分三角形或四边形时,如果内部细分级别小于或等于 1,则细分器可以生成位于补丁边缘的内部顶点。此类顶点被认为与细分补丁外边缘生成的顶点不同,即使存在具有相同坐标的顶点对。
细分图元排序
对于细分生成的图元的相对排序,提供的保证很少,因为它们与图元排序有关。
-
从每个输入图元生成的输出图元以实现相关的顺序传递到后续的管线阶段。
-
从给定输入图元生成的所有输出图元在从后续输入图元生成任何输出图元之前传递到后续的管线阶段。
细分器顶点缠绕顺序
当细分器生成三角形(在 Triangles
或 Quads
模式下)时,所有三角形的方向由细分控制或细分评估着色器中的 VertexOrderCw
或 VertexOrderCcw
的 OpExecutionMode
指定。如果顺序为 VertexOrderCw
,则所有生成的三角形的顶点在 (u,v) 或 (u,v,w) 空间中具有顺时针顺序。如果顺序为 VertexOrderCcw
,则顶点在该空间中具有逆时针顺序。
如果细分域的原点位于左上角,则当以下条件成立时,三角形的顶点具有逆时针顺序:
-
a = u0 v1 - u1 v0 + u1 v2 - u2 v1 + u2 v0 - u0 v2
如果 a 为负,则为逆时针顺序;如果 a 为正,则为顺时针顺序。ui 和 vi 是三角形第 i 个顶点在归一化参数空间中的 u 和 v 坐标。如果细分域的原点位于左下角,则当 a 为正时,三角形的顶点具有逆时针顺序;当 a 为负时,则为顺时针顺序。
值 a 与三角形的有符号面积成正比(具有正因子)。 在 |
三角形细分
如果细分图元模式为 Triangles
,则一个等边三角形会被细分为一组覆盖原始三角形区域的三角形。首先,原始三角形被细分为一组同心的等边三角形。每个三角形的边都被细分,并且每对三角形之间的区域由连接细分边上的顶点生成的三角形填充。同心三角形的数量和除最外层三角形之外的每个三角形上的细分数都来自第一个内部细分级别。最外层三角形的边独立细分,使用第一、第二和第三个外部细分级别来控制 u = 0(左)、v = 0(下)和 w = 0(右)边的细分数。第二个内部细分级别和第四个外部细分级别在此模式下不起作用。
如果第一个内部细分级别和所有三个外部细分级别在钳制和舍入后恰好为 1,则只会生成一个 (u,v,w) 坐标分别为 (0,0,1)、(1,0,0) 和 (0,1,0) 的三角形。如果内部细分级别为 1,并且任何外部细分级别大于 1,则内部细分级别将被视为最初指定为 1 + ε,并将根据细分间距产生两段或三段细分。当与分数奇数间距一起使用时,三段细分**可能**会生成位于三角形边缘上的*内部顶点*。
如果任何细分级别大于 1,则细分首先生成一组同心的内部三角形并细分其边。首先,使用钳制和舍入后的第一个内部细分级别和指定的细分间距,临时细分三个外边,生成 n 个线段。对于最外层的内三角形,如果 n 为 2,则内三角形是退化的——三角形中心的一个单点。否则,对于外三角形的每个角,会在垂直于角的两个相邻边的两条线的交点处生成一个内三角形角,这两条线穿过最靠近该角细分外边的顶点。如果 n 为 3,则内三角形的边不会被细分,并且它是同心三角形集合中的最后一个三角形。否则,内三角形的每条边被划分为 n - 2 个线段,该细分的 n - 1 个顶点通过与穿过外边细分的 n - 1 个最内侧顶点的垂直于该边的线相交来生成。一旦最外层的内三角形被细分,之前的细分过程会重复自身,使用生成的三角形作为外三角形。此细分过程在 内部三角形细分 中进行了说明。
一旦生成所有同心三角形并细分其边,则每对相邻内三角形之间的区域将完全填充一组不重叠的三角形。在此细分中,每个三角形的三个顶点中的两个取自一个三角形的细分边上的相邻顶点;第三个是另一个三角形的相应边上的一个顶点。如果最内侧的三角形是退化的(即一个点),则包含它的三角形将通过将该三角形上的六个顶点中的每一个与中心点连接起来细分为六个三角形。如果最内侧的三角形不是退化的,则该三角形按原样添加到生成的三角形集合中。
在填充与任何内三角形对应的区域后,细分器会生成三角形以覆盖最外层三角形和最内层三角形之间的区域。为此,上述外三角形边的临时细分将被丢弃。相反,u = 0、v = 0 和 w = 0 边分别根据第一、第二和第三个外部细分级别以及细分间距进行细分。保留第一个内三角形的原始细分。如上所述,外三角形和第一个内三角形之间的区域完全由不重叠的三角形填充。如果第一个(也是唯一的)内三角形是退化的,则通过将外三角形边上的每个顶点与中心点连接来生成一组三角形。
生成所有三角形后,根据细分三角形中每个顶点相对于外三角形的三个顶点的位置,为每个顶点分配重心 (u,v,w) 坐标。
用于将 (u,v,w) 空间中的三角形域细分为单个三角形的算法取决于具体实现。但是,生成的三角形集合将完全覆盖该域,并且该域的任何部分都不会被多个三角形覆盖。
四边形细分
如果细分图元模式是Quads
,则会将矩形细分为覆盖原始矩形区域的三角形集合。首先,将原始矩形细分为一个规则的矩形网格,其中沿 u = 0 和 u = 1(垂直)边以及 v = 0 和 v = 1(水平)边的矩形数量分别从第一个和第二个内部细分级别派生。所有矩形,除了那些与外矩形边缘相邻的矩形外,都被分解为三角形对。最外层的矩形边缘是独立细分的,使用第一个、第二个、第三个和第四个外部细分级别来分别控制 u = 0(左)、v = 0(底)、u = 1(右)和 v = 1(顶)边的细分数量。网格内部矩形与外部矩形边缘之间的区域由连接细分的外部边缘上的顶点到内部矩形网格边缘上的顶点生成的三角形填充。
如果钳制的内部细分级别和所有四个钳制的外部细分级别都恰好为 1,则仅生成覆盖外部矩形的单个三角形对。否则,如果任一钳制的内部细分级别为 1,则该细分级别将被视为最初指定为 1 + ε,并将根据细分间距产生两段或三段细分。当与分数奇数间距一起使用时,三段细分可能会在矩形的边缘产生内部顶点。
如果任何细分级别大于 1,则细分首先使用钳制并四舍五入的第一个内部细分级别和细分间距,将外矩形的 u = 0 和 u = 1 边细分为 m 段。使用第二个内部细分级别,将 v = 0 和 v = 1 边细分为 n 段。将 u = 0 和 v = 0 边上的每个顶点与 u = 1 和 v = 1 边上的相应顶点连接起来,生成一组垂直和水平线,将矩形划分为较小矩形的网格。图元生成器会发出覆盖每个不与外矩形边缘相邻的矩形的一对不重叠的三角形。由这些三角形覆盖的区域的边界形成一个内部矩形,其边缘由位于边缘上的网格顶点细分。如果 m 或 n 中任何一个是 2,则内部矩形是退化的,并且矩形的边缘中的一个或两个由单个点组成。此细分在图 内部四边形细分中进行了说明。
在填充完与内部矩形对应的区域后,细分器必须生成三角形以覆盖内部和外部矩形之间的区域。为此,上面外部矩形边缘的细分将被丢弃。相反,根据第一个、第二个、第三个和第四个外部细分级别以及细分间距,分别细分 u = 0、v = 0、u = 1 和 v = 1 边。保留内部矩形的原始细分。外部和内部矩形之间的区域完全由不重叠的三角形填充。每个三角形的三个顶点中的两个是某个矩形细分边缘上的相邻顶点;第三个是另一个矩形相应边缘上的顶点之一。如果最内部矩形的任何边缘是退化的,则通过将外部边缘上的每个顶点与构成内部边缘的单个顶点连接起来,来填充相应外部边缘附近的区域。
用于将 (u,v) 空间中的矩形域细分为单个三角形的算法取决于具体实现。但是,生成的三角形集将完全覆盖该域,并且该域的任何部分都不会被多个三角形覆盖。
等值线细分
如果细分图元模式是IsoLines
,则会绘制一组独立的水平线段。这些线段被排列成称为等值线的连接条,其中每个等值线的顶点都具有恒定的 v 坐标和覆盖整个范围 [0,1] 的 u 坐标。生成的等值线数量从第一个外部细分级别派生;每个等值线中的线段数量从第二个外部细分级别派生。在这种模式下,内部细分级别以及第三个和第四个外部细分级别都没有影响。
与上面的四边形细分一样,等值线细分从矩形开始。根据第一个外部细分级别细分矩形的 u = 0 和 u = 1 边。为了进行此细分,细分间距模式被忽略并被视为 equal_spacing。绘制一条等值线,将 u = 0 矩形边缘上的每个顶点连接到 u = 1 矩形边缘上的相应顶点,但 (0,1) 和 (1,1) 之间不绘制任何线。如果细分的 u = 0 和 u = 1 边缘上的等值线数量为 n,则此过程将生成 n 条等间距线,其 v 坐标恒定为 0、\(\frac{1}{n}, \frac{2}{n}, \ldots, \frac{n-1}{n}\)。
然后根据第二个外部细分级别和细分间距细分 n 条等值线中的每一条,从而产生 m 条线段。细分器会发出每条线的每个线段。这些线段的生成拓扑结构类似于直线列表,但每条线的生成顺序以及每个线段的顶点生成顺序都取决于具体实现。
如果启用了 |
细分点模式
对于所有基本图元模式,细分器能够生成点而不是线或三角形。如果细分控制或细分评估着色器指定了 OpExecutionMode
PointMode
,则图元生成器将为细分产生的每个不同顶点生成一个点,而不是发出三角形或线。否则,细分器将根据图元模式生成一系列线段或三角形。这些点的生成拓扑结构类似于点列表,但每个输入图元生成点的顺序是未定义的。
如果启用了 |
细分管线状态
VkGraphicsPipelineCreateInfo 的 pTessellationState
成员是指向 VkPipelineTessellationStateCreateInfo
结构的指针。
VkPipelineTessellationStateCreateInfo
结构定义如下:
// Provided by VK_VERSION_1_0
typedef struct VkPipelineTessellationStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkPipelineTessellationStateCreateFlags flags;
uint32_t patchControlPoints;
} VkPipelineTessellationStateCreateInfo;
-
sType
是一个 VkStructureType 值,用于标识此结构。 -
pNext
是NULL
或指向扩展此结构的结构的指针。 -
flags
保留供将来使用。 -
patchControlPoints
是每个面片的控制点数量。
// Provided by VK_VERSION_1_0
typedef VkFlags VkPipelineTessellationStateCreateFlags;
VkPipelineTessellationStateCreateFlags
是用于设置掩码的位掩码类型,但目前保留供将来使用。
VkPipelineTessellationDomainOriginStateCreateInfo
结构定义如下:
// Provided by VK_VERSION_1_1
typedef struct VkPipelineTessellationDomainOriginStateCreateInfo {
VkStructureType sType;
const void* pNext;
VkTessellationDomainOrigin domainOrigin;
} VkPipelineTessellationDomainOriginStateCreateInfo;
或等效的:
// Provided by VK_KHR_maintenance2
typedef VkPipelineTessellationDomainOriginStateCreateInfo VkPipelineTessellationDomainOriginStateCreateInfoKHR;
-
sType
是一个 VkStructureType 值,用于标识此结构。 -
pNext
是NULL
或指向扩展此结构的结构的指针。 -
domainOrigin
是一个 VkTessellationDomainOrigin 值,用于控制细分域空间的原点。
如果 VkPipelineTessellationDomainOriginStateCreateInfo
结构包含在 VkPipelineTessellationStateCreateInfo 的 pNext
链中,则它控制细分域的原点。如果此结构不存在,则相当于 domainOrigin
为 VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT
。
可能的细分域原点由 VkTessellationDomainOrigin 枚举指定
// Provided by VK_VERSION_1_1
typedef enum VkTessellationDomainOrigin {
VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT = 0,
VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT = 1,
// Provided by VK_KHR_maintenance2
VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT,
// Provided by VK_KHR_maintenance2
VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT_KHR = VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT,
} VkTessellationDomainOrigin;
或等效的:
// Provided by VK_KHR_maintenance2
typedef VkTessellationDomainOrigin VkTessellationDomainOriginKHR;
-
VK_TESSELLATION_DOMAIN_ORIGIN_UPPER_LEFT
指定域空间的原点位于左上角,如图 图 15. 细分图元模式的域参数化(左上原点)所示。 -
VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT
指定域空间的原点位于左下角,如图 图 16. 细分图元模式的域参数化(左下原点)所示。
此枚举会影响 VertexOrderCw
和 VertexOrderCcw
细分执行模式的解释方式,因为缠绕是相对于域的方向定义的。
要动态设置细分域空间的原点,请调用
// Provided by VK_EXT_extended_dynamic_state3 with VK_KHR_maintenance2 or VK_VERSION_1_1, VK_EXT_shader_object
void vkCmdSetTessellationDomainOriginEXT(
VkCommandBuffer commandBuffer,
VkTessellationDomainOrigin domainOrigin);
-
commandBuffer
是将命令记录到的命令缓冲区。 -
domainOrigin
指定细分域空间的原点。
当使用着色器对象进行绘制时,或者当图形管线在 VkPipelineDynamicStateCreateInfo::pDynamicStates
中设置了 VK_DYNAMIC_STATE_TESSELLATION_DOMAIN_ORIGIN_EXT
时创建时,此命令会设置后续绘制命令的细分域空间的原点。否则,此状态由用于创建当前活动管线的 VkPipelineTessellationDomainOriginStateCreateInfo::domainOrigin
值指定。