`
xuela_net
  • 浏览: 489456 次
文章分类
社区版块
存档分类
最新评论

Windows 8 Directx 开发学习笔记(十四)使用几何着色器实现三角形细分

 
阅读更多

几何着色器是从DirectX 10才引入的着色器,是一个可选阶段,位于顶点着色器和像素着色器阶段之间。顶点着色器以顶点作为输入数据,而几何着色器以完整的图元作为输入数据,像点、直线、三角形等。之所以引进几何着色器是为了充分利用GPU的计算能力来生成几何结构和模型细节,减轻CPU的负担,让CPU更专注于逻辑控制。几何着色器的编程和其它着色器类似,在VS2012中默认生成的几何着色器代码如下:

struct GSOutput
{
    float4 pos : SV_POSITION;
};
 
[maxvertexcount(3)]
void main(
    triangle float4 input[3] :SV_POSITION,
    inout TriangleStream<GSOutput > output
)
{
    for (uint i = 0; i < 3;i++)
    {
       GSOutputelement;
       element.pos =input[i];
       output.Append(element);
    }
}

这部分代码没有对顶点进行任何处理,主要注意下几何着色器与其它着色器不同的地方:maxvertexcount用来说明几何着色器所能输出顶点的最大数量,因为几何着色器每次输出的顶点数目可以不同。而triangle则说明输入是一个三角形图元,input[3]表明每次读入三角形的三个顶点。TriangleStream说明输出是一个流类型的对象,它是一个描述三角形的顶点列表,在main方法中构造好一个顶点后,使用Append方法将其加入列表。关于几何着色器更详细的介绍当然还得参考书籍和MSDN,接下来就实现一个用几何着色器对三角形进行细分的效果。


可以看出,把一个三角形细分成四个三角形,总的顶点数增加一倍。(写到这里突然想到游戏中常用的材质细节选项,不知道是不是通过设置不同的细分参数来区别。)知道基本方法后就可以进行代码的编写。这次是对第十二篇实现的模型进行细分。

首先向项目中添加几何着色器,并重命名为SimpleGeometryShader。注意这里需要更改编译选项,之前的着色器模型是 4 级别 9_3 (/4_0_level_9_3),而几何着色器是在DirectX 10中才引入的,所以要更改着色器模型为4 (/4_0)

然后要修改几何着色器中的结构体定义,与顶点着色器对应:

struct VertexShaderOutput
{
    float4 posH : SV_POSITION;
    float3 posW   : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
};
 
struct GSOutput
{
    float4 posH : SV_POSITION;
    float3 posW : POSITION;
    float3 normal : NORMAL;
    float2 tex : TEXCOOD;
    float2 texa :TEXCOOD_ALPHA;
    uint   PrimID : SV_PrimitiveID;
};

几何着色器的输入是顶点着色器的输出,所以几何着色器的main方法定义如下:

// 将输入三角形分割为四个小三角形
[maxvertexcount(8)]
void main(
    triangle VertexShaderOutputinput[3],
    inout TriangleStream<GSOutput > output
    )

main方法中进行计算时与其它着色器没什么区别。首先生成5个数组,对应结构体的5个属性,而每个数组均包含6个元素,对应之前图中的6个顶点。

  
    // v1,v2,v3是原始顶点,m0,m1,m2是各边中点
    //
    //     示意图          新数组中的序号
    //       v1              5
    //       *               *
    //      / \             / \
    //     /  \            / a \
    //  m0*-----*m1      1*-----*3
    //   / \  / \        / \ c / \ 
    //  /   \/   \      / b \ / d \
    // *-----*-----*   *-----*-----*
    //v0    m2    v2   0     2     4
 
    float4 ph[6];
    ph[0] =input[0].posH;
    ph[1] =0.5f*(input[0].posH + input[1].posH);
    ph[2] =0.5f*(input[2].posH + input[0].posH);
    ph[3] =0.5f*(input[1].posH + input[2].posH);
    ph[4] =input[2].posH;
    ph[5] =input[1].posH;
 
    float3 pw[6];
    pw[0] =input[0].posW;
    pw[1] =0.5f*(input[0].posW + input[1].posW);
    pw[2] =0.5f*(input[2].posW + input[0].posW);
    pw[3] =0.5f*(input[1].posW + input[2].posW);
    pw[4] =input[2].posW;
    pw[5] =input[1].posW;
 
    float3 n[6];
    n[0] =input[0].normal;
    n[1] =normalize(input[0].normal + input[1].normal);
    n[2] =normalize(input[2].normal + input[0].normal);
    n[3] =normalize(input[1].normal + input[2].normal);
    n[4] =input[2].normal;
    n[5] =input[1].normal;
 
    float2 t[6];
    t[0] =input[0].tex;
    t[1] =0.5f*(input[0].tex + input[1].tex);
    t[2] =0.5f*(input[2].tex + input[0].tex);
    t[3] =0.5f*(input[1].tex + input[2].tex);
    t[4] =input[2].tex;
    t[5] =input[1].tex;
 
    float2 ta[6];
    ta[0] =input[0].texa;
    ta[1] =0.5f*(input[0].texa + input[1].texa);
    ta[2] =0.5f*(input[2].texa + input[0].texa);
    ta[3] =0.5f*(input[1].texa + input[2].texa);
    ta[4] =input[2].texa;
    ta[5] =input[1].texa;

生成所有需要的信息后就可以输出小三角形了。

// 画分割后的小三角形bcd
for(int i = 0; i < 5;++i)
{ 
   gOut.posH =ph[i];
   gOut.posW =pw[i];
   gOut.normal =n[i];
   gOut.tex =t[i];
   gOut.texa =ta[i];
   output.Append(gOut);
}

细分后下面的一排三角形b、c、d是连续的,构成三角形带,可以只循环5次。而上面的三角形a需要单独输出。

 
output.RestartStrip();

// 画分割后的小三角形a
gOut.posH =ph[1];
gOut.posW =pw[1];
gOut.normal =n[1];
gOut.tex = t[1];
gOut.texa = ta[1];
output.Append(gOut);
 
gOut.posH =ph[5];
gOut.posW =pw[5];
gOut.normal =n[5];
gOut.tex = t[5];
gOut.texa =ta[5];
output.Append(gOut);
 
gOut.posH =ph[3];
gOut.posW =pw[3];
gOut.normal =n[3];
gOut.tex = t[3];
gOut.texa =ta[3];
output.Append(gOut);

开始调用RestartStrip方法用来结束三角形带,重新开始输出顶点序列,让后面添加的三个顶点构成三角形。

至此,几何着色器的代码编写完成,而顶点着色器和像素着色器均无需更改,接下来就修改C++代码。首先为Renderer类添加新成员:

Microsoft::WRL::ComPtr<ID3D11GeometryShader> m_geometryShader;

然后在CreateDeviceResources方法中添加以下代码以载入几何着色器

auto loadGSTask = DX::ReadDataAsync("SimpleGeometryShader.cso");
auto createGSTask = loadGSTask.then([this](Platform::Array<byte>^ fileData) {
       DX::ThrowIfFailed(
           m_d3dDevice->CreateGeometryShader(
           fileData->Data,
           fileData->Length,
           nullptr,
           &m_geometryShader
           )
           );
    });

现在开始就能够在程序中使用几何着色器了。在Render方法中设置顶点着色器后面添加下面的代码,用来设置几何着色器:

// 设置几何着色器
    m_d3dContext->GSSetShader(
       m_geometryShader.Get(),
       nullptr,
       0
       );

另外,为了看清细分与不细分的区别,将水面设置为线框渲染模式,并改变了下观察角度。对比图如下:


上面是细分后的效果,下面是未细分的效果。

本篇文章源代码:Direct3DApp_HillWaveGS

原文地址:http://blog.csdn.net/raymondcode/article/details/8503985

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics