65.9K
CodeProject 正在变化。 阅读更多。
Home

驯服细分器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.38/5 (4投票s)

2012年3月16日

CPOL

4分钟阅读

viewsIcon

21951

downloadIcon

335

外壳着色器,域着色器

引言

本文旨在展示如何使用外壳着色器和域着色器在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之间的带宽。

© . All rights reserved.