DirectX11 With Windows SDK--15 幾何著色器初探
阿新 • • 發佈:2018-08-09
lsh b- win 方向 osc ++i eas get() 但是 # 前言
從這一部分開始,感覺就像是踏入了無人深空一樣,在之前初學DX11的時候,這部分內容都是基本上跳過的,現在打算重新認真地把它給拾回來。
[DirectX11 With Windows SDK完整目錄](http://www.cnblogs.com/X-Jun/p/9028764.html)
[Github項目源碼](https://github.com/MKXJun/DirectX11-With-Windows-SDK)
# 幾何著色器
首先用一張圖來回顧一下渲染管線的各個階段,目前為止我們接觸的著色器有頂點著色器和像素著色器,而渲染管線階段有:輸入裝配階段、頂點著色階段、光柵化階段、像素著色階段、輸出合並階段。
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180805203259072-78808112.png)
可以看到,幾何著色器是我們在將頂點送入光柵化階段之前,可以操作頂點的最後一個階段。它同樣也允許我們編寫自己的著色器代碼。幾何著色器可以做如下事情:
1. 讓程序自動決定如何在渲染管線中插入/移除幾何體;
2. 通過流輸出階段將頂點信息再次傳遞到頂點緩沖區;
3. 改變圖元類型(如輸入點圖元,輸出三角形圖元);
但它也有缺點,幾何著色器輸出的頂點數據很可能是有較多重復的,從流輸出拿回到頂點緩沖區的話會占用較多的內存空間。它本身無法輸出索引數組。
幾何著色階段會收到一系列代表輸入幾何體類型的頂點,然後我們可以自由選擇其中的這些頂點信息,然後交給流輸出對象重新解釋成新的原始類型(或者不變),傳遞給流輸出階段或者是光柵化階段。而幾何著色器僅能夠接受來自輸入裝配階段提供的頂點信息,對每個頂點進行處理,無法自行決定增減頂點。
註意:離開幾何著色器的頂點如果要傳遞給光柵化階段,需要包含有轉換到齊次裁剪坐標系的坐標信息(語義為`SV_POSITION`的`float4`向量)
# 可編程的幾何著色器
## 從一個看似沒什麽用的幾何著色器代碼入手
若我們直接從VS項目新建一個幾何著色器文件,則可以看到下面的代碼:
```cpp
struct GSOutput
{
float4 pos : SV_POSITION;
};
[maxvertexcount(3)]
void main(
triangle float4 input[3] : SV_POSITION,
inout TriangleStream< GSOutput > output
)
{
for (uint i = 0; i < 3; i++)
{
GSOutput element;
element.pos = input[i];
output.Append(element);
}
}
```
ps. 可能有些人會對void main的寫法表示不爽,比如說我。不過這不是C語言的主函數......
若在輸入裝配階段指定使用TriangleList圖元的話,初步觀察該代碼,實際上你可以發現其實該著色器只是把輸入的頂點按原樣輸出給流輸出對象,即跟什麽都沒做(鹹魚)有什麽區別。。不過從這份代碼裏面就已經包含了幾何著色器所特有的絕大部分語法了。
首先,幾何著色器是根據圖元類型來進行調用的,若使用的是TriangleList,則每一個三角形的三個頂點都會作為輸入,觸發幾何著色器的調用。這樣一個TriangleList解釋的30個頂點會觸發10次調用。
對於幾何著色器,我們必須要指定它每次調用所允許輸出的最大頂點數目。我們可以使用屬性語法來強行修改著色器行為:
`[maxvertexcount(N)]`
這裏`N`就是每次調用允許產出的最大頂點數目,然後最終輸出的頂點數目不會超過`N`的值。`maxvertexcount`的值應當盡可能的小。
關於性能上的表現,我根據龍書提供的引用找到了對應的說明文檔:
[NVIDIA08](http://developer.download.nvidia.com/GPU_Programming_Guide/GPU_Programming_Guide_G80.pdf)
雖然是10年前的文檔,這裏說到:在GeForce 8800 GTX,一個幾何著色器的調用在輸出1到20個標量的時候可以達到最大運行性能表現,但是當我們指定最大允許輸出標量的數目在27到40個時,性能僅達到峰值的50%。比如說,如果頂點的聲明如下:
```cpp
struct V0
{
float3 pos : POSITION;
float2 tex : TEXCOORD;
};
```
這裏每個頂點就已經包含了5個標量了,如果以它作為輸出類型,則`maxvertexcount`為4的時候就可以達到理論上的峰值性能(20個標量)。
但如果頂點類型中還包含有`float3`類型的法向量,每個頂點就額外包含了3個標量,這樣在`maxvertexcount`為4的時候就輸出了32個標量,只有50%的峰值性能表現。
這份文檔已經將近10年了,對於那時候的顯卡來說使用幾何著色器可能不是一個很好的選擇,不過當初的顯卡也早已不能和現在的顯卡相提並論了。
> 註意:
> 1. `maxvertexcount`的值應當設置到盡可能小的值,因為它將直接決定幾何著色器的運行效率。
> 2. 幾何著色器的每次調用最多只能處理1024個標量,對於只包含4D位置向量的頂點來說也只能處理256個頂點。
在HLSL編譯器裏,如果設置的`maxvertexcount`過大,會直接收到編譯錯誤:
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180806192145098-1956370002.png)
然後代碼中的`triangle`是用於指定輸入的圖元類型,具體支持的關鍵字如下:
|圖元類型|描述|
|--------|----|
|point |Point list|
|line |Line list or line strip|
|triangle|Triangle list or triangle strip|
|lineadj |Line list with adjacency or line strip with adjacency|
|triangleadj|Triangle list with adjacency or triangle strip with adjacency|
具體的圖元類型可以到第2章回顧:[點擊此處](https://www.cnblogs.com/X-Jun/p/9031959.html)
而參數類型可以是用戶自定義的結構體類型,或者是向量(float4)類型。從頂點著色器傳過來的頂點至少會包含一個表示齊次裁剪坐標的向量。
參數名`inupt`實際上用戶是可以任意指定的。
對於該輸入參數的元素數目,取決於前面聲明的圖元類型:
|圖元類型|元素數目|
|--------|--------|
|point |[1] 每次只能處理1個頂點|
|line |[2] 一個線段必須包含2個頂點|
|triangle|[3] 一個三角形需要3個頂點|
|lineadj |[4] 一個鄰接線段需要4個頂點|
|triangleadj|[6] 一個鄰接三角形需要6個頂點|
而第二個參數必須是一個流輸出對象,而且需要被指定為`inout`可讀寫類型。可以看到,它是一個類模板,模板的形參指定要輸出的類型。流輸出對象有如下三種:
|流輸出對象類型|描述|
|--------------|----|
|PointStream |一系列點的圖元|
|LineStream |一系列線段的圖元|
|TriangleStream|一系列三角形的圖元|
流輸出對象都具有下面兩種方法:
|方法|描述|
|----|----|
|Append|向指定的流輸出對象添加一個輸出的數據|
|RestartStrip|在以線段或者三角形作為圖元的時候,默認是以strip的形式輸出的, 如果我們不希望下一個輸出的頂點與之前的頂點構成新圖元,則需要調用此方法來重新開始新的strip。若希望輸出的圖元類型也保持和原來一樣的TriangleList,則需要每調用3次Append方法後就調用一次RestartStrip。|
>註意:
> 1. 所謂的刪除頂點,實際上就是不將該頂點傳遞給流輸出對象
> 2. 若傳入的頂點中多余的部分無法構成對應的圖元,則拋棄掉這些多余的頂點
在開始前,先放出`Basic.fx`文件的內容:
```
#include "LightHelper.hlsli"
cbuffer CBChangesEveryFrame : register(b0)
{
row_major matrix gWorld;
row_major matrix gWorldInvTranspose;
}
cbuffer CBChangesOnResize : register(b1)
{
row_major matrix gProj;
}
cbuffer CBNeverChange : register(b2)
{
DirectionalLight gDirLight;
Material gMaterial;
row_major matrix gView;
float3 gEyePosW;
float gCylinderHeight;
}
struct VertexPosColor
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexPosHColor
{
float4 PosH : SV_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: 將一個三角形分割成三個三角形
現在我們的目標是把一個三角形分裂成三個三角形:
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180808204508906-886184113.jpg)
這也為以後實現分形做為基礎。建議讀者可以先自行嘗試編寫著色器代碼再來對比。在編寫好著色器代碼後,
要給渲染管線綁定好一切所需的資源才能夠看到效果。
### HLSL代碼
`Triangle_VS.hlsl`, `Triangle_GS.hlsl`和`Triangle_PS.hlsl`的實現如下:
```cpp
// Triangle_VS.hlsl
#include "Basic.fx"
VertexPosHColor VS(VertexPosColor pIn)
{
row_major matrix worldViewProj = mul(mul(gWorld, gView), gProj);
VertexPosHColor pOut;
pOut.Color = pIn.Color;
pOut.PosH = mul(float4(pIn.PosL, 1.0f), worldViewProj);
return pOut;
}
```
```cpp
Triangle_GS.hlsl
#include "Basic.fx"
[maxvertexcount(9)]
void GS(triangle VertexPosHColor input[3], inout TriangleStream output)
{
//
// 將一個三角形分裂成三個三角形,即沒有v3v4v5的三角形
// v1
// / // / // v3/____\v4
// /\xxxx/ // / \xx/ // /____\/____ // v0 v5 v2
VertexPosHColor 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].PosH = (input[i].PosH + input[(i + 1) % 3].PosH) / 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();
}
}
```
```cpp
// Triangle_PS.hlsl
#include "Basic.fx"
float4 PS(VertexPosHColor pIn) : SV_Target
{
return pIn.Color;
}
```
這裏輸入和輸出的圖元類型都是一致的,但無論什麽情況都一定要註意設置好`maxvertexcount`的值,這裏固定一個三角形的三個頂點輸出9個頂點(構成三個三角形),並且每3次`Append`就需要調用1次`RestartStrip`。
## 實戰2: 通過圓線構造圓柱體側面
已知圖元類型為LineStrip,現在有一系列連續的頂點構成圓線(近似圓弧的連續折線),構造出圓柱體的側面。即輸入圖元類型為線段,輸出一個矩形(兩個三角形)。
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180808205822537-1877161667.png)
思路: 光有頂點位置還不足以構造出圓柱體側面,因為無法確定圓柱往哪個方向延伸。所以我們還需要對每個頂點引入所在圓柱側面的法向量,通過叉乘就可以確定上方向/下方向並進行延伸了。
### HLSL代碼
`Cylinder_VS.hlsl`, `Cylinder_GS.hlsl`和`Cylinder_PS.hlsl`的實現如下:
```cpp
// Cylinder_VS.hlsl
#include "Basic.fx"
VertexPosHWNormalColor VS(VertexPosNormalColor pIn)
{
VertexPosHWNormalColor pOut;
row_major matrix viewProj = mul(gView, gProj);
pOut.PosW = mul(float4(pIn.PosL, 1.0f), gWorld).xyz;
pOut.PosH = mul(float4(pOut.PosW, 1.0f), viewProj);
pOut.NormalW = mul(pIn.NormalL, (float3x3)gWorldInvTranspose);
pOut.Color = pIn.Color;
return pOut;
}
```
```cpp
// Cylinder_GS.hlsl
#include "Basic.fx"
// 一個v0v1線段輸出6個三角形頂點
[maxvertexcount(6)]
void GS(line VertexPosHWNormalColor input[2], inout TriangleStream output)
{
// *****************************
// 要求圓線框是順時針的,然後自底向上構造圓柱側面
// --> v2____v3
// ______ |\ |
// / \ | \ |
// \______/ | \ |
// <-- |___\|
// v1(i1) v0(i0)
float3 upDir = normalize(cross(input[0].NormalW, (input[1].PosW - input[0].PosW)));
VertexPosHWNormalColor v2, v3;
matrix viewProj = mul(gView, gProj);
v2.PosW = input[1].PosW + upDir * gCylinderHeight;
v2.PosH = mul(float4(v2.PosW, 1.0f), viewProj);
v2.NormalW = input[1].NormalW;
v2.Color = input[1].Color;
v3.PosW = input[0].PosW + upDir * gCylinderHeight;
v3.PosH = mul(float4(v3.PosW, 1.0f), viewProj);
v3.NormalW = input[0].NormalW;
v3.Color = input[0].Color;
output.Append(input[0]);
output.Append(input[1]);
output.Append(v2);
output.RestartStrip();
output.Append(v2);
output.Append(v3);
output.Append(input[0]);
}
```
```cpp
// Cylinder_PS.hlsl
#include "Basic.fx"
float4 PS(VertexPosHWNormalColor pIn) : SV_Target
{
// 標準化法向量
pIn.NormalW = normalize(pIn.NormalW);
// 頂點指向眼睛的向量
float3 toEyeW = normalize(gEyePosW - 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(gMaterial, gDirLight, pIn.NormalW, toEyeW, ambient, diffuse, spec);
return pIn.Color * (ambient + diffuse) + spec;
}
```
## 實戰3: 畫出頂點的法向量
畫出頂點的法向量可以幫助你進行調試,排查法向量是否出現了問題。這時候圖元的類型為PointList,需要通過幾何著色器輸出一個線段(兩個頂點)。由於頂點中包含法向量,剩下的就是要自行決定法向量的長度。
下圖的法向量長度為0.5
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180808210947538-382005993.png)
### HLSL代碼
`Normal_VS.hlsl`, `Normal_GS.hlsl`和`Normal_PS.hlsl`的實現如下:
```cpp
// Normal_VS.hlsl
#include "Basic.fx"
VertexPosHWNormalColor VS(VertexPosNormalColor pIn)
{
VertexPosHWNormalColor pOut;
row_major matrix viewProj = mul(gView, gProj);
pOut.PosW = mul(float4(pIn.PosL, 1.0f), gWorld).xyz;
pOut.PosH = mul(float4(pOut.PosW, 1.0f), viewProj);
pOut.NormalW = mul(pIn.NormalL, (float3x3) gWorldInvTranspose);
pOut.Color = pIn.Color;
return pOut;
}
```
```cpp
// Normal_GS.hlsl
#include "Basic.fx"
[maxvertexcount(2)]
void GS(point VertexPosHWNormalColor input[1], inout LineStream output)
{
matrix viewProj = mul(gView, gProj);
VertexPosHWNormalColor v;
// 防止深度值資源爭奪
v.PosW = input[0].PosW + input[0].NormalW * 0.01f;
v.NormalW = input[0].NormalW;
v.PosH = mul(float4(v.PosW, 1.0f), viewProj);
v.Color = input[0].Color;
output.Append(v);
v.PosW = v.PosW + input[0].NormalW * 0.5f;
v.PosH = mul(float4(v.PosW, 1.0f), viewProj);
output.Append(v);
}
```
```cpp
// Normal_PS.hlsl
#include "Basic.fx"
float4 PS(VertexPosHWNormalColor pIn) : SV_TARGET
{
return pIn.Color;
}
```
# C++代碼的部分變化
## BasicFX.h的變化
常量緩沖區和`BasicFX`類的變化如下:
```cpp
#ifndef BASICFX_H
#define BASICFX_H
#include
#include
#include
#include
#include
#include "LightHelper.h"
#include "RenderStates.h"
#include "Vertex.h"
// 由於常量緩沖區的創建需要是16字節的倍數,該函數可以返回合適的字節大小
inline UINT Align16Bytes(UINT size)
{
return (size + 15) & (UINT)(-16);
}
struct CBChangesEveryFrame
{
DirectX::XMMATRIX world;
DirectX::XMMATRIX worldInvTranspose;
};
struct CBChangesOnResize
{
DirectX::XMMATRIX proj;
};
struct CBNeverChange
{
DirectionalLight dirLight;
Material material;
DirectX::XMMATRIX view;
DirectX::XMFLOAT3 eyePos;
float cylinderHeight;
};
class BasicFX
{
public:
// 使用模板別名(C++11)簡化類型名
template
using ComPtr = Microsoft::WRL::ComPtr;
// 初始化Basix.fx所需資源並初始化光柵化狀態
bool InitAll(ComPtr device);
// 是否已經初始化
bool IsInit() const;
template
void UpdateConstantBuffer(const T& cbuffer);
// 繪制三角形分裂
void SetRenderSplitedTriangle();
// 繪制無上下蓋的圓柱體
void SetRenderCylinderNoCap();
// 繪制所有頂點的法向量
void SetRenderNormal();
private:
// objFileNameInOut為編譯好的著色器二進制文件(.*so),若有指定則優先尋找該文件並讀取
// hlslFileName為著色器代碼,若未找到著色器二進制文件則編譯著色器代碼
// 編譯成功後,若指定了objFileNameInOut,則保存編譯好的著色器二進制信息到該文件
// ppBlobOut輸出著色器二進制信息
HRESULT CreateShaderFromFile(const WCHAR* objFileNameInOut, const WCHAR* hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** ppBlobOut);
private:
ComPtr mTriangleVS;
ComPtr mTrianglePS;
ComPtr mTriangleGS;
ComPtr mCylinderVS;
ComPtr mCylinderPS;
ComPtr mCylinderGS;
ComPtr mNormalVS;
ComPtr mNormalPS;
ComPtr mNormalGS;
ComPtr mVertexPosColorLayout; // VertexPosColor輸入布局
ComPtr mVertexPosNormalColorLayout; // VertexPosNormalColor輸入布局
ComPtr md3dImmediateContext; // 設備上下文
std::vector> mConstantBuffers; // 常量緩沖區
};
#endif
```
### Basic::InitAll方法
現在需要初始化一堆著色器、輸入布局和常量緩沖區,並綁定常量緩沖區到默認渲染管線:
```cpp
bool BasicFX::InitAll(ComPtr device)
{
if (!device)
return false;
ComPtr blob;
// 創建頂點著色器和頂點布局
HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.vso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTriangleVS.GetAddressOf()));
HR(device->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout),
blob->GetBufferPointer(), blob->GetBufferSize(), mVertexPosColorLayout.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Cylinder_VS.vso", L"HLSL\\Cylinder_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderVS.GetAddressOf()));
HR(device->CreateInputLayout(VertexPosNormalColor::inputLayout, ARRAYSIZE(VertexPosNormalColor::inputLayout),
blob->GetBufferPointer(), blob->GetBufferSize(), mVertexPosNormalColorLayout.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Normal_VS.vso", L"HLSL\\Normal_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalVS.GetAddressOf()));
// 創建像素著色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_PS.pso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTrianglePS.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Cylinder_PS.pso", L"HLSL\\Cylinder_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderPS.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Normal_PS.pso", L"HLSL\\Normal_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalPS.GetAddressOf()));
// 創建幾何著色器
HR(CreateShaderFromFile(L"HLSL\\Triangle_GS.gso", L"HLSL\\Triangle_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mTriangleGS.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Cylinder_GS.gso", L"HLSL\\Cylinder_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mCylinderGS.GetAddressOf()));
HR(CreateShaderFromFile(L"HLSL\\Normal_GS.gso", L"HLSL\\Normal_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, mNormalGS.GetAddressOf()));
RenderStates::InitAll(device);
device->GetImmediateContext(md3dImmediateContext.GetAddressOf());
// ******************
// 設置常量緩沖區描述
mConstantBuffers.assign(3, nullptr);
D3D11_BUFFER_DESC cbd;
ZeroMemory(&cbd, sizeof(cbd));
cbd.Usage = D3D11_USAGE_DEFAULT;
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.CPUAccessFlags = 0;
cbd.ByteWidth = Align16Bytes(sizeof(CBChangesEveryFrame));
HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[0].GetAddressOf()));
cbd.ByteWidth = Align16Bytes(sizeof(CBChangesOnResize));
HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[1].GetAddressOf()));
cbd.ByteWidth = Align16Bytes(sizeof(CBNeverChange));
HR(device->CreateBuffer(&cbd, nullptr, mConstantBuffers[2].GetAddressOf()));
// 預先綁定各自所需的緩沖區
md3dImmediateContext->VSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
md3dImmediateContext->VSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
md3dImmediateContext->VSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());
md3dImmediateContext->GSSetConstantBuffers(0, 1, mConstantBuffers[0].GetAddressOf());
md3dImmediateContext->GSSetConstantBuffers(1, 1, mConstantBuffers[1].GetAddressOf());
md3dImmediateContext->GSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());
md3dImmediateContext->PSSetConstantBuffers(2, 1, mConstantBuffers[2].GetAddressOf());
return true;
}
```
### Basic::SetRenderSplitedTriangle方法--渲染分裂的三角形
該方法處理的是圖元TriangleList。因為後續的方法處理的圖元不同,在調用開始就得設置回正確的圖元。也請確保輸入裝配階段提供好需要分裂的三角形頂點。
```cpp
void BasicFX::SetRenderSplitedTriangle()
{
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
md3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get());
md3dImmediateContext->VSSetShader(mTriangleVS.Get(), nullptr, 0);
md3dImmediateContext->GSSetShader(mTriangleGS.Get(), nullptr, 0);
md3dImmediateContext->RSSetState(nullptr);
md3dImmediateContext->PSSetShader(mTrianglePS.Get(), nullptr, 0);
}
```
### Basic::SetRenderCylinderNoCap方法--渲染圓柱側面
該方法處理的是圖元LineStrip,確保輸入的一系列頂點和法向量能夠在同一平面上。若提供的頂點集合按順時針排布,則會自底向上構建出圓柱體,反之則是自頂向下構建。
這裏需要關閉背面裁剪,因為我們也可以看到圓柱體的內部。
```cpp
void BasicFX::SetRenderCylinderNoCap()
{
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP);
md3dImmediateContext->IASetInputLayout(mVertexPosNormalColorLayout.Get());
md3dImmediateContext->VSSetShader(mCylinderVS.Get(), nullptr, 0);
md3dImmediateContext->GSSetShader(mCylinderGS.Get(), nullptr, 0);
md3dImmediateContext->RSSetState(RenderStates::RSNoCull.Get());
md3dImmediateContext->PSSetShader(mCylinderPS.Get(), nullptr, 0);
}
```
### Basic::SetRenderNormal方法--渲染法向量
該方法處理的圖元是PointList,確保輸入的頂點要包含法向量。
```cpp
void BasicFX::SetRenderNormal()
{
md3dImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);
md3dImmediateContext->IASetInputLayout(mVertexPosNormalColorLayout.Get());
md3dImmediateContext->VSSetShader(mNormalVS.Get(), nullptr, 0);
md3dImmediateContext->GSSetShader(mNormalGS.Get(), nullptr, 0);
md3dImmediateContext->RSSetState(nullptr);
md3dImmediateContext->PSSetShader(mNormalPS.Get(), nullptr, 0);
}
```
## GameApp類的變化
該項目包含上面三種實戰內容,需要用戶去指定當前播放的模式。
首先聲明部分變化如下:
```cpp
class GameApp : public D3DApp
{
public:
enum class Mode { SplitedTriangle, CylinderNoCap, CylinderNoCapWithNormal };
public:
GameApp(HINSTANCE hInstance);
~GameApp();
bool Init();
void OnResize();
void UpdateScene(float dt);
void DrawScene();
private:
bool InitResource();
void ResetTriangle();
void ResetRoundWire();
private:
ComPtr mColorBrush; // 單色筆刷
ComPtr mFont; // 字體
ComPtr mTextFormat; // 文本格式
ComPtr mVertexBuffer; // 頂點集合
int mVertexCount; // 頂點數目
Mode mShowMode; // 當前顯示模式
BasicFX mBasicFX; // Basic特效管理類
CBChangesEveryFrame mCBChangeEveryFrame; // 該緩沖區存放每幀更新的變量
CBChangesOnResize mCBOnReSize; // 該緩沖區存放僅在窗口大小變化時更新的變量
CBNeverChange mCBNeverChange; // 該緩沖區存放不會再進行修改的變量
};
```
### GameApp::ResetTriangle方法--重設為三角形頂點
```cpp
void GameApp::ResetTriangle()
{
// ******************
// 初始化三角形
// 設置三角形頂點
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;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建頂點緩沖區
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf()));
// 三角形頂點數
mVertexCount = 3;
}
```
### GameApp::ResetRoundWire方法--重設圓線
```cpp
void GameApp::ResetRoundWire()
{
// ******************
// 初始化圓線
// 設置圓邊上各頂點
// 必須要按順時針設置
// 由於要形成閉環,起始點需要使用2次
// ______
// / // \______/
//
VertexPosNormalColor vertices[41];
for (int i = 0; i < 40; ++i)
{
vertices[i].pos = XMFLOAT3(cosf(XM_PI / 20 * i), -1.0f, -sinf(XM_PI / 20 * i));
vertices[i].normal = XMFLOAT3(cosf(XM_PI / 20 * i), 0.0f, -sinf(XM_PI / 20 * i));
vertices[i].color = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
}
vertices[40] = vertices[0];
// 設置頂點緩沖區描述
D3D11_BUFFER_DESC vbd;
ZeroMemory(&vbd, sizeof(vbd));
vbd.Usage = D3D11_USAGE_DEFAULT;
vbd.ByteWidth = sizeof vertices;
vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vbd.CPUAccessFlags = 0;
// 新建頂點緩沖區
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory(&InitData, sizeof(InitData));
InitData.pSysMem = vertices;
HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf()));
// 線框頂點數
mVertexCount = 41;
}
```
### GameApp::UpdateScene方法變化
每次切換需要記得重新設置頂點緩沖區,重新將頂點集綁定到輸入,並重設渲染類型。
```cpp
void GameApp::UpdateScene(float dt)
{
// 更新鼠標事件,獲取相對偏移量
Mouse::State mouseState = mMouse->GetState();
Mouse::State lastMouseState = mMouseTracker.GetLastState();
mMouseTracker.Update(mouseState);
Keyboard::State keyState = mKeyboard->GetState();
mKeyboardTracker.Update(keyState);
// 更新每幀變化的值
if (mShowMode == Mode::SplitedTriangle)
{
mCBChangeEveryFrame.worldInvTranspose = mCBChangeEveryFrame.world = XMMatrixIdentity();
}
else
{
static float phi = 0.0f, theta = 0.0f;
phi += 0.0001f, theta += 0.00015f;
mCBChangeEveryFrame.world = XMMatrixRotationX(phi) * XMMatrixRotationY(theta);
mCBChangeEveryFrame.worldInvTranspose = XMMatrixTranspose(XMMatrixInverse(nullptr, mCBChangeEveryFrame.world));
}
mBasicFX.UpdateConstantBuffer(mCBChangeEveryFrame);
// 切換顯示模式
if (mKeyboardTracker.IsKeyPressed(Keyboard::D1))
{
mShowMode = Mode::SplitedTriangle;
ResetTriangle();
// 輸入裝配階段的頂點緩沖區設置
UINT stride = sizeof(VertexPosColor); // 跨越字節數
UINT offset = 0; // 起始偏移量
md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);
mBasicFX.SetRenderSplitedTriangle();
}
else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2))
{
mShowMode = Mode::CylinderNoCap;
ResetRoundWire();
// 輸入裝配階段的頂點緩沖區設置
UINT stride = sizeof(VertexPosNormalColor); // 跨越字節數
UINT offset = 0; // 起始偏移量
md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset);
mBasicFX.SetRenderCylinderNoCap();
}
// 顯示法向量
if (mKeyboardTracker.IsKeyPressed(Keyboard::Q))
{
if (mShowMode == Mode::CylinderNoCap)
mShowMode = Mode::CylinderNoCapWithNormal;
else if (mShowMode == Mode::CylinderNoCapWithNormal)
mShowMode = Mode::CylinderNoCap;
}
}
```
### GameApp::DrawScene的變化
```cpp
void GameApp::DrawScene()
{
assert(md3dImmediateContext);
assert(mSwapChain);
md3dImmediateContext->ClearRenderTargetView(mRenderTargetView.Get(), reinterpret_cast(&Colors::Black));
md3dImmediateContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
md3dImmediateContext->Draw(mVertexCount, 0);
// 繪制法向量,繪制完後記得歸位
if (mShowMode == Mode::CylinderNoCapWithNormal)
{
mBasicFX.SetRenderNormal();
md3dImmediateContext->Draw(mVertexCount, 0);
mBasicFX.SetRenderCylinderNoCap();
}
//
// 繪制Direct2D部分
//
md2dRenderTarget->BeginDraw();
std::wstring text = L"切換類型:1-分裂的三角形 2-圓線構造柱面\n"
"當前模式: ";
if (mShowMode == Mode::SplitedTriangle)
text += L"分裂的三角形";
else if (mShowMode == Mode::CylinderNoCap)
text += L"圓線構造柱面(Q-顯示圓線的法向量)";
else
text += L"圓線構造柱面(Q-隱藏圓線的法向量)";
md2dRenderTarget->DrawTextW(text.c_str(), (UINT32)text.length(), mTextFormat.Get(),
D2D1_RECT_F{ 0.0f, 0.0f, 500.0f, 60.0f }, mColorBrush.Get());
HR(md2dRenderTarget->EndDraw());
HR(mSwapChain->Present(0, 0));
}
```
最終效果如下:
![](https://images2018.cnblogs.com/blog/1172605/201808/1172605-20180808214140597-971901454.gif)
[DirectX11 With Windows SDK完整目錄](http://www.cnblogs.com/X-Jun/p/9028764.html)
[Github項目源碼](https://github.com/MKXJun/DirectX11-With-Windows-SDK)
DirectX11 With Windows SDK--15 幾何著色器初探