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

几何着色器

首先用一张图来回顾一下渲染管线的各个阶段,目前为止我们接触的着色器有顶点着色器和像素着色器,而接触到的渲染管线阶段有:输入装配阶段、顶点着色阶段、光栅化阶段、像素着色阶段、输出合并阶段.

图1

可以看到,几何着色器是我们在将顶点送入光栅化阶段之前,可以操作顶点的最后一个阶段。它同样也允许我们编写自己的着色器代码。几何着色器可以做如下事情:

让程序自动决定如何在渲染管线中插入/移除几何体;

通过流输出阶段将顶点信息再次传递到顶点缓冲区;

改变图元类型(如输入点图元,输出三角形图元);

但它也有缺点,几何着色器输出的顶点数据很可能是有较多重复的,从流输出拿回到顶点缓冲区的话会占用较多的内存空间。它本身无法输出索引数组。

几何着色阶段会收到一系列代表输入几何体类型的顶点,然后我们可以自由选择其中的这些顶点信息,然后交给流输出对象重新解释成新的图元类型(或者不变),传递给流输出阶段或者是光栅化阶段。而几何着色器仅能够接受来自输入装配阶段提供的顶点信息,对每个顶点进行处理,无法自行决定增减顶点。

注意:离开几何着色器的顶点如果要传递给光栅化阶段,需要包含有转换到齐次裁剪坐标系的坐标信息(语义为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图元的话,初步观察该代码,实际上你可以发现其实该着色器只是把输入的顶点按原样输出给流输出对象,即跟什么都没做(咸鱼)有什么区别。。不过从这份代码里面就已经包含了几何着色器所特有的绝大部分语法了。

首先,几何着色器是根据图元类型来进行调用的,若使用的是TriangleList,则每一个三角形的三个顶点都会作为输入,触发几何着色器的调用。这样一个TriangleList解释的30个顶点会触发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;

};

实战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。

实战2: 通过圆线构造圆柱体侧面

已知图元类型为LineStrip,现在有一系列连续的顶点构成圆线(近似圆弧的连续折线),构造出圆柱体的侧面。即输入图元类型为线段,输出一个矩形(两个三角形)。

图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"// 一个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;}

实战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的变化常量缓冲区和BasicFX类的变化如下:#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)简化类型名 templateusing ComPtr = Microsoft::WRL::ComPtr; // 初始化Basix.fx所需资源并初始化光栅化状态 bool InitAll(ComPtrdevice); // 是否已经初始化 bool IsInit() const; templatevoid 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: ComPtrmTriangleVS; ComPtrmTrianglePS; ComPtrmTriangleGS; ComPtrmCylinderVS; ComPtrmCylinderPS; ComPtrmCylinderGS; ComPtrmNormalVS; ComPtrmNormalPS; ComPtrmNormalGS; ComPtrmVertexPosColorLayout; // VertexPosColor输入布局 ComPtrmVertexPosNormalColorLayout; // VertexPosNormalColor输入布局 ComPtrmd3dImmediateContext; // 设备上下文 std::vector> mConstantBuffers; // 常量缓冲区};#endifBasic::InitAll方法现在需要初始化一堆着色器、输入布局和常量缓冲区,并绑定常量缓冲区到默认渲染管线:bool BasicFX::InitAll(ComPtrdevice){ if (!device) return false; ComPtrblob; // 创建顶点着色器和顶点布局 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。因为后续的方法处理的图元不同,在调用开始就得设置回正确的图元。也请确保输入装配阶段提供好需要分裂的三角形顶点。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类的变化该项目包含上面三种实战内容,需要用户去指定当前播放的模式。首先声明部分变化如下: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; // 该缓冲区存放每帧更新的变量 CBChangesOnResize mCBOnReSize; // 该缓冲区存放仅在窗口大小变化时更新的变量 CBNeverChange mCBNeverChange; // 该缓冲区存放不会再进行修改的变量};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) } }; // 设置顶点缓冲区描述 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方法--重设圆线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方法变化每次切换需要记得重新设置顶点缓冲区,重新将顶点集绑定到输入,并重设渲染类型。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的变化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.学习编号:久伴。感谢大家支持!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容