前言
在上一章,我們知道了如何使用幾何着色器來重新組裝圖元,比如從一個三角形分裂成三個三角形。但是為了實現更高階的分形,我們必須要從幾何着色器拿到輸出的頂點。這里我們可以使用可選的流輸出階段來拿到頂點集合。
注意: 本章末尾有大量的GIF動圖!
在此之前需要額外了解的章節如下:
章節回顧 |
---|
15 幾何着色器初探 |
Visual Studio圖形調試器詳細使用教程(編程捕獲部分) |
DirectX11 With Windows SDK完整目錄
歡迎加入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
指定存在同名語義下用索引值標記的唯一成員。
然后StartComponent
和ComponentCount
用於控制該向量需要輸出哪些分量。若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.hlsl
和Triangle_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
函數是線性插值函數,數學公式如下:
其中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,它可以根據輸入裝配階段綁定的緩沖區(內部存有圖元數目的記錄)自動進行繪制,它會經過頂點着色階段並一直到流輸出階段。它可能會繼續經過光柵化階段到輸出合並階段,也可以不經過。
但是它的調用要求如下:
- 在輸入裝配階段下,輸入槽0需要綁定頂點緩沖區
- 綁定到輸入槽0的頂點緩沖區需要設置綁定標簽
D3D11_BIND_VERTEX_BUFFER
和D3D11_BIND_STREAM_OUTPUT
- 作為流輸出的緩沖區也需要設置上述同樣的兩個綁定標簽
- 綁定到輸入槽0的頂點緩沖區,需要存在圖元數目的內部記錄
關於最后一點,一般的頂點緩沖區是不會存在內部記錄的。通常要求第一次流輸出繪制時使用Draw
、DrawIndexed
系列的方法,而不是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完整目錄
歡迎加入QQ群: 727623616 可以一起探討DX11,以及有什么問題也可以在這里匯報。