本文是对此文的翻译,有模糊之处还请移步原文
D3D11为了实现曲面细分(低模变高模),在GPU管线中新增了三个渲染步骤:
- Hull-Shader stage
- Tessellation stage
- Domain-Shader Stage
曲面细分既可以由硬件(显卡)实现,也可以由软件实现。硬件实现的曲面细分对于刷新率不会造成什么影响,而软件实现的就不一定了。
曲面细分需要shader model 5.0才能支持。
Tessellation Benefits
通过曲面细分技术,可以只传入一个低模到GPU就能得到一个高精度模型的效果。这种方式无疑能够节省带宽以及内存。比如使用这种技术来实现视差贴图(用这种技术实现的效果已经不能称为视差贴图了,直接就是高模),可以得到非常amazing的效果,而消耗非常低,可以说是性价比非常高的方案。
通过曲面细分,可以非常方便的支持LOD(模型或者地形)的实现。
曲面细分可以通过低频计算(低模运算)得到高精效果,从而实现性能提升。比如对于骨骼模型,一些低模角色的低模面片在经过曲面细分之后,根据骨骼的加权计算新生成的顶点的位置,就能够实现高模效果。
D3D11的曲面细分技术是通过硬件实现的,从而将计算压力从CPU转移到GPU,对于场景中存在大量的可变形物体的情况下,这样的方案的性能表现会更优秀。前面说到,曲面细分主要可以分为三个新步骤:Hull-Shader,Tessellation,Domain-Shader。下面对这几个新的步骤进行介绍。
New Pipeline Stages
曲面细分的基本表现是从四边形面片,三角形面片或者等高线中构造出更多的更精细的几何单元,其中原来的每个几何单元(四边形面片,三角形面片或者等高线)都会根据传入的曲面细分系数被细分成更多的三角形,点或者线段等几何单元。其实现过程主要包含以下三个阶段:
Hull-Shader Stage - 这是一个新的可编程shader阶段,这个阶段是紧接着顶点shader之后进行的,其处理的基本单元是一个片元(patch),比如说如果片元是三角形的话,那么这个shader的处理就是针对整个三角形而进行的。其输入是顶点shader输出的控制点(也就是经过顶点shader处理后的顶点),这个阶段的输出有两个部分:
- 根据顶点shader的输入控制点进行处理后输出的新的顶点坐标
- 对于片元上的每条边,控制其细分因子(比如说设置为2,那么就一分为2)以及片元内部的细分因子(如果设置为3,则是分成三块)
![](https://img.haomeiwen.com/i19200103/fc9e9e0cd5b376aa.png)
上面这张图的各边细分因子分别为3,5,2,而内部细分因子则是3(如果是4,那么就将三根虚线继续延伸到三角形中心进行交汇)(参考文章)。在hull shader中指定各个分割系数的时候还需要考虑各个片元之间的衔接关系,避免两个共用一条边的片元在这条边上的分割系数不同而导致的cracks。(详情参见这篇文章)。此外,这些分割系数是支持浮点数的,浮点数得到的结果是两个分割结果的一个过渡。
这个阶段目的是为每个输入的几何片元(每个面片)生成一个新的几何单元批次以及对应的批次常量(比如指定每条边需要划分成多少份之类)
Tessellator Stage - 紧接着hullshader的是是一个由硬件自行完成的固定管线Stage。在这个步骤中,GPU会在输入的面片上创建一系列的采样点(顶点),之后生成一套连接这些采样点的细小片元。
Domain-Shader Stage - 紧接着tessellation stage的是Domain-Shader阶段,这个阶段也是通过可编程shader实现的,在这个stage中会对上一步生成的采样点进行顶点位置调整,使得最终生成的细小片元集合能够比较好的契合高模的形状。
![](https://img.haomeiwen.com/i19200103/fe85960c6f207d2f.png)
下图给出了曲面细分各个阶段的的结果。从低模面片开始,在hull shader中指定对应的细分系数,在tessellation阶段生成对应的多个细小片元,在domain阶段为生成的细小片元的顶点进行位置调整。
![](https://img.haomeiwen.com/i19200103/387ddc7632621e64.png)
Hull-Shader Stage
hull shader是以片元为单位运行的,在这个shader中:会对输入的顶点进行变换(实际上大部分情况都是维持输入,而不会做修正),并对片元的边与内部区域进行分割处理,其输入输出大致可以如下图所示:
![](https://img.haomeiwen.com/i19200103/1a0d8220b29e10d3.png)
输入的顶点(控制点)经过hull shader处理后会输出给domain shader。片元分割常量(也就是边与片元内部的分割系数)也会输出给后续的tessellation阶段与domain shader用以进行片元分割。
细分因子决定了片元是如何进行分割:包括了控制点的数目,片元面片的类型以及在进行细分时候的分割类型。
如果在hull shader中将某条边的细分因子设置为0或者NaN,那么就会直接移除这个片元,并跳过后续的tessellation以及domain shader 的运行。
在实际运行hull shader时,总体来说,有两个部分需要计算,一个是控制点的转换,另一个是细分因子的计算,这两个部分都是由硬件完成,且是并行运行的。
控制点的计算实际上就是对片元的顶点进行转换处理。
片元常量的处理,就是将各边的分割方法与内部的分割方法设置好,并没有太多的计算,在这个处理过程中对于输入输出的控制点都是只读的。
hull shader示例:
[patchsize(12)]
[patchconstantfunc(MyPatchConstantFunc)]
MyOutPoint main(uint Id : SV_ControlPointID,
InputPatch<MyInPoint, 12> InPts)
{
MyOutPoint result;
...
result = TransformControlPoint( InPts[Id] );
return result;
}
For an example that creates a hull shader, see How To: Create a Hull Shader.
Tessellator Stage
tessellation是不能通过编程进行调整的固定管线组件,在这个阶段中会读取hull shader的分割系数对各个片元进行分割。tessellator会按照一种非常简洁的方式对片元进行分割。比如说,对于一个quad片元,tessellation会将片元放到一个归一化的坐标空间进行分割,比如说,一个四边形会被当成一个单位变长的正方形来进行分割。
tessellation过程对于每个片元只执行一次,在这个过程中会读取hull shader传入的分割系数以及分割类型来对片元进行分割,分割完成后,会将uv坐标(这里说的uv坐标不是贴图坐标,而是后续用于计算新生成的顶点位置的加权系数,对于quad-based片元而言只需要uv坐标即可,而对于三角片元而言(三个控制点),则可能需要uvw三个坐标才行,通常有u+v+w=1的关系,即使需要w坐标,也不需要通过tessellator传入)以及面片的拓扑结构(这个片元有多少个顶点,这些顶点是如何相互连接的)传入domain shader。
主要可以分成两个部分:
第一个部分进行片元分割系数的处理:四舍五入问题,小分割因子,分割因子的缩减与合并。这个过程执行的时候用的是32位浮点数。
第二个部分则是根据分割类型生成对应的顶点或者几何面片列表。这个过程是使用16位的定点运算完成的,顶点运算可以在保证一定精度的情况下得到硬件加速。
| Type of Partitioning | Range |
| Fractional_odd | [1...63] |
| Fractional_even | TessFactor range: [2..64] |
| Integer | TessFactor range: [1..64] |
| Pow2 | TessFactor range: [1..64] |
Domain-Shader Stage
domain shader用于对上一阶段生成的顶点进行位置变换。对于上一阶段输出的每个顶点,都会运行一遍domain shader,在这个阶段中对于上一步生成的顶点的uv坐标以及hull shader的输出数据等是只读的。
![](https://img.haomeiwen.com/i19200103/bdc15307e63ccaa7.png)
domain shader的特性有:
对每个输出顶点运行一次。
需要使用到hull shader输出的控制点(原始顶点)数据
对每个输出顶点的坐标进行计算
以hull shader的控制点输出,片元常量数据输出以及分割因子输出作为输入,分割因子数据包括tessellation使用之前的原始数据以及被tessellation处理过的更新数据。
在domain shader完成之后,我们就得到了细分过的面片数据,这些数据就会进入下一个阶段(geometry shader,pixel shader等)的处理过程。对于geometry shader来说,如果其需要的数据是一个片元以及其周边的数据(比如一个三角面片就需要六个顶点数据)在tessellation生效的时候,可能就会变得不可用(毕竟输出的数据已经发生了变化,边都分割成好多段了)
domain shader:
void main( out MyDSOutput result,
float2 myInputUV : SV_DomainPoint,
MyDSInput DSInputs,
OutputPatch<MyOutPoint, 12> ControlPts,
MyTessFactors tessFactors)
{
...
result.Position = EvaluateSurfaceUV(ControlPoints, myInputUV);
}
APIs for initializing Tessellation Stages
Tessellation is implemented with two new programmable shader stages: a hull shader and a domain shader. These new shader stages are programmed with HLSL code that is defined in shader model 5. The new shader targets are: hs_5_0 and ds_5_0. Like all programmable shader stages, code for the hardware is extracted from compiled shaders passed into the runtime when shaders are bound to the pipeline using APIs such as DSSetShader and HSSetShader. But first, the shader must be created using APIs such as CreateHullShader and CreateDomainShader.
Enable tessellation by creating a hull shader and binding it to the hull-shader stage (this automatically sets up the tessellator stage). To generate the final vertex positions from the tessellated patches, you will also need to create a domain shader and bind it to the domain-shader stage. Once tessellation is enabled, the data input to the input-assembler stage must be patch data. That is, the input assembler topology must be a patch constant topology from D3D11_PRIMITIVE_TOPOLOGY set with IASetPrimitiveTopology.
To disable tessellation, set the hull shader and the domain shader to NULL. Neither the geometry-shader stage nor the stream-output stage can read hull-shader output-control points or patch data.
New topologies for the input-assembler stage, which are extensions to this enumeration.
enum D3D11_PRIMITIVE_TOPOLOGY
The topology is set to the input-assembler stage using IASetPrimitiveTopology
Of course, the new programmable shader stages require other state to be set, to bind constant buffers, samples, and shader resources to the appropriate pipeline stages. These new ID3D11Device methods are implemented for setting this state.
网友评论