DirectX11 With Windows SDK--15 幾何著色器初探

幾何著色器

首先用一張圖來回顧一下渲染管線的各個階段,目前為止我們接觸的著色器有頂點著色器和像素著色器,而接觸到的渲染管線階段有:輸入裝配階段、頂點著色階段、光柵化階段、像素著色階段、輸出合并階段.

圖1

可以看到,幾何著色器是我們在將頂點送入光柵化階段之前,可以操作頂點的最后一個階段。它同樣也允許我們編寫自己的著色器代碼。幾何著色器可以做如下事情:

讓程序自動決定如何在渲染管線中插入/移除幾何體;

通過流輸出階段將頂點信息再次傳遞到頂點緩沖區(qū);

改變圖元類型(如輸入點圖元,輸出三角形圖元);

但它也有缺點,幾何著色器輸出的頂點數據很可能是有較多重復的,從流輸出拿回到頂點緩沖區(qū)的話會占用較多的內存空間。它本身無法輸出索引數組。

幾何著色階段會收到一系列代表輸入幾何體類型的頂點,然后我們可以自由選擇其中的這些頂點信息,然后交給流輸出對象重新解釋成新的圖元類型(或者不變),傳遞給流輸出階段或者是光柵化階段。而幾何著色器僅能夠接受來自輸入裝配階段提供的頂點信息,對每個頂點進行處理,無法自行決定增減頂點。

注意:離開幾何著色器的頂點如果要傳遞給光柵化階段,需要包含有轉換到齊次裁剪坐標系的坐標信息(語義為SV_POSITION的float4向量)。

可編程的幾何著色器

從一個看似沒什么用的幾何著色器代碼入手

若我們直接從VS項目新建一個幾何著色器文件,則可以看到下面的代碼:

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圖元的話,初步觀察該代碼,實際上你可以發(fā)現其實該著色器只是把輸入的頂點按原樣輸出給流輸出對象,即跟什么都沒做(咸魚)有什么區(qū)別。。不過從這份代碼里面就已經包含了幾何著色器所特有的絕大部分語法了。

首先,幾何著色器是根據圖元類型來進行調用的,若使用的是TriangleList,則每一個三角形的三個頂點都會作為輸入,觸發(fā)幾何著色器的調用。這樣一個TriangleList解釋的30個頂點會觸發(fā)10次調用。

對于幾何著色器,我們必須要指定它每次調用所允許輸出的最大頂點數目。我們可以使用屬性語法來強行修改著色器行為:

[maxvertexcount(N)]

這里N就是每次調用允許產出的最大頂點數目,然后最終輸出的頂點數目不會超過N的值。maxvertexcount的值應當盡可能的小。

關于性能上的表現,我根據龍書提供的引用找到了對應的說明文檔:

NVIDIA08

雖然是10年前的文檔,這里說到:在GeForce 8800 GTX,一個幾何著色器的調用在輸出1到20個標量的時候可以達到最大運行性能表現,但是當我們指定最大允許輸出標量的數目在27到40個時,性能僅達到峰值的50%。比如說,如果頂點的聲明如下:

struct V0

{

? ? float3 pos : POSITION;

? ? float2 tex : TEXCOORD;

};

這里每個頂點就已經包含了5個標量了,如果以它作為輸出類型,則maxvertexcount為4的時候就可以達到理論上的峰值性能(20個標量)。

但如果頂點類型中還包含有float3類型的法向量,每個頂點就額外包含了3個標量,這樣在maxvertexcount為4的時候就輸出了32個標量,只有50%的峰值性能表現。

這份文檔已經將近10年了,對于那時候的顯卡來說使用幾何著色器可能不是一個很好的選擇,不過當初的顯卡也早已不能和現在的顯卡相提并論了。

注意:

maxvertexcount的值應當設置到盡可能小的值,因為它將直接決定幾何著色器的運行效率。

幾何著色器的每次調用最多只能處理1024個標量,對于只包含4D位置向量的頂點來說也只能處理256個頂點。

在HLSL編譯器里,如果設置的maxvertexcount過大,會直接收到編譯錯誤:

然后代碼中的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章回顧:點擊此處

而參數類型可以是用戶自定義的結構體類型,或者是向量(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。

注意:

所謂的刪除頂點,實際上就是不將該頂點傳遞給流輸出對象

若傳入的頂點中多余的部分無法構成對應的圖元,則拋棄掉這些多余的頂點

在開始前,先放出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;

};

實戰(zhàn)1: 將一個三角形分割成三個三角形

現在我們的目標是把一個三角形分裂成三個三角形:

圖2

HLSL代碼Triangle_VS.hlsl, Triangle_GS.hlsl和Triangle_PS.hlsl的實現如下:// 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;}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();

? ? }

}

// Triangle_PS.hlsl

#include "Basic.fx"

float4 PS(VertexPosHColor pIn) : SV_Target

{

? ? return pIn.Color;

}

這里輸入和輸出的圖元類型都是一致的,但無論什么情況都一定要注意設置好maxvertexcount的值,這里固定一個三角形的三個頂點輸出9個頂點(構成三個三角形),并且每3次Append就需要調用1次RestartStrip。

實戰(zhàn)2: 通過圓線構造圓柱體側面

已知圖元類型為LineStrip,現在有一系列連續(xù)的頂點構成圓線(近似圓弧的連續(xù)折線),構造出圓柱體的側面。即輸入圖元類型為線段,輸出一個矩形(兩個三角形)。

圖3

思路: 光有頂點位置還不足以構造出圓柱體側面,因為無法確定圓柱往哪個方向延伸。所以我們還需要對每個頂點引入所在圓柱側面的法向量,通過叉乘就可以確定上方向/下方向并進行延伸了。

HLSL代碼

Cylinder_VS.hlsl,?Cylinder_GS.hlsl和Cylinder_PS.hlsl的實現如下:

// Cylinder_VS.hlsl#include "Basic.fx"VertexPosHWNormalColor VS(VertexPosNormalColorpIn){? ? VertexPosHWNormalColor pOut;row_majormatrixviewProj = 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;returnpOut;}

// Cylinder_GS.hlsl#include "Basic.fx"http:// 一個v0v1線段輸出6個三角形頂點[maxvertexcount(6)]void GS(line VertexPosHWNormalColor input[2], inout TriangleStream output){// *****************************// 要求圓線框是順時針的,然后自底向上構造圓柱側面? ? ? ? ? //? -->? ? ? v2____v3//? ______? ? |\? |// /? ? ? \? ? | \? |// \______/? ? |? \ |//? <--? ? ? |___\|//? ? ? ? ? v1(i1) v0(i0)float3upDir = normalize(cross(input[0].NormalW, (input[1].PosW - input[0].PosW)));? ? VertexPosHWNormalColor v2, v3;matrixviewProj = 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]);}

// Cylinder_PS.hlsl#include "Basic.fx"float4PS(VertexPosHWNormalColor pIn) : SV_Target{// 標準化法向量pIn.NormalW = normalize(pIn.NormalW);// 頂點指向眼睛的向量float3toEyeW = normalize(gEyePosW - pIn.PosW);// 初始化為0 float4ambient =float4(0.0f,0.0f,0.0f,0.0f);float4diffuse =float4(0.0f,0.0f,0.0f,0.0f);float4spec =float4(0.0f,0.0f,0.0f,0.0f);// 只計算方向光ComputeDirectionalLight(gMaterial, gDirLight, pIn.NormalW, toEyeW, ambient, diffuse, spec);returnpIn.Color * (ambient + diffuse) + spec;}

實戰(zhàn)3: 畫出頂點的法向量

畫出頂點的法向量可以幫助你進行調試,排查法向量是否出現了問題。這時候圖元的類型為PointList,需要通過幾何著色器輸出一個線段(兩個頂點)。由于頂點中包含法向量,剩下的就是要自行決定法向量的長度。

下圖的法向量長度為0.5


圖4

HLSL代碼Normal_VS.hlsl, Normal_GS.hlsl和Normal_PS.hlsl的實現如下:// 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;}// Normal_GS.hlsl#include "Basic.fx"[maxvertexcount(2)]void GS(point VertexPosHWNormalColor input[1], inout LineStreamoutput){ 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);}// Normal_PS.hlsl#include "Basic.fx"float4 PS(VertexPosHWNormalColor pIn) : SV_TARGET{ return pIn.Color;}回到頂部C++代碼的部分變化BasicFX.h的變化常量緩沖區(qū)和BasicFX類的變化如下:#ifndef BASICFX_H#define BASICFX_H#include#include#include#include#include#include "LightHelper.h"#include "RenderStates.h"#include "Vertex.h"http:// 由于常量緩沖區(qū)的創(chuàng)建需要是16字節(jié)的倍數,該函數可以返回合適的字節(jié)大小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)簡化類型名 templateusing ComPtr = Microsoft::WRL::ComPtr; // 初始化Basix.fx所需資源并初始化光柵化狀態(tài) bool InitAll(ComPtrdevice); // 是否已經初始化 bool IsInit() const; templatevoid UpdateConstantBuffer(const T& cbuffer); // 繪制三角形分裂 void SetRenderSplitedTriangle(); // 繪制無上下蓋的圓柱體 void SetRenderCylinderNoCap(); // 繪制所有頂點的法向量 void SetRenderNormal();private: // objFileNameInOut為編譯好的著色器二進制文件(.*so),若有指定則優(yōu)先尋找該文件并讀取 // hlslFileName為著色器代碼,若未找到著色器二進制文件則編譯著色器代碼 // 編譯成功后,若指定了objFileNameInOut,則保存編譯好的著色器二進制信息到該文件 // ppBlobOut輸出著色器二進制信息 HRESULT CreateShaderFromFile(const WCHAR* objFileNameInOut, const WCHAR* hlslFileName, LPCSTR entryPoint, LPCSTR shaderModel, ID3DBlob** ppBlobOut);private: ComPtrmTriangleVS; ComPtrmTrianglePS; ComPtrmTriangleGS; ComPtrmCylinderVS; ComPtrmCylinderPS; ComPtrmCylinderGS; ComPtrmNormalVS; ComPtrmNormalPS; ComPtrmNormalGS; ComPtrmVertexPosColorLayout; // VertexPosColor輸入布局 ComPtrmVertexPosNormalColorLayout; // VertexPosNormalColor輸入布局 ComPtrmd3dImmediateContext; // 設備上下文 std::vector> mConstantBuffers; // 常量緩沖區(qū)};#endifBasic::InitAll方法現在需要初始化一堆著色器、輸入布局和常量緩沖區(qū),并綁定常量緩沖區(qū)到默認渲染管線:bool BasicFX::InitAll(ComPtrdevice){ if (!device) return false; ComPtrblob; // 創(chuàng)建頂點著色器和頂點布局 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())); // 創(chuàng)建像素著色器 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())); // 創(chuàng)建幾何著色器 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()); // ****************** // 設置常量緩沖區(qū)描述 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())); // 預先綁定各自所需的緩沖區(qū) 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。因為后續(xù)的方法處理的圖元不同,在調用開始就得設置回正確的圖元。也請確保輸入裝配階段提供好需要分裂的三角形頂點。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,確保輸入的一系列頂點和法向量能夠在同一平面上。若提供的頂點集合按順時針排布,則會自底向上構建出圓柱體,反之則是自頂向下構建。這里需要關閉背面裁剪,因為我們也可以看到圓柱體的內部。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,確保輸入的頂點要包含法向量。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類的變化該項目包含上面三種實戰(zhàn)內容,需要用戶去指定當前播放的模式。首先聲明部分變化如下: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: ComPtrmColorBrush; // 單色筆刷 ComPtrmFont; // 字體 ComPtrmTextFormat; // 文本格式 ComPtrmVertexBuffer; // 頂點集合 int mVertexCount; // 頂點數目 Mode mShowMode; // 當前顯示模式 BasicFX mBasicFX; // Basic特效管理類 CBChangesEveryFrame mCBChangeEveryFrame; // 該緩沖區(qū)存放每幀更新的變量 CBChangesOnResize mCBOnReSize; // 該緩沖區(qū)存放僅在窗口大小變化時更新的變量 CBNeverChange mCBNeverChange; // 該緩沖區(qū)存放不會再進行修改的變量};GameApp::ResetTriangle方法--重設為三角形頂點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) } }; // 設置頂點緩沖區(qū)描述 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; // 新建頂點緩沖區(qū) D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData, sizeof(InitData)); InitData.pSysMem = vertices; HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf())); // 三角形頂點數 mVertexCount = 3;}GameApp::ResetRoundWire方法--重設圓線void GameApp::ResetRoundWire(){ // ****************** // 初始化圓線 // 設置圓邊上各頂點 // 必須要按順時針設置 // 由于要形成閉環(huán),起始點需要使用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]; // 設置頂點緩沖區(qū)描述 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; // 新建頂點緩沖區(qū) D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData, sizeof(InitData)); InitData.pSysMem = vertices; HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf())); // 線框頂點數 mVertexCount = 41;}GameApp::UpdateScene方法變化每次切換需要記得重新設置頂點緩沖區(qū),重新將頂點集綁定到輸入,并重設渲染類型。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(); // 輸入裝配階段的頂點緩沖區(qū)設置 UINT stride = sizeof(VertexPosColor); // 跨越字節(jié)數 UINT offset = 0; // 起始偏移量 md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset); mBasicFX.SetRenderSplitedTriangle(); } else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2)) { mShowMode = Mode::CylinderNoCap; ResetRoundWire(); // 輸入裝配階段的頂點緩沖區(qū)設置 UINT stride = sizeof(VertexPosNormalColor); // 跨越字節(jié)數 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的變化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));

}

最終效果如下:

圖6

喜歡的小伙伴點個關注。想要學習資料的可以加qun710520381.學習編號:久伴。感謝大家支持!

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容