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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章