DirectX11 With Windows SDK--16 流輸出階段

前言

在上一章,咱們知道了如何使用幾何着色器來從新組裝圖元,好比從一個三角形分裂成三個三角形。可是爲了實現更高階的分形,咱們必需要從幾何着色器拿到輸出的頂點。這裏咱們可使用可選的流輸出階段來拿到頂點集合。html

注意: 本章末尾有大量的GIF動圖!git

在此以前須要額外瞭解的章節以下:github

章節回顧
15 幾何着色器初探
Visual Studio圖形調試器詳細使用教程(編程捕獲部分)

DirectX11 With Windows SDK完整目錄編程

Github項目源碼數組

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。app

流輸出階段

如今咱們知道GPU能夠寫入紋理(textures),例如深度/模板緩衝區以及後備緩衝區。固然,咱們也能夠經過渲染管線的流輸出階段讓GPU將幾何着色器輸出的頂點集合寫入到指定的頂點緩衝區(vertex buffer)。除此以外,咱們還可以指定不進行光柵化以及後續的全部階段,僅讓頂點數據通過流輸出階段。ide

在幾何着色器中,最多四個流輸出對象能夠被設置,即幾何着色器的入口函數中只容許設置四個流輸出對象的參數。當多個流輸出對象存在時,它們必須都要爲PointStream類模板,但容許模板參數不一樣。輸出的頂點回流到頂點緩衝區後能夠再次進行一遍新的渲染管線流程。函數

上一章也提到,幾何着色器的單次調用不能產出超過1024個標量。所以分配給全部流輸出對象的標量總和不能超過1024。好比如今我有2個流輸出對象,它們的結構體相同,容納512個標量,那最多僅容許輸出2個這樣的頂點來分配給這2個流輸出對象。佈局

流輸出狀態的配置

ID3D11DeviceContext::SOSetTargets方法--綁定流輸出對應用於接收數據的頂點緩衝區

void ID3D11DeviceContext::SOSetTargets(
  UINT         NumBuffers,              // [In]頂點緩衝區數目
  ID3D11Buffer * const *ppSOTargets,    // [In]頂點緩衝區數組
  const UINT   *pOffsets                // [In]一個數組包含對每一個頂點緩衝區的字節偏移量
);

該方法多容許設置4個頂點緩衝區。ui

每一個要綁定到流輸出階段的緩衝區資源必需要在建立的時候額外設置D3D11_BIND_STREAM_OUTPUT綁定標籤。

若偏移值設爲-1,則會引發流輸出緩衝區被追加到最後一個緩衝區的後面

頂點緩衝區綁定到流輸出階段的輸出槽0操做以下:

UINT offset = 0;
m_pd3dImmediateContext->SOSetTargets(1, vertexBufferOut.GetAddressOf(), &offset);

若是咱們須要恢復默認的狀態,則能夠這樣調用:

ID3D11Buffer* nullBuffer = nullptr;
UINT offset = 0;
m_pd3dImmediateContext->SOSetTargets(1, &nullBuffer, &offset);

注意: 若是使用的是當前綁定到輸入裝配階段的頂點緩衝區,則綁定會失效。由於頂點緩衝區不能夠同時被綁定到輸入裝配階段和流輸出階段。

由於後續咱們是將每一階輸出的頂點都保存下來,即使不須要交換頂點緩衝區,但也有可能出現同時綁定輸入/輸出的狀況。一種合理的綁定順序以下:

// 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
UINT stride = sizeof(VertexPosColor);
UINT offset = 0;
ID3D11Buffer * nullBuffer = nullptr;
m_pd3dImmediateContext->SOSetTargets(1, &nullBuffer, &offset);
// ...
m_pd3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get());
// ...
m_pd3dImmediateContext->SOSetTargets(1, vertexBufferOut.GetAddressOf(), &offset);

當渲染管線完成一次流輸出後,咱們就能夠用下面的方法來獲取綁定在流輸出階段上的頂點緩衝區(固然你自己持有該緩衝區的指針的話就不須要了)

ID3D11DeviceContext::SOGetTargets方法--獲取綁定在流輸出階段的頂點緩衝區

void ID3D11DeviceContext::SOGetTargets(
  UINT         NumBuffers,          // [In]緩衝區數目
  ID3D11Buffer **ppSOTargets        // [Out]獲取綁定流輸出階段的頂點緩衝區
);

輸出的頂點緩衝區引用數會加1,最好是可以使用ComPtr來承接頂點緩衝區,不然就要在結束的時候手工調用Release方法,若忘記調用則會引起內存泄漏。

ID3D11Device::CreateGeometryShaderWithStreamOutput方法--建立帶流輸出階段的幾何着色器

接下來咱們須要指定數據會流向哪一個輸出槽,首先咱們須要填充結構體D3D11_SO_DECLARATION_ENTRY,結構體聲明以下:

typedef struct D3D11_SO_DECLARATION_ENTRY {
  UINT   Stream;            // 輸出流索引,從0開始
  LPCSTR SemanticName;      // 語義名
  UINT   SemanticIndex;     // 語義索引
  BYTE   StartComponent;    // 從第幾個份量(xyzw)開始,只能取0-3
  BYTE   ComponentCount;    // 份量的輸出數目,只能取1-4
  BYTE   OutputSlot;        // 輸出槽索引,只能取0-3
};

其中,語義名SemanticName用於指定在幾何着色器的流輸出對象對應的結構體中該語義描述的成員,而後用語義索引SemanticIndex指定存在同名語義下用索引值標記的惟一成員。

而後StartComponentComponentCount用於控制該向量須要輸出哪些份量。若StartComponent爲1,ComponentCount爲2,則輸出的份量爲(y, z),而要輸出所有份量,則指定StartCompnent爲0, ComponentCount爲4.

輸出槽索引OutputSlot用於指定選擇綁定流輸出的緩衝區數組中的某一元素。

因爲這裏一個結構體只能指定某個輸出流中的某一貫量,因此一般咱們須要像頂點輸入佈局那樣傳遞一個數組來取出組合成特定頂點。

好比說如今頂點着色器輸入的頂點和流輸出的頂點是一致的:

struct VertexPosColor
{
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT4 color;
    static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};

輸入佈局描述以下:

const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

HLSL中的結構體以下:

struct VertexPosColor
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};

流輸出的入口描述以下:

const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
    { 0, "POSITION", 0, 0, 3, 0 },
    { 0, "COLOR", 0, 0, 4, 0 }
};

這裏對應的是索引爲0的流輸出對象,輸出給綁定在索引爲0的輸出槽的頂點緩衝區,先輸出語義爲POSITION的向量中的xyz份量,而後輸出COLOR整個向量。這樣一個輸出的頂點就和原來的頂點一致了。

接下來給出ID3D11Device::CreateGeometryShaderWithStreamOutput方法的原型:

HRESULT ID3D11Device::CreateGeometryShaderWithStreamOutput(
  const void                       *pShaderBytecode,    // [In]編譯好的着色器字節碼
  SIZE_T                           BytecodeLength,      // [In]字節碼長度
  const D3D11_SO_DECLARATION_ENTRY *pSODeclaration,     // [In]D3D11_SO_DECLARATION_ENTRY的數組
  UINT                             NumEntries,          // [In]入口總數
  const UINT                       *pBufferStrides,     // [In]一個數組包含了每一個綁定到流輸出的緩衝區中頂點字節大小
  UINT                             NumStrides,          // [In]上面數組的元素數目
  UINT                             RasterizedStream,    // [In]按索引指定哪一個流輸出對象用於傳遞到光柵化階段
  ID3D11ClassLinkage               *pClassLinkage,      // [In]忽略
  ID3D11GeometryShader             **ppGeometryShader   // [Out]建立好的幾何着色器
);

若是不須要有流輸出對象提供數據給光柵化階段,則RasterizedStream應當指定爲D3D11_SO_NO_RASTERIZED_STREAM。即使某一流輸出對象傳遞了數據給光柵化階段,它仍能夠提供數據給某一綁定的緩衝區。

下面是一個調用的例子:

const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
    { 0, "POSITION", 0, 0, 3, 0 },
    { 0, "COLOR", 0, 0, 4, 0 }
};

HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
    &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, m_pTriangleSOGS.GetAddressOf()));

當該着色器被綁定到渲染管線上,流輸出階段就會被激活,咱們可使用ID3D11DeviceContext::Draw方法來進行繪製。而後當渲染管線開始執行的時候,任何傳遞給幾何着色器中的流輸出對象的數據,都會基於語義名和語義索引嘗試匹配輸出佈局。一旦發現有匹配的語義,該數據就會流向對應的緩衝區來建立完整的輸出頂點集。

繪製各類酷炫的分形

因爲如今的着色器多到使人髮指,並且有沒有很好的辦法歸類整合,故在下面用一張表列出全部繪製流程用到的着色器hlsl文件名稱:

操做 VS GS PS
經過流輸出獲得分裂的三角形 TriangleSO_VS TriangleSO_GS X
經過流輸出獲得分形雪花 SnowSO_VS SnowSO_GS X
經過流輸出獲得分形球體 SphereSO_VS SphereSO_GS X
繪製分形三角形 Triangle_VS X Triangle_PS
繪製分形雪花 Snow_VS X Snow_PS
繪製分形球體 Sphere_VS X Sphere_PS
繪製法向量 Normal_VS Normal_GS Normal_PS

首先給出Basic.hlsli文件的內容,要注意裏面的常量緩衝區和以前有所變化:

#include "LightHelper.hlsli"

cbuffer CBChangesEveryFrame : register(b0)
{
    matrix g_World;
    matrix g_WorldInvTranspose;
}

cbuffer CBChangesOnResize : register(b1)
{
    matrix g_Proj;
}

cbuffer CBChangesRarely : register(b2)
{
    DirectionalLight g_DirLight[5];
    PointLight g_PointLight[5];
    SpotLight g_SpotLight[5];
    Material g_Material;
    matrix g_View;
    float3 g_SphereCenter;
    float g_SphereRadius;
    float3 g_EyePosW;
    float g_Pad;
}


struct VertexPosColor
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};

struct VertexPosHColor
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};

struct VertexPosHLColor
{
    float4 PosH : SV_POSITION;
    float3 PosL : POSITION;
    float4 Color : COLOR;
};


struct VertexPosNormalColor
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float4 Color : COLOR;
};

struct VertexPosHWNormalColor
{
    float4 PosH : SV_POSITION;
    float3 PosW : POSITION;
    float3 NormalW : NORMAL;
    float4 Color : COLOR;
};

實戰1: 繪製分形三角形

經過流輸出階段,一個三角形就分裂出了三個三角形,頂點的數目翻了3倍。若規定1階分形三角形的頂點數爲3,則N階分形三角形的頂點數爲\(3^{N}\)

HLSL代碼

首先是TriangleSO_VS.hlsl,它負責將頂點直接傳遞給幾何着色器。

// TriangleSO_VS.hlsl
#include "Basic.hlsli"

VertexPosColor VS(VertexPosColor vIn)
{
    return vIn;
}

而後和上一章同樣,TriangleSO_GS.hlsl中的幾何着色器將一個三角形分裂成三個三角形,而且輸出的頂點類型和輸入的頂點是一致的。

// TriangleSO_GS.hlsl
#include "Basic.hlsli"

[maxvertexcount(9)]
void GS(triangle VertexPosColor input[3], inout TriangleStream<VertexPosColor> output)
{
    //
    // 將一個三角形分裂成三個三角形,即沒有v3v4v5的三角形
    //       v1
    //       /\
    //      /  \
    //   v3/____\v4
    //    /\xxxx/\
    //   /  \xx/  \
    //  /____\/____\
    // v0    v5    v2


    VertexPosColor vertexes[6];
    int i;
    [unroll]
    for (i = 0; i < 3; ++i)
    {
        vertexes[i] = input[i];
        vertexes[i + 3].Color = (input[i].Color + input[(i + 1) % 3].Color) / 2.0f;
        vertexes[i + 3].PosL = (input[i].PosL + input[(i + 1) % 3].PosL) / 2.0f;
    }

    [unroll]
    for (i = 0; i < 3; ++i)
    {
        output.Append(vertexes[i]);
        output.Append(vertexes[3 + i]);
        output.Append(vertexes[(i + 2) % 3 + 3]);

        output.RestartStrip();
    }
}

接下來的Triangle_VS.hlslTriangle_PS.hlsl則是常規的三角形繪製:

// Triangle_VS.hlsl
#include "Basic.hlsli"

VertexPosHColor VS(VertexPosColor vIn)
{
    matrix worldViewProj = mul(mul(g_World, g_View), g_Proj);
    VertexPosHColor vOut;
    vOut.Color = vIn.Color;
    vOut.PosH = mul(float4(vIn.PosL, 1.0f), worldViewProj);
    return vOut;
}
// Triangle_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHColor pIn) : SV_Target
{
    return pIn.Color;
}

實戰2: 繪製分形雪花

如今規定第一張圖爲一階分形雪花,第二張爲二階分形雪花。觀察兩者之間的變化,能夠發現前者的每一條直線變成了四條折線。其中每一個尖銳角的度數都在60度,而且每條邊的長度都應該是一致的。

HLSL代碼

和以前同樣,SnowSO_VS.hlsl中的頂點着色器階段只用於頂點直傳:

// SnowSO_VS.hlsl
#include "Basic.hlsli"

VertexPosNormalColor VS(VertexPosNormalColor vIn)
{
    return vIn;
}

而後重點就在於SnowSO_GS.hlsl的幾何着色器了。這裏先放出代碼:

// SnowSO_GS.hlsl
#include "Basic.hlsli"

[maxvertexcount(5)]
void GS(line VertexPosColor input[2], inout LineStream<VertexPosColor> output)
{
    // 要求分形線段按順時針排布
    // z份量必須相等,由於頂點沒有提供法向量沒法判斷垂直上方向
    //                       v1
    //                       /\
    // ____________ =>  ____/  \____
    // i0         i1   i0  v0  v2  i1
    
    VertexPosColor v0, v1, v2;
    v0.Color = lerp(input[0].Color, input[1].Color, 0.25f);
    v1.Color = lerp(input[0].Color, input[1].Color, 0.5f);
    v2.Color = lerp(input[0].Color, input[1].Color, 0.75f);

    v0.PosL = lerp(input[0].PosL, input[1].PosL, 1.0f / 3.0f);
    v2.PosL = lerp(input[0].PosL, input[1].PosL, 2.0f / 3.0f);

    // xy平面求出它的垂直單位向量
    //     
    //     |
    // ____|_____
    float2 upDir = normalize(input[1].PosL - input[0].PosL).yx;
    float len = length(input[1].PosL.xy - input[0].PosL.xy);
    upDir.x = -upDir.x;

    v1.PosL = lerp(input[0].PosL, input[1].PosL, 0.5f);
    v1.PosL.xy += sqrt(3) / 6.0f * len * upDir;

    output.Append(input[0]);
    output.Append(v0);
    output.Append(v1);
    output.Append(v2);
    output.Append(input[1]);

}

能夠發現分形雪花每升一階,須要繪製的頂點數就變成了上一階的4倍。

這裏要求了z份量必須相等,由於使用的着色器仍把一切的頂點仍當作3D頂點來對待出來(固然你也能夠寫成2D的着色器)。

而後開始具體分析從直線變折線的過程,能夠看到由於頂點v1所在角的度數在60度,且v0, v1, v2構成等邊三角形,故v0v2,
v0v1和v1v2的邊長是一致的。並且4條折線要求邊長相等,故這裏的i0v0和v2i1應當各佔線段i0i1的1/3.

其中lerp函數是線性插值函數,數學公式以下:
\[ \mathbf{p} = \mathbf{p}_0 + t(\mathbf{p}_1 - \mathbf{p}_0) \]

其中t的取值範圍在[0.0f, 1.0f],而且操做對象p0和p1能夠是標量,也能夠是矢量,對矢量來講則是對每一個份量都進行線性插值。

當t = 0.5f時,描述的就是p0和p1的中值或中點。

該函數很容易描述兩點之間某一相對位置。

因爲咱們規定了連續線段必須按順時針排布,咱們就能夠利用向量i0i1逆時針旋轉90度獲得對應的突出方向向量,而後標準化,乘上相應的高度值便可獲得頂點v1的位置。

最後就是用於繪製的着色器代碼:

// Snow_VS.hlsl
#include "Basic.hlsli"

VertexPosHColor VS(VertexPosColor vIn)
{
    matrix worldViewProj = mul(mul(g_World, g_View), g_Proj);
    VertexPosHColor vOut;
    vOut.Color = vIn.Color;
    vOut.PosH = mul(float4(vIn.PosL, 1.0f), worldViewProj);
    return vOut;
}
// Snow_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHColor pIn) : SV_Target
{
    return pIn.Color;
}

實戰3: 繪製分形圓球

如下是一階和二階的分形圓球:

仔細觀察能夠看到,原先的一個三角形分裂出了四個三角形,即每升一階,須要繪製的頂點數就變成了上一階的4倍。

HLSL代碼

SphereSO_VS.hlsl代碼和SphereSO_GS.hlsl代碼以下:

// SphereSO_VS.hlsl
#include "Basic.hlsli"

VertexPosNormalColor VS(VertexPosNormalColor vIn)
{
    return vIn;
}
// SphereSO_GS.hlsl

#include "Basic.hlsli"

[maxvertexcount(12)]
void GS(triangle VertexPosNormalColor input[3], inout TriangleStream<VertexPosNormalColor> output)
{
    //
    // 將一個三角形分裂成四個三角形,但同時頂點v3, v4, v5也須要在球面上
    //       v1
    //       /\
    //      /  \
    //   v3/____\v4
    //    /\xxxx/\
    //   /  \xx/  \
    //  /____\/____\
    // v0    v5    v2
    
    VertexPosNormalColor vertexes[6];

    matrix viewProj = mul(g_View, g_Proj);

    [unroll]
    for (int i = 0; i < 3; ++i)
    {
        vertexes[i] = input[i];
        vertexes[i + 3].Color = lerp(input[i].Color, input[(i + 1) % 3].Color, 0.5f);
        vertexes[i + 3].NormalL = normalize(input[i].NormalL + input[(i + 1) % 3].NormalL);
        vertexes[i + 3].PosL = g_SphereCenter + g_SphereRadius * vertexes[i + 3].NormalL;
    }
        
    output.Append(vertexes[0]);
    output.Append(vertexes[3]);
    output.Append(vertexes[5]);
    output.RestartStrip();

    output.Append(vertexes[3]);
    output.Append(vertexes[4]);
    output.Append(vertexes[5]);
    output.RestartStrip();

    output.Append(vertexes[5]);
    output.Append(vertexes[4]);
    output.Append(vertexes[2]);
    output.RestartStrip();

    output.Append(vertexes[3]);
    output.Append(vertexes[1]);
    output.Append(vertexes[4]);
}

因爲v3, v4, v5也須要在球面上,咱們還須要額外知道球的半徑和球心位置。雖說經過三角形三個頂點位置和法向量能夠算出圓心和半徑,但直接從常量緩衝區提供這兩個信息會更方便一些。

要計算諸如v3頂點所在位置,咱們能夠先求出它的法向量,將v0和v1的法向量相加取其單位向量即爲v3的法向量,而後從圓心開始加上半徑長度的法向量便可獲得頂點v3的位置。

剩下繪製圓的着色器代碼以下:

// Sphere_VS.hlsl
#include "Basic.hlsli"

VertexPosHWNormalColor VS(VertexPosNormalColor vIn)
{
    VertexPosHWNormalColor vOut;
    matrix viewProj = mul(g_View, g_Proj);
    float4 posW = mul(float4(vIn.PosL, 1.0f), g_World);

    vOut.PosH = mul(posW, viewProj);
    vOut.PosW = posW.xyz;
    vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
    vOut.Color = vIn.Color;
    return vOut;
}
// Sphere_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHWNormalColor pIn) : SV_Target
{
    // 標準化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 頂點指向眼睛的向量
    float3 toEyeW = normalize(g_EyePosW - pIn.PosW);

    // 初始化爲0 
    float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
    float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);

    // 只計算方向光
    ComputeDirectionalLight(g_Material, g_DirLight[0], pIn.NormalW, toEyeW, ambient, diffuse, spec);

    return pIn.Color * (ambient + diffuse) + spec;
}

C++代碼的變化

BasicEffect::InitAll方法的變化

如今着色器的建立按繪製類別進行分組:

bool BasicEffect::InitAll(ID3D11Device * device)
{
    if (!device)
        return false;

    if (!pImpl->m_pCBuffers.empty())
        return true;

    if (!RenderStates::IsInit())
        throw std::exception("RenderStates need to be initialized first!");

    const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
        { 0, "POSITION", 0, 0, 3, 0 },
        { 0, "COLOR", 0, 0, 4, 0 }
    };

    const D3D11_SO_DECLARATION_ENTRY posNormalColorLayout[3] = {
        { 0, "POSITION", 0, 0, 3, 0 },
        { 0, "NORMAL", 0, 0, 3, 0 },
        { 0, "COLOR", 0, 0, 4, 0 }
    };

    UINT stridePosColor = sizeof(VertexPosColor);
    UINT stridePosNormalColor = sizeof(VertexPosNormalColor);

    ComPtr<ID3DBlob> blob;

    // ******************
    // 流輸出分裂三角形
    //
    HR(CreateShaderFromFile(L"HLSL\\TriangleSO_VS.cso", L"HLSL\\TriangleSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTriangleSOVS.GetAddressOf()));
    // 建立頂點輸入佈局
    HR(device->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout), blob->GetBufferPointer(),
        blob->GetBufferSize(), pImpl->m_pVertexPosColorLayout.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\TriangleSO_GS.cso", L"HLSL\\TriangleSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
        &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pTriangleSOGS.GetAddressOf()));

    // ******************
    // 繪製分形三角形
    //
    HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTriangleVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Triangle_PS.cso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTrianglePS.GetAddressOf()));


    // ******************
    // 流輸出分形球體
    //
    HR(CreateShaderFromFile(L"HLSL\\SphereSO_VS.cso", L"HLSL\\SphereSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSphereSOVS.GetAddressOf()));
    // 建立頂點輸入佈局
    HR(device->CreateInputLayout(VertexPosNormalColor::inputLayout, ARRAYSIZE(VertexPosNormalColor::inputLayout), blob->GetBufferPointer(),
        blob->GetBufferSize(), pImpl->m_pVertexPosNormalColorLayout.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\SphereSO_GS.cso", L"HLSL\\SphereSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posNormalColorLayout, ARRAYSIZE(posNormalColorLayout),
        &stridePosNormalColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pSphereSOGS.GetAddressOf()));

    // ******************
    // 繪製球體
    //
    HR(CreateShaderFromFile(L"HLSL\\Sphere_VS.cso", L"HLSL\\Sphere_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSphereVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Sphere_PS.cso", L"HLSL\\Sphere_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSpherePS.GetAddressOf()));


    // ******************
    // 流輸出分形雪花
    //
    HR(CreateShaderFromFile(L"HLSL\\SnowSO_VS.cso", L"HLSL\\SnowSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowSOVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\SnowSO_GS.cso", L"HLSL\\SnowSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
        &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pSnowSOGS.GetAddressOf()));

    // ******************
    // 繪製雪花
    //
    HR(CreateShaderFromFile(L"HLSL\\Snow_VS.cso", L"HLSL\\Snow_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Snow_PS.cso", L"HLSL\\Snow_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowPS.GetAddressOf()));


    // ******************
    // 繪製法向量
    //
    HR(CreateShaderFromFile(L"HLSL\\Normal_VS.cso", L"HLSL\\Normal_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Normal_GS.cso", L"HLSL\\Normal_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalGS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Normal_PS.cso", L"HLSL\\Normal_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalPS.GetAddressOf()));



    pImpl->m_pCBuffers.assign({
        &pImpl->m_CBFrame, 
        &pImpl->m_CBOnResize, 
        &pImpl->m_CBRarely});

    // 建立常量緩衝區
    for (auto& pBuffer : pImpl->m_pCBuffers)
    {
        HR(pBuffer->CreateBuffer(device));
    }

    return true;
}

BasicEffect::SetRenderSplitedTriangle方法--繪製分形三角形

因爲新增了流輸出的階段,這裏開始接下來的每個用於繪製的方法都須要把流輸出綁定的頂點緩衝區都解除綁定。

void BasicEffect::SetRenderSplitedTriangle(ID3D11DeviceContext * deviceContext)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pTriangleVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pTrianglePS.Get(), nullptr, 0);
}

BasicEffect::SetRenderSplitedSnow方法--繪製分形雪花

void BasicEffect::SetRenderSplitedSnow(ID3D11DeviceContext * deviceContext)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pSnowVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pSnowPS.Get(), nullptr, 0);
}

BasicEffect::SetRenderSplitedSphere方法--繪製分形球體

void BasicEffect::SetRenderSplitedSphere(ID3D11DeviceContext * deviceContext)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pSphereVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pSpherePS.Get(), nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedTriangle方法--通過流輸出保存下一階分形三角形的頂點

爲了簡化設置,這裏還須要提供額外的輸入緩衝區和輸出緩衝區。爲了防止出現頂點緩衝區同時被綁定到輸入裝配和流輸出階段的狀況,須要先清空流輸出綁定的頂點緩衝區,而後將用於輸入的頂點緩衝區綁定到輸入裝配階段,最後纔是把輸出的頂點緩衝區綁定到流輸出階段。

void BasicEffect::SetStreamOutputSplitedTriangle(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetInputLayout(nullptr);
    deviceContext->SOSetTargets(0, nullptr, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());

    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pTriangleSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pTriangleSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedSnow方法--通過流輸出保存下一階分形雪花的頂點

注意這裏是用LineList而不是LineStrip方式。

void BasicEffect::SetStreamOutputSplitedSnow(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pSnowSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pSnowSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedSphere方法--通過流輸出保存下一階分形球體的頂點

void BasicEffect::SetStreamOutputSplitedSphere(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢復流輸出默認設置,防止頂點緩衝區同時綁定在輸入和輸出階段
    UINT stride = sizeof(VertexPosNormalColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get());

    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pSphereSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pSphereSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}

GameApp類的變化

ID3D11DeviceContext::DrawAuto方法--繪製未知數目的圖元

時隔多月,是時候請回該方法了。

這是一個惟一不須要形參就能繪製的API,它能夠根據輸入裝配階段綁定的緩衝區(內部存有圖元數目的記錄)自動進行繪製,它會通過頂點着色階段並一直到流輸出階段。它可能會繼續通過光柵化階段到輸出合併階段,也能夠不通過。

可是它的調用要求以下:

  1. 在輸入裝配階段下,輸入槽0須要綁定頂點緩衝區
  2. 綁定到輸入槽0的頂點緩衝區須要設置綁定標籤D3D11_BIND_VERTEX_BUFFERD3D11_BIND_STREAM_OUTPUT
  3. 做爲流輸出的緩衝區也須要設置上述一樣的兩個綁定標籤
  4. 綁定到輸入槽0的頂點緩衝區,須要存在圖元數目的內部記錄

關於最後一點,通常的頂點緩衝區是不會存在內部記錄的。一般要求第一次流輸出繪製時使用DrawDrawIndexed系列的方法,而不是DrawAuto來繪製,這樣流輸出緩衝區在產生運行結果的同時,其內部還會產生圖元數目的記錄。這樣在後續的調用中,你就能夠將該流輸出緩衝區做爲輸入,而後提供另外一個流輸出緩衝區做爲輸出,最後調用DrawAuto來正確繪製了。

GameApp::ResetSplitedTriangle方法--從新創建包含1-7階的分形三角形頂點的緩衝區

首先咱們只須要給1階的頂點緩衝區使用指定三角形的三個頂點,而後後續階數的頂點緩衝區就根據上一階產出的頂點緩衝區進行"繪製"。通過6次Draw方法的調用後,裏面的7個頂點緩衝區都應該被初始化完畢,後續繪製的時候只須要直接綁定某一個頂點緩衝區到輸入便可。

注意頂點緩衝區在建立的時候必定要加上D3D11_BIND_STREAM_OUTPUT標籤。

void GameApp::ResetSplitedTriangle()
{
    // ******************
    // 初始化三角形
    //

    // 設置三角形頂點
    VertexPosColor vertices[] =
    {
        { XMFLOAT3(-1.0f * 3, -0.866f * 3, 0.0f), XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) },
        { XMFLOAT3(0.0f * 3, 0.866f * 3, 0.0f), XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) },
        { XMFLOAT3(1.0f * 3, -0.866f * 3, 0.0f), XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) }
    };
    // 設置頂點緩衝區描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;    // 這裏須要容許流輸出階段經過GPU寫入
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 須要額外添加流輸出標籤
    vbd.CPUAccessFlags = 0;
    // 新建頂點緩衝區
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

    // 三角形頂點數
    m_InitVertexCounts = 3;
    // 初始化全部頂點緩衝區
    for (int i = 1; i < 7; ++i)
    {
        vbd.ByteWidth *= 3;
        HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
        m_BasicEffect.SetStreamOutputSplitedTriangle(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
        // 第一次繪製須要調用通常繪製指令,以後就可使用DrawAuto了
        if (i == 1)
        {
            m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
        }
        else
        {
            m_pd3dImmediateContext->DrawAuto();
        }

    }
}

GameApp::ResetSplitedSnow方法--從新創建包含1-7階的分形雪花頂點的緩衝區

因爲繪製方式統一用LineList,初始階段應當提供3條線段的6個頂點,雖說每一個頂點都被重複使用了2次。

void GameApp::ResetSplitedSnow()
{
    // ******************
    // 雪花分形從初始化三角形開始,須要6個頂點
    //

    // 設置三角形頂點
    float sqrt3 = sqrt(3.0f);
    VertexPosColor vertices[] =
    {
        { XMFLOAT3(-3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, sqrt3 / 2, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, sqrt3 / 2, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(-3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) }
    };
    // 將三角形寬度和高度都放大3倍
    for (VertexPosColor& v : vertices)
    {
        v.pos.x *= 3;
        v.pos.y *= 3;
    }

    // 設置頂點緩衝區描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;    // 這裏須要容許流輸出階段經過GPU寫入
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 須要額外添加流輸出標籤
    vbd.CPUAccessFlags = 0;
    // 新建頂點緩衝區
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

    // 頂點數
    m_InitVertexCounts = 6;
    // 初始化全部頂點緩衝區
    for (int i = 1; i < 7; ++i)
    {
        vbd.ByteWidth *= 4;
        HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
        m_BasicEffect.SetStreamOutputSplitedSnow(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
        // 第一次繪製須要調用通常繪製指令,以後就可使用DrawAuto了
        if (i == 1)
        {
            m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
        }
        else
        {
            m_pd3dImmediateContext->DrawAuto();
        }
    }
}

GameApp::ResetSplitedSphere方法--從新創建包含1-7階的分形圓球頂點的緩衝區

這裏不使用Geometry類來構造一階圓球,而是僅提供與外接正方體相交的六個頂點,包含八個三角形對應的24個頂點。

void GameApp::ResetSplitedSphere()
{
    // ******************
    // 分形球體
    //

    VertexPosNormalColor basePoint[] = {
        { XMFLOAT3(0.0f, 2.0f, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(2.0f, 0.0f, 0.0f), XMFLOAT3(1.0f, 0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, 0.0f, 2.0f), XMFLOAT3(0.0f, 0.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(-2.0f, 0.0f, 0.0f), XMFLOAT3(-1.0f, 0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, 0.0f, -2.0f), XMFLOAT3(0.0f, 0.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
        { XMFLOAT3(0.0f, -2.0f, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    };
    int indices[] = { 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 1, 4, 1, 2, 5, 2, 3, 5, 3, 4, 5, 4, 1, 5 };

    std::vector<VertexPosNormalColor> vertices;
    for (int pos : indices)
    {
        vertices.push_back(basePoint[pos]);
    }


    // 設置頂點緩衝區描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;    // 這裏須要容許流輸出階段經過GPU寫入
    vbd.ByteWidth = (UINT)(vertices.size() * sizeof(VertexPosNormalColor));
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 須要額外添加流輸出標籤
    vbd.CPUAccessFlags = 0;
    // 新建頂點緩衝區
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices.data();
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

//#if defined(DEBUG) | defined(_DEBUG)
//  ComPtr<IDXGraphicsAnalysis> graphicsAnalysis;
//  HR(DXGIGetDebugInterface1(0, __uuidof(graphicsAnalysis.Get()), reinterpret_cast<void**>(graphicsAnalysis.GetAddressOf())));
//  graphicsAnalysis->BeginCapture();
//#endif

    // 頂點數
    m_InitVertexCounts = 24;
    // 初始化全部頂點緩衝區
    for (int i = 1; i < 7; ++i)
    {
        vbd.ByteWidth *= 4;
        HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
        m_BasicEffect.SetStreamOutputSplitedSphere(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
        // 第一次繪製須要調用通常繪製指令,以後就可使用DrawAuto了
        if (i == 1)
        {
            m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
        }
        else
        {
            m_pd3dImmediateContext->DrawAuto();
        }
    }
}

GameApp::DrawScene方法的變化

同理,在進行正常繪製的時候,因爲索引1到6的頂點緩衝區內部都記錄了圖元數目,也能夠直接用DrawAuto方法繪製到屏幕上。

void GameApp::DrawScene()
{
    assert(m_pd3dImmediateContext);
    assert(m_pSwapChain);


    m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
    m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);


    // 根據當前繪製模式設置須要用於渲染的各項資源
    if (m_ShowMode == Mode::SplitedTriangle)
    {
        m_BasicEffect.SetRenderSplitedTriangle(m_pd3dImmediateContext.Get());
    }
    else if (m_ShowMode == Mode::SplitedSnow)
    {
        m_BasicEffect.SetRenderSplitedSnow(m_pd3dImmediateContext.Get());
    }
    else if (m_ShowMode == Mode::SplitedSphere)
    {
        m_BasicEffect.SetRenderSplitedSphere(m_pd3dImmediateContext.Get());
    }

    // 設置線框/面模式
    if (m_IsWireFrame)
    {
        m_pd3dImmediateContext->RSSetState(RenderStates::RSWireframe.Get());
    }
    else
    {
        m_pd3dImmediateContext->RSSetState(nullptr);
    }

    // 應用常量緩衝區的變動
    m_BasicEffect.Apply(m_pd3dImmediateContext.Get());
    // 除了索引爲0的緩衝區缺乏內部圖元數目記錄,其他均可以使用DrawAuto方法
    if (m_CurrIndex == 0)
    {
        m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
    }
    else
    {
        m_pd3dImmediateContext->DrawAuto();
    }
        
    // 繪製法向量
    if (m_ShowNormal)
    {
        m_BasicEffect.SetRenderNormal(m_pd3dImmediateContext.Get());
        m_BasicEffect.Apply(m_pd3dImmediateContext.Get());
        // 除了索引爲0的緩衝區缺乏內部圖元數目記錄,其他均可以使用DrawAuto方法
        if (m_CurrIndex == 0)
        {
            m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
        }
        else
        {
            m_pd3dImmediateContext->DrawAuto();
        }
    }


    // ******************
    // 繪製Direct2D部分
    //
    if (m_pd2dRenderTarget != nullptr)
    {
        m_pd2dRenderTarget->BeginDraw();
        std::wstring text = L"切換分形:Q-三角形(面/線框) W-雪花(線框) E-球(面/線框)\n"
            L"主鍵盤數字1 - 7:分形階數,越高越精細\n"
            L"M-面/線框切換\n\n"
            L"當前階數: " + std::to_wstring(m_CurrIndex + 1) + L"\n"
            "當前分形: ";
        if (m_ShowMode == Mode::SplitedTriangle)
            text += L"三角形";
        else if (m_ShowMode == Mode::SplitedSnow)
            text += L"雪花";
        else
            text += L"球";

        if (m_IsWireFrame)
            text += L"(線框)";
        else
            text += L"(面)";

        if (m_ShowMode == Mode::SplitedSphere)
        {
            if (m_ShowNormal)
                text += L"(N-關閉法向量)";
            else
                text += L"(N-開啓法向量)";
        }



        m_pd2dRenderTarget->DrawTextW(text.c_str(), (UINT32)text.length(), m_pTextFormat.Get(),
            D2D1_RECT_F{ 0.0f, 0.0f, 600.0f, 200.0f }, m_pColorBrush.Get());
        HR(m_pd2dRenderTarget->EndDraw());
    }

    HR(m_pSwapChain->Present(0, 0));

}

如今來看一下動圖感覺一些這些酷炫的效果吧:

分形三角形繪製效果

分形雪花繪製效果

分形圓球繪製效果

因爲文件大小限制,這裏分紅兩個部分:

下面是帶法向量的:

遺留問題

該項目使用圖形調試器並退出的時候,會引起內存泄漏,而具體的泄漏對象估計是ID3D11Query,然而我也沒有辦法直接拿到該接口對象來釋放。

DirectX11 With Windows SDK完整目錄

Github項目源碼

歡迎加入QQ羣: 727623616 能夠一塊兒探討DX11,以及有什麼問題也能夠在這裏彙報。

相關文章
相關標籤/搜索