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

幾何著色器

首先用一張圖來回顧一下渲染管線的各個(gè)階段,目前為止我們接觸的著色器有頂點(diǎn)著色器和像素著色器卦尊,而接觸到的渲染管線階段有:輸入裝配階段酌伊、頂點(diǎn)著色階段、光柵化階段卷员、像素著色階段盈匾、輸出合并階段.

圖1

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

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

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

改變圖元類型(如輸入點(diǎn)圖元,輸出三角形圖元)叙凡;

但它也有缺點(diǎn)劈伴,幾何著色器輸出的頂點(diǎn)數(shù)據(jù)很可能是有較多重復(fù)的,從流輸出拿回到頂點(diǎn)緩沖區(qū)的話會占用較多的內(nèi)存空間狭姨。它本身無法輸出索引數(shù)組宰啦。

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

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

可編程的幾何著色器

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

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

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語言的主函數(shù)......

若在輸入裝配階段指定使用TriangleList圖元的話,初步觀察該代碼涵叮,實(shí)際上你可以發(fā)現(xiàn)其實(shí)該著色器只是把輸入的頂點(diǎn)按原樣輸出給流輸出對象惭蹂,即跟什么都沒做(咸魚)有什么區(qū)別伞插。。不過從這份代碼里面就已經(jīng)包含了幾何著色器所特有的絕大部分語法了盾碗。

首先媚污,幾何著色器是根據(jù)圖元類型來進(jìn)行調(diào)用的,若使用的是TriangleList廷雅,則每一個(gè)三角形的三個(gè)頂點(diǎn)都會作為輸入耗美,觸發(fā)幾何著色器的調(diào)用。這樣一個(gè)TriangleList解釋的30個(gè)頂點(diǎn)會觸發(fā)10次調(diào)用航缀。

對于幾何著色器商架,我們必須要指定它每次調(diào)用所允許輸出的最大頂點(diǎn)數(shù)目。我們可以使用屬性語法來強(qiáng)行修改著色器行為:

[maxvertexcount(N)]

這里N就是每次調(diào)用允許產(chǎn)出的最大頂點(diǎn)數(shù)目芥玉,然后最終輸出的頂點(diǎn)數(shù)目不會超過N的值甸私。maxvertexcount的值應(yīng)當(dāng)盡可能的小。

關(guān)于性能上的表現(xiàn)飞傀,我根據(jù)龍書提供的引用找到了對應(yīng)的說明文檔:

NVIDIA08

雖然是10年前的文檔,這里說到:在GeForce 8800 GTX诬烹,一個(gè)幾何著色器的調(diào)用在輸出1到20個(gè)標(biāo)量的時(shí)候可以達(dá)到最大運(yùn)行性能表現(xiàn)砸烦,但是當(dāng)我們指定最大允許輸出標(biāo)量的數(shù)目在27到40個(gè)時(shí),性能僅達(dá)到峰值的50%绞吁。比如說幢痘,如果頂點(diǎn)的聲明如下:

struct V0

{

? ? float3 pos : POSITION;

? ? float2 tex : TEXCOORD;

};

這里每個(gè)頂點(diǎn)就已經(jīng)包含了5個(gè)標(biāo)量了,如果以它作為輸出類型家破,則maxvertexcount為4的時(shí)候就可以達(dá)到理論上的峰值性能(20個(gè)標(biāo)量)颜说。

但如果頂點(diǎn)類型中還包含有float3類型的法向量,每個(gè)頂點(diǎn)就額外包含了3個(gè)標(biāo)量汰聋,這樣在maxvertexcount為4的時(shí)候就輸出了32個(gè)標(biāo)量门粪,只有50%的峰值性能表現(xiàn)。

這份文檔已經(jīng)將近10年了烹困,對于那時(shí)候的顯卡來說使用幾何著色器可能不是一個(gè)很好的選擇玄妈,不過當(dāng)初的顯卡也早已不能和現(xiàn)在的顯卡相提并論了。

注意:

maxvertexcount的值應(yīng)當(dāng)設(shè)置到盡可能小的值髓梅,因?yàn)樗鼘⒅苯記Q定幾何著色器的運(yùn)行效率拟蜻。

幾何著色器的每次調(diào)用最多只能處理1024個(gè)標(biāo)量,對于只包含4D位置向量的頂點(diǎn)來說也只能處理256個(gè)頂點(diǎn)枯饿。

在HLSL編譯器里酝锅,如果設(shè)置的maxvertexcount過大,會直接收到編譯錯(cuò)誤:

然后代碼中的triangle是用于指定輸入的圖元類型奢方,具體支持的關(guān)鍵字如下:

圖元類型 描述

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章回顧:點(diǎn)擊此處

而參數(shù)類型可以是用戶自定義的結(jié)構(gòu)體類型搔扁,或者是向量(float4)類型爸舒。從頂點(diǎn)著色器傳過來的頂點(diǎn)至少會包含一個(gè)表示齊次裁剪坐標(biāo)的向量。

參數(shù)名inupt實(shí)際上用戶是可以任意指定的阁谆。

對于該輸入?yún)?shù)的元素?cái)?shù)目碳抄,取決于前面聲明的圖元類型:

圖元類型 元素?cái)?shù)目

point [1] 每次只能處理1個(gè)頂點(diǎn)

line [2] 一個(gè)線段必須包含2個(gè)頂點(diǎn)

triangle [3] 一個(gè)三角形需要3個(gè)頂點(diǎn)

lineadj [4] 一個(gè)鄰接線段需要4個(gè)頂點(diǎn)

triangleadj [6] 一個(gè)鄰接三角形需要6個(gè)頂點(diǎn)

而第二個(gè)參數(shù)必須是一個(gè)流輸出對象,而且需要被指定為inout可讀寫類型场绿∑市В可以看到,它是一個(gè)類模板焰盗,模板的形參指定要輸出的類型璧尸。流輸出對象有如下三種:

流輸出對象類型 描述

PointStream 一系列點(diǎn)的圖元

LineStream 一系列線段的圖元

TriangleStream 一系列三角形的圖元

流輸出對象都具有下面兩種方法:

方法 描述

Append 向指定的流輸出對象添加一個(gè)輸出的數(shù)據(jù)

RestartStrip 在以線段或者三角形作為圖元的時(shí)候,默認(rèn)是以strip的形式輸出的熬拒,

如果我們不希望下一個(gè)輸出的頂點(diǎn)與之前的頂點(diǎn)構(gòu)成新圖元爷光,則需要

調(diào)用此方法來重新開始新的strip。若希望輸出的圖元類型也保持和原

來一樣的TriangleList澎粟,則需要每調(diào)用3次Append方法后就調(diào)用一次

RestartStrip蛀序。

注意:

所謂的刪除頂點(diǎn),實(shí)際上就是不將該頂點(diǎn)傳遞給流輸出對象

若傳入的頂點(diǎn)中多余的部分無法構(gòu)成對應(yīng)的圖元活烙,則拋棄掉這些多余的頂點(diǎn)

在開始前徐裸,先放出Basic.fx文件的內(nèi)容:

#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;

};

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

現(xiàn)在我們的目標(biāo)是把一個(gè)三角形分裂成三個(gè)三角形:

圖2

HLSL代碼Triangle_VS.hlsl, Triangle_GS.hlsl和Triangle_PS.hlsl的實(shí)現(xiàn)如下:// 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)

{

? ? //

? ? // 將一個(gè)三角形分裂成三個(gè)三角形,即沒有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;

}

這里輸入和輸出的圖元類型都是一致的啸盏,但無論什么情況都一定要注意設(shè)置好maxvertexcount的值重贺,這里固定一個(gè)三角形的三個(gè)頂點(diǎn)輸出9個(gè)頂點(diǎn)(構(gòu)成三個(gè)三角形),并且每3次Append就需要調(diào)用1次RestartStrip回懦。

實(shí)戰(zhàn)2: 通過圓線構(gòu)造圓柱體側(cè)面

已知圖元類型為LineStrip气笙,現(xiàn)在有一系列連續(xù)的頂點(diǎn)構(gòu)成圓線(近似圓弧的連續(xù)折線),構(gòu)造出圓柱體的側(cè)面怯晕。即輸入圖元類型為線段潜圃,輸出一個(gè)矩形(兩個(gè)三角形)。

圖3

思路: 光有頂點(diǎn)位置還不足以構(gòu)造出圓柱體側(cè)面贫贝,因?yàn)闊o法確定圓柱往哪個(gè)方向延伸秉犹。所以我們還需要對每個(gè)頂點(diǎn)引入所在圓柱側(cè)面的法向量,通過叉乘就可以確定上方向/下方向并進(jìn)行延伸了稚晚。

HLSL代碼

Cylinder_VS.hlsl,?Cylinder_GS.hlsl和Cylinder_PS.hlsl的實(shí)現(xiàn)如下:

// 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:// 一個(gè)v0v1線段輸出6個(gè)三角形頂點(diǎn)[maxvertexcount(6)]void GS(line VertexPosHWNormalColor input[2], inout TriangleStream output){// *****************************// 要求圓線框是順時(shí)針的崇堵,然后自底向上構(gòu)造圓柱側(cè)面? ? ? ? ? //? -->? ? ? 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{// 標(biāo)準(zhǔn)化法向量pIn.NormalW = normalize(pIn.NormalW);// 頂點(diǎn)指向眼睛的向量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);// 只計(jì)算方向光ComputeDirectionalLight(gMaterial, gDirLight, pIn.NormalW, toEyeW, ambient, diffuse, spec);returnpIn.Color * (ambient + diffuse) + spec;}

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

畫出頂點(diǎn)的法向量可以幫助你進(jìn)行調(diào)試,排查法向量是否出現(xiàn)了問題客燕。這時(shí)候圖元的類型為PointList鸳劳,需要通過幾何著色器輸出一個(gè)線段(兩個(gè)頂點(diǎn))。由于頂點(diǎn)中包含法向量也搓,剩下的就是要自行決定法向量的長度赏廓。

下圖的法向量長度為0.5


圖4

HLSL代碼Normal_VS.hlsl, Normal_GS.hlsl和Normal_PS.hlsl的實(shí)現(xiàn)如下:// 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é)的倍數(shù)涵紊,該函數(shù)可以返回合適的字節(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); // 是否已經(jīng)初始化 bool IsInit() const; templatevoid UpdateConstantBuffer(const T& cbuffer); // 繪制三角形分裂 void SetRenderSplitedTriangle(); // 繪制無上下蓋的圓柱體 void SetRenderCylinderNoCap(); // 繪制所有頂點(diǎn)的法向量 void SetRenderNormal();private: // objFileNameInOut為編譯好的著色器二進(jìn)制文件(.*so),若有指定則優(yōu)先尋找該文件并讀取 // hlslFileName為著色器代碼幔摸,若未找到著色器二進(jìn)制文件則編譯著色器代碼 // 編譯成功后摸柄,若指定了objFileNameInOut,則保存編譯好的著色器二進(jìn)制信息到該文件 // ppBlobOut輸出著色器二進(jìn)制信息 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; // 設(shè)備上下文 std::vector> mConstantBuffers; // 常量緩沖區(qū)};#endifBasic::InitAll方法現(xiàn)在需要初始化一堆著色器既忆、輸入布局和常量緩沖區(qū)驱负,并綁定常量緩沖區(qū)到默認(rèn)渲染管線:bool BasicFX::InitAll(ComPtrdevice){ if (!device) return false; ComPtrblob; // 創(chuàng)建頂點(diǎn)著色器和頂點(diǎn)布局 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()); // ****************** // 設(shè)置常量緩沖區(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())); // 預(yù)先綁定各自所需的緩沖區(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。因?yàn)楹罄m(xù)的方法處理的圖元不同患雇,在調(diào)用開始就得設(shè)置回正確的圖元跃脊。也請確保輸入裝配階段提供好需要分裂的三角形頂點(diǎn)。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方法--渲染圓柱側(cè)面該方法處理的是圖元LineStrip苛吱,確保輸入的一系列頂點(diǎn)和法向量能夠在同一平面上酪术。若提供的頂點(diǎn)集合按順時(shí)針排布,則會自底向上構(gòu)建出圓柱體翠储,反之則是自頂向下構(gòu)建绘雁。這里需要關(guān)閉背面裁剪,因?yàn)槲覀円部梢钥吹綀A柱體的內(nèi)部援所。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咧七,確保輸入的頂點(diǎn)要包含法向量。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類的變化該項(xiàng)目包含上面三種實(shí)戰(zhàn)內(nèi)容任斋,需要用戶去指定當(dāng)前播放的模式。首先聲明部分變化如下: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; // 頂點(diǎn)集合 int mVertexCount; // 頂點(diǎn)數(shù)目 Mode mShowMode; // 當(dāng)前顯示模式 BasicFX mBasicFX; // Basic特效管理類 CBChangesEveryFrame mCBChangeEveryFrame; // 該緩沖區(qū)存放每幀更新的變量 CBChangesOnResize mCBOnReSize; // 該緩沖區(qū)存放僅在窗口大小變化時(shí)更新的變量 CBNeverChange mCBNeverChange; // 該緩沖區(qū)存放不會再進(jìn)行修改的變量};GameApp::ResetTriangle方法--重設(shè)為三角形頂點(diǎn)void GameApp::ResetTriangle(){ // ****************** // 初始化三角形 // 設(shè)置三角形頂點(diǎn) 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) } }; // 設(shè)置頂點(diǎn)緩沖區(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; // 新建頂點(diǎn)緩沖區(qū) D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData, sizeof(InitData)); InitData.pSysMem = vertices; HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf())); // 三角形頂點(diǎn)數(shù) mVertexCount = 3;}GameApp::ResetRoundWire方法--重設(shè)圓線void GameApp::ResetRoundWire(){ // ****************** // 初始化圓線 // 設(shè)置圓邊上各頂點(diǎn) // 必須要按順時(shí)針設(shè)置 // 由于要形成閉環(huán)耻涛,起始點(diǎ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]; // 設(shè)置頂點(diǎn)緩沖區(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; // 新建頂點(diǎn)緩沖區(qū) D3D11_SUBRESOURCE_DATA InitData; ZeroMemory(&InitData, sizeof(InitData)); InitData.pSysMem = vertices; HR(md3dDevice->CreateBuffer(&vbd, &InitData, mVertexBuffer.ReleaseAndGetAddressOf())); // 線框頂點(diǎn)數(shù) mVertexCount = 41;}GameApp::UpdateScene方法變化每次切換需要記得重新設(shè)置頂點(diǎn)緩沖區(qū)废酷,重新將頂點(diǎn)集綁定到輸入,并重設(shè)渲染類型抹缕。void GameApp::UpdateScene(float dt){ // 更新鼠標(biāo)事件澈蟆,獲取相對偏移量 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(); // 輸入裝配階段的頂點(diǎn)緩沖區(qū)設(shè)置 UINT stride = sizeof(VertexPosColor); // 跨越字節(jié)數(shù) UINT offset = 0; // 起始偏移量 md3dImmediateContext->IASetVertexBuffers(0, 1, mVertexBuffer.GetAddressOf(), &stride, &offset); mBasicFX.SetRenderSplitedTriangle(); } else if (mKeyboardTracker.IsKeyPressed(Keyboard::D2)) { mShowMode = Mode::CylinderNoCap; ResetRoundWire(); // 輸入裝配階段的頂點(diǎn)緩沖區(qū)設(shè)置 UINT stride = sizeof(VertexPosNormalColor); // 跨越字節(jié)數(shù) 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-圓線構(gòu)造柱面\n"

? ? ? ? "當(dāng)前模式: ";

? ? if (mShowMode == Mode::SplitedTriangle)

? ? ? ? text += L"分裂的三角形";

? ? else if (mShowMode == Mode::CylinderNoCap)

? ? ? ? text += L"圓線構(gòu)造柱面(Q-顯示圓線的法向量)";

? ? else

? ? ? ? text += L"圓線構(gòu)造柱面(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

喜歡的小伙伴點(diǎn)個(gè)關(guān)注卓研。想要學(xué)習(xí)資料的可以加qun710520381.學(xué)習(xí)編號:久伴趴俘。感謝大家支持!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末奏赘,一起剝皮案震驚了整個(gè)濱河市寥闪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌磨淌,老刑警劉巖疲憋,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梁只,居然都是意外死亡缚柳,警方通過查閱死者的電腦和手機(jī)埃脏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秋忙,“玉大人彩掐,你說我怎么就攤上這事』易罚” “怎么了堵幽?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長监嗜。 經(jīng)常有香客問我谐檀,道長,這世上最難降的妖魔是什么裁奇? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任桐猬,我火速辦了婚禮,結(jié)果婚禮上刽肠,老公的妹妹穿的比我還像新娘溃肪。我一直安慰自己,他們只是感情好音五,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布惫撰。 她就那樣靜靜地躺著,像睡著了一般躺涝。 火紅的嫁衣襯著肌膚如雪厨钻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天坚嗜,我揣著相機(jī)與錄音夯膀,去河邊找鬼。 笑死苍蔬,一個(gè)胖子當(dāng)著我的面吹牛诱建,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播碟绑,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼俺猿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了格仲?” 一聲冷哼從身側(cè)響起押袍,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碎连,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡汽摹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年午笛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惭蟋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,626評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡药磺,死狀恐怖告组,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情癌佩,我是刑警寧澤木缝,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站围辙,受9級特大地震影響我碟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姚建,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一矫俺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掸冤,春花似錦厘托、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至饺藤,卻和暖如春包斑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涕俗。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工舰始, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咽袜。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像枕稀,于是被迫代替她去往敵國和親询刹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容