镶嵌器阶段会输出新建的所有顶点与三角形,在此阶段所创建顶点,都会逐一调用域着色器(domain shader)进行后续处理。随着曲面细分功能的开启,顶点着色器便化身为“处理每个控制点的顶点着色器”,而域着色器的本质实为“针对已经过镶嵌化的面片进行处理的顶点着色器”。特别是,我们可以在此将经镶嵌化处理的面片顶点投射到齐次裁剪空间。
对于四边形面片来讲,域着色器以曲面细分因子(还有一些来自常量外壳着色器所输出的每个面片 的附加信息)、控制点外壳着色器所输岀的所有面片控制点以及镶嵌化处理后的顶点位置参数坐标(u,v)作为输入。注意,域着色器给岀的并不是镶嵌化处理后的实际顶点位置, 而是这些点位于面片域空间(patch domain space )内的参数 坐标(u,v)。是否利用这些参数坐标以及控制点 来求取真正的3D顶点位置,完全取决于用户自己。在以下 代码中,我们将通过双线性插值(bilinear interpolation,其 工作原理与纹理的线性过滤相似)来实现这一点。
struct DomainOut
{
float4 PosH : SV_POSITION;
};
//每当镶嵌器(tessellator)创建顶点时都会调用域着色器。
//可以把它看作镶嵌处理阶段后的“顶点着色器”
// 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);
//置换贴图
// Displacement mapping
p.y = 0.3f*( p.z*sin(p.x) + p.x*cos(p.z) );
float4 posW = mul(float4(p, 1.0f), gWorld);
dout.PosH = mul(posW, gViewProj);
return dout;
}
域着色器处理三角形面片的方法与处理四边形面片的方法很相似,只是把顶点用类型为float3的 重心坐标(u,v,w)来表示,以此作为域着色器的输入, 从而取代参数坐标(u,v)。将三角形面片以重心坐标作为输出的原因,很可能是因为贝塞尔三角形面片都是用重心坐标来定义所导致的。