DirectX11 With Windows SDK--15 几何着色器初探

# 前言
从这一部分开始,感觉就像是踏入了无人深空一样,在之前初学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 output
)
{
for (uint i = 0; i 注意:
> 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 output)
{
// *****************************
// 要求圆线框是顺时针的,然后自底向上构造圆柱侧面
// --> v2____v3
// ______ |\ |
// / \ | \ |
// \______/ | \ |
// 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 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)

原文地址:https://www.cnblogs.com/X-Jun/p/9429881.html

时间: 2024-10-01 00:22:11

DirectX11 With Windows SDK--15 几何着色器初探的相关文章

DirectX11 With Windows SDK--17 利用几何着色器实现公告板效果

前言 上一章我们知道了如何使用几何着色器将顶点通过流输出阶段输出到绑定的顶点缓冲区.接下来我们继续利用它来实现一些新的效果,在这一章,你将了解: 实现公告板效果 Alpha-To-Coverage 对GPU资源进行读/写操作 纹理数组 实现雾效 DirectX11 With Windows SDK完整目录 Github项目源码 实现雾效 虽然这部分与几何着色器并没有什么关系,但是雾的效果在该Demo中会用到,并且前面也没有讲过这部分内容,故先在这里提出来. 有时候我们需要在游戏中模拟一些特定的天

DirectX11 With Windows SDK--16 利用几何着色器可选的流输出阶段帮助绘制多种分形

前言 在上一章,我们知道了如何使用几何着色器来重新组装图元,比如从一个三角形分裂成三个三角形.但是为了实现更高阶的分形,我们必须要从几何着色器拿到输出的顶点.这里我们可以使用可选的流输出阶段来拿到顶点集合. 注意: 本章末尾有大量的GIF动图! DirectX11 With Windows SDK完整目录 Github项目源码 流输出阶段 现在我们知道GPU可以写入纹理(textures),例如深度/模板缓冲区以及后备缓冲区.当然,我们也可以通过渲染管线的流输出阶段让GPU将几何着色器输出的顶点

粒子系统与雨的效果 (DirectX11 with Windows SDK)

前言 最近在学粒子系统,看这之前的<<3D图形编程基础 基于DirectX 11 >>是基于Direct SDK的,而DXSDK微软已经很久没有更新过了并且我学的DX11是用Windows SDK来实现. 顺手安利一波:我最近在学DX11 with WindowSDK 教程 博客地址: https://www.cnblogs.com/X-Jun/p/9028764.html 所以下面的都是基于这个教程和上面提到的那本书来写的,推荐看到教程17章和书里第15章之后再来看这篇博客,有助

DirectX11 With Windows SDK 24--Render-To-Texture(RTT)技术的应用、使用ScreenGrab保存纹理到文件

前言 尽管在上一章的动态天空盒中用到了Render-To-Texture技术,但那是针对纹理立方体的特化实现.考虑到该技术的应用层面非常广,在这里抽出独立的一章专门来讲有关它的通用实现以及各种应用.此外,这里还会讲到如何使用DirectXTex的ScreenGrab来保存纹理,可以说是干货满满了. 如果想要看Render-To-Texture在动态天空盒的应用,可以点此回顾: 章节 23 立方体映射:动态天空盒的实现 DirectX11 With Windows SDK完整目录 Github项目

opengl 几何着色器

绘制4条线段 1 #define GLEW_STATIC 2 #include <GL/glew.h> 3 #include <GLFW/glfw3.h> 4 5 #include "Shader.h" 6 #include <fstream> 7 #include <iostream> 8 using namespace std; 9 10 void framebuffer_size_callback(GLFWwindow* windo

DirectX11 Windows Windows SDK--28 计算着色器:波浪(水波)

前言 有关计算着色器的基础其实并不是很多.接下来继续讲解如何使用计算着色器实现水波效果,即龙书中所实现的水波.但是光看代码可是完全看不出来是在做什么的.个人根据书中所给的参考书籍找到了对应的实现原理,但是里面涉及到比较多的物理公式.因此,要看懂这一章需要有高数功底(求导.偏导.微分方程),我会把推导过程给列出来. 本章演示项目还用到了其他的一些效果,在学习本章之前建议先了解如下内容: 章节内容 11 混合状态 17 利用几何着色器实现公告板效果(雾效部分) 26 计算着色器:入门 27 计算着色

DirectX11 With Windows SDK--29 计算着色器:内存模型、线程同步;实现顺序无关透明度(OIT)

前言 由于透明混合在不同的绘制顺序下结果会不同,这就要求绘制前要对物体进行排序,然后再从后往前渲染.但即便是仅渲染一个物体(如上一章的水波),也会出现透明绘制顺序不对的情况,普通的绘制是无法避免的.如果要追求正确的效果,就需要对每个像素位置对所有的像素按深度值进行排序.本章将介绍一种仅DirectX11及更高版本才能实现的顺序无关的透明度(Order-Independent Transparency,OIT),虽然它是用像素着色器来实现的,但是用到了计算着色器里面的一些相关知识. 这一章综合性很

DirectX11 With Windows SDK--30 计算着色器:图像模糊、索贝尔算子

前言 到这里计算着色器的主线学习基本结束,剩下的就是再补充两个有关图像处理方面的应用.这里面包含了龙书11的图像模糊,以及龙书12额外提到的Sobel算子进行边缘检测.主要内容源自于龙书12,项目源码也基于此进行调整. 学习目标: 熟悉图像处理常用的卷积 熟悉高斯模糊.Sobel算子 DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报. 图像卷积 在图像处理中,经常需要用到

DirectX11 With Windows SDK--26 计算着色器:入门

前言 现在开始迎来所谓的高级篇了,目前计划是计算着色器部分的内容视项目情况,大概会分3-5章来讲述. DirectX11 With Windows SDK完整目录 Github项目源码 欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报. 概述 GPU通常被设计为从一个位置或连续的位置读取并处理大量的内存数据(即流操作),而CPU则被设计为专门处理随机内存的访问. 由于顶点数据和像素数据可以分开处理,GPU架构使得它能够高度并行,在处理图像上效率非常高.但