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

文章目錄

前言

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

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

在此之前需要額外瞭解的章節如下:

章節回顧
15 幾何着色器初探

DirectX11 With Windows SDK完整目錄

Github項目源碼

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

流輸出階段

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

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

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

流輸出狀態的配置

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

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

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

每個要綁定到流輸出階段的緩衝區資源必須要在創建的時候額外設置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階分形三角形的頂點數爲3N3^{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函數是線性插值函數,數學公式如下:
p=p0+t(p1p0) \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,以及有什麼問題也可以在這裏彙報。

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