驯服细分器






4.38/5 (4投票s)
外壳着色器,域着色器
引言
本文旨在展示如何使用外壳着色器和域着色器在DX11中使用细分器。DX11中的细分允许将曲面(三角形)细分为更小的子曲面,并生成新的互连三角形,这些新三角形拥有自己的一组顶点,可以对其进行操作以创建各种效果。
背景
最好阅读有关DirectX11和细分、细节层次 (LOD) 的资料,还需要掌握基本的数学知识来计算域着色器创建的点的坐标。此外,还需要了解纹理采样,以便在域着色器中使用置换贴图,但这篇文章不会用到。置换贴图是创建曲面扰动的最常用方法,在大多数游戏和演示中都会使用。
Using the Code
像我之前的所有文章一样,代码必须始终被引用。此外,本文不会讨论DX11设备和渲染的创建。假设读者已经了解DirectX11设备和设备上下文。
附加的代码增加了对DX9网格的支持,并使用了DX-SDK中提供的网格:tiger.x。该代码使用DX9网格支持来构建DX11顶点/索引缓冲区,其讨论超出了本文的范围(本文主要讨论外壳着色器和域着色器)。
首先创建一个DX11设备(细分器仅在DX11中可用)。
您可以使用以下代码
D3D_FEATURE_LEVEL uFeatureLevel=D3D_FEATURE_LEVEL_11_0;
D3D_FEATURE_LEVEL uFeatureLevel1;
HRESULT hr =D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE ,NULL, 0,
&uFeatureLevel, 1, D3D11_SDK_VERSION, &sd, &pSwapChain,
&pd3dDevice, &uFeatureLevel1,&pDeviceContext);
if(hr!=S_OK)
hr=D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_REFERENCE ,NULL, 0,
&uFeatureLevel, 1, D3D11_SDK_VERSION, &sd, &pSwapChain,
&pd3dDevice, &uFeatureLevel1,&pDeviceContext);
确保基元拓扑设置为D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST
,这是外壳/域着色器所必需的。这将告诉DX11运行时,输入的是要由外壳着色器操作的控制点,并将由细分器用于生成额外的顶点。
pDeviceContext->IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
现在到了最好的部分,编写外壳/域着色器。
我们首先定义要在整个着色器管道中使用的结构。
struct VS_INPUT {float4 Pos : POSITION;float3 vNormal : NORMAL;float2 Tex : TEXCOORD0;};
struct PS_INPUT {float4 Pos : SV_POSITION;float3 vNormal : NORMAL;float2 Tex : TEXCOORD0;};
接下来是创建要发送到固定功能细分器的结构,其中包含所需细分级别信息。固定功能细分器将使用控制点在曲面上生成额外的点。
struct HS_CONSTANT_DATA_OUTPUT { float Edges[3] : SV_TessFactor; float Inside : SV_InsideTessFactor; };
外壳着色器本身分为两部分
面片常量函数
面片常量函数为每个面片执行一次,以计算对整个面片都恒定的任何数据(直接来自MSDN)。
下面的代码将细分因子指定为1,增加该值可以增加细分器的负载并细分曲面。由于输入面片可用,因此可以根据输入面片值来控制细分(以及LOD)。(最好只在物体更靠近视点时增加LOD)。
HS_CONSTANT_DATA_OUTPUT ConstantHS( InputPatch<PS_INPUT, 3> ip, uint PatchID : SV_PrimitiveID )
{
HS_CONSTANT_DATA_OUTPUT Output;
Output.Edges[0] = 1;Output.Edges[1] = 1;Output.Edge[2]= 1;
Output.Inside = 1;
return Output;
}
外壳着色器
此着色器将为发送的每个点运行一次,类似于顶点着色器。
我们还向细分器指定控制点和所需的细分类型。
在下面的代码中,我们将点指定为三角形(“tri”),曲面分区指定为“integer”(您可以尝试其他曲面分区,例如Fractional_odd、Fractional_even等),输出拓扑为三角形,有三个控制点,还有此外壳着色器要使用的面片函数。
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_ccw")]
[outputcontrolpoints(3)]
[patchconstantfunc("ConstantHS")]
PS_INPUT HS( InputPatch<PS_INPUT, 3> p, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID )
{
PS_INPUT Output;
Output.Pos = p[i].Pos; Output.vNormal = p[i].vNormal; Output.Tex = p[i].Tex;
return Output;
}
域着色器
下一部分(更重要的是)是域着色器,此着色器并行地处理细分器创建的每个点。域着色器从外壳着色器转换后的控制点生成曲面几何体。可以在这里使用置换贴图来创建曲面的扰动(从而增加细节)。
对于四边形:[domain("quad")],点的坐标以相对于提供给细分器的四边形控制点的UV坐标提供:float2 UV : SV_DomainLocation
对于三角形(在附加代码中使用):[domain("tri")],点的坐标以相对于提供的三角形面片控制点的UVW重心坐标提供。请注意,根据重心坐标,新的位置、法线等是使用域位置提供的重心计算的(在本例中为UVW)。
[domain("tri")]
PS_INPUT DS( HS_CONSTANT_DATA_OUTPUT input, float3 UVW :
SV_DomainLocation, const OutputPatch<PS_INPUT, 3> quad )
{
PS_INPUT Output;
Output.Pos = UVW.x * quad[0].Pos + UVW.y * quad[1].Pos + UVW.z * quad[2].Pos;
Output.vNormal= UVW.x * quad[0].vNormal + UVW.y * quad[1].vNormal + UVW.z * quad[2].vNormal;
Output.Tex= UVW.x * quad[0].Tex + UVW.y * quad[1].Tex + UVW.z * quad[2].Tex;
return Output;
}
域着色器的输出是像素着色器的输入。
顶点着色器、几何着色器和像素着色器在上一代DirectX10运行时中可用。
此处不考虑几何着色器,因为它可选。
设置外壳和域着色器,如下所示
technique10 Main
{
pass P0
{
SetVertexShader ( CompileShader( vs_5_0, VS() ) );
SetPixelShader ( CompileShader( ps_5_0, PS() ) );
SetHullShader ( CompileShader( hs_5_0, HS() ) );
SetDomainShader ( CompileShader( ds_5_0, DS() ) );
}
}
我希望这篇文章能帮助您了解DX11中提供的细分器。
关注点
DX11细分器允许开发人员根据模型的位置缩放LOD。置换贴图允许开发人员创建逼真的粗糙曲面,而无需增加模型的多边形数量(细分器将执行此操作),这避免了生成新顶点的麻烦,并且不会因发送大量的顶点数据而阻塞CPU和GPU之间的带宽。