DirectX11 With Windows SDK--16 流输出阶段

前言

在上一章,我们知道了如何使用几何着色器来重新组装图元,比如从一个三角形分裂成三个三角形。但是为了实现更高阶的分形,我们必须要从几何着色器拿到输出的顶点。这里我们可以使用可选的流输出阶段来拿到顶点集合。

注意: 本章末尾有大量的GIF动图!

在此之前需要额外了解的章节如下:

章节回顾
15 几何着色器初探
Visual Studio图形调试器详细使用教程(编程捕获部分)

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

流输出阶段

现在我们知道GPU可以写入纹理(textures),例如深度/模板缓冲区以及后备缓冲区。当然,我们也可以通过渲染管线的流输出阶段让GPU将几何着色器输出的顶点集合写入到指定的顶点缓冲区(vertex buffer)。除此之外,我们还能够指定不进行光栅化以及后续的所有阶段,仅让顶点数据经过流输出阶段。

在几何着色器中,最多四个流输出对象可以被设置,即几何着色器的入口函数中只允许设置四个流输出对象的参数。当多个流输出对象存在时,它们必须都要为PointStream类模板,但允许模板参数不同。输出的顶点回流到顶点缓冲区后可以再次进行一遍新的渲染管线流程。

上一章也提到,几何着色器的单次调用不能产出超过1024个标量。因此分配给所有流输出对象的标量总和不能超过1024。比如现在我有2个流输出对象,它们的结构体相同,容纳512个标量,那最多仅允许输出2个这样的顶点来分配给这2个流输出对象。

流输出状态的配置

ID3D11DeviceContext::SOSetTargets方法--绑定流输出对应用于接收数据的顶点缓冲区

void ID3D11DeviceContext::SOSetTargets(
  UINT         NumBuffers,              // [In]顶点缓冲区数目
  ID3D11Buffer * const *ppSOTargets,    // [In]顶点缓冲区数组
  const UINT   *pOffsets                // [In]一个数组包含对每个顶点缓冲区的字节偏移量
);

该方法多允许设置4个顶点缓冲区。

每个要绑定到流输出阶段的缓冲区资源必须要在创建的时候额外设置D3D11_BIND_STREAM_OUTPUT绑定标签。

若偏移值设为-1,则会引起流输出缓冲区被追加到最后一个缓冲区的后面

顶点缓冲区绑定到流输出阶段的输出槽0操作如下:

UINT offset = 0;
m_pd3dImmediateContext->SOSetTargets(1, vertexBufferOut.GetAddressOf(), &offset);

如果我们需要恢复默认的状态,则可以这样调用:

ID3D11Buffer* nullBuffer = nullptr;
UINT offset = 0;
m_pd3dImmediateContext->SOSetTargets(1, &nullBuffer, &offset);

注意: 如果使用的是当前绑定到输入装配阶段的顶点缓冲区,则绑定会失效。因为顶点缓冲区不可以同时被绑定到输入装配阶段和流输出阶段。

因为后续我们是将每一阶输出的顶点都保存下来,即便不需要交换顶点缓冲区,但也有可能出现同时绑定输入/输出的情况。一种合理的绑定顺序如下:

// 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
UINT stride = sizeof(VertexPosColor);
UINT offset = 0;
ID3D11Buffer * nullBuffer = nullptr;
m_pd3dImmediateContext->SOSetTargets(1, &nullBuffer, &offset);
// ...
m_pd3dImmediateContext->IASetInputLayout(mVertexPosColorLayout.Get());
// ...
m_pd3dImmediateContext->SOSetTargets(1, vertexBufferOut.GetAddressOf(), &offset);

当渲染管线完成一次流输出后,我们就可以用下面的方法来获取绑定在流输出阶段上的顶点缓冲区(当然你本身持有该缓冲区的指针的话就不需要了)

ID3D11DeviceContext::SOGetTargets方法--获取绑定在流输出阶段的顶点缓冲区

void ID3D11DeviceContext::SOGetTargets(
  UINT         NumBuffers,          // [In]缓冲区数目
  ID3D11Buffer **ppSOTargets        // [Out]获取绑定流输出阶段的顶点缓冲区
);

输出的顶点缓冲区引用数会加1,最好是能够使用ComPtr来承接顶点缓冲区,否则就要在结束的时候手工调用Release方法,若忘记调用则会引发内存泄漏。

ID3D11Device::CreateGeometryShaderWithStreamOutput方法--创建带流输出阶段的几何着色器

接下来我们需要指定数据会流向哪个输出槽,首先我们需要填充结构体D3D11_SO_DECLARATION_ENTRY ,结构体声明如下:

typedef struct D3D11_SO_DECLARATION_ENTRY {
  UINT   Stream;            // 输出流索引,从0开始
  LPCSTR SemanticName;      // 语义名
  UINT   SemanticIndex;     // 语义索引
  BYTE   StartComponent;    // 从第几个分量(xyzw)开始,只能取0-3
  BYTE   ComponentCount;    // 分量的输出数目,只能取1-4
  BYTE   OutputSlot;        // 输出槽索引,只能取0-3
};

其中,语义名SemanticName用于指定在几何着色器的流输出对象对应的结构体中该语义描述的成员,然后用语义索引SemanticIndex指定存在同名语义下用索引值标记的唯一成员。

然后StartComponentComponentCount用于控制该向量需要输出哪些分量。若StartComponent为1,ComponentCount为2,则输出的分量为(y, z),而要输出全部分量,则指定StartCompnent为0, ComponentCount为4.

输出槽索引OutputSlot用于指定选择绑定流输出的缓冲区数组中的某一元素。

由于这里一个结构体只能指定某个输出流中的某一向量,所以通常我们需要像顶点输入布局那样传递一个数组来取出组合成特定顶点。

比如说现在顶点着色器输入的顶点和流输出的顶点是一致的:

struct VertexPosColor
{
    DirectX::XMFLOAT3 pos;
    DirectX::XMFLOAT4 color;
    static const D3D11_INPUT_ELEMENT_DESC inputLayout[2];
};

输入布局描述如下:

const D3D11_INPUT_ELEMENT_DESC VertexPosColor::inputLayout[2] = {
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};

HLSL中的结构体如下:

struct VertexPosColor
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};

流输出的入口描述如下:

const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
    { 0, "POSITION", 0, 0, 3, 0 },
    { 0, "COLOR", 0, 0, 4, 0 }
};

这里对应的是索引为0的流输出对象,输出给绑定在索引为0的输出槽的顶点缓冲区,先输出语义为POSITION的向量中的xyz分量,然后输出COLOR整个向量。这样一个输出的顶点就和原来的顶点一致了。

接下来给出ID3D11Device::CreateGeometryShaderWithStreamOutput方法的原型:

HRESULT ID3D11Device::CreateGeometryShaderWithStreamOutput(
  const void                       *pShaderBytecode,    // [In]编译好的着色器字节码
  SIZE_T                           BytecodeLength,      // [In]字节码长度
  const D3D11_SO_DECLARATION_ENTRY *pSODeclaration,     // [In]D3D11_SO_DECLARATION_ENTRY的数组
  UINT                             NumEntries,          // [In]入口总数
  const UINT                       *pBufferStrides,     // [In]一个数组包含了每个绑定到流输出的缓冲区中顶点字节大小
  UINT                             NumStrides,          // [In]上面数组的元素数目
  UINT                             RasterizedStream,    // [In]按索引指定哪个流输出对象用于传递到光栅化阶段
  ID3D11ClassLinkage               *pClassLinkage,      // [In]忽略
  ID3D11GeometryShader             **ppGeometryShader   // [Out]创建好的几何着色器
);

如果不需要有流输出对象提供数据给光栅化阶段,则RasterizedStream应当指定为D3D11_SO_NO_RASTERIZED_STREAM。即便某一流输出对象传递了数据给光栅化阶段,它仍可以提供数据给某一绑定的缓冲区。

下面是一个调用的例子:

const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
    { 0, "POSITION", 0, 0, 3, 0 },
    { 0, "COLOR", 0, 0, 4, 0 }
};

HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
    &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, m_pTriangleSOGS.GetAddressOf()));

当该着色器被绑定到渲染管线上,流输出阶段就会被激活,我们可以使用ID3D11DeviceContext::Draw方法来进行绘制。然后当渲染管线开始执行的时候,任何传递给几何着色器中的流输出对象的数据,都会基于语义名和语义索引尝试匹配输出布局。一旦发现有匹配的语义,该数据就会流向对应的缓冲区来创建完整的输出顶点集。

绘制各种酷炫的分形

由于现在的着色器多到令人发指,而且有没有很好的办法归类整合,故在下面用一张表列出所有绘制流程用到的着色器hlsl文件名称:

操作 VS GS PS
通过流输出得到分裂的三角形 TriangleSO_VS TriangleSO_GS X
通过流输出得到分形雪花 SnowSO_VS SnowSO_GS X
通过流输出得到分形球体 SphereSO_VS SphereSO_GS X
绘制分形三角形 Triangle_VS X Triangle_PS
绘制分形雪花 Snow_VS X Snow_PS
绘制分形球体 Sphere_VS X Sphere_PS
绘制法向量 Normal_VS Normal_GS Normal_PS

首先给出Basic.hlsli文件的内容,要注意里面的常量缓冲区和之前有所变化:

#include "LightHelper.hlsli"

cbuffer CBChangesEveryFrame : register(b0)
{
    matrix g_World;
    matrix g_WorldInvTranspose;
}

cbuffer CBChangesOnResize : register(b1)
{
    matrix g_Proj;
}

cbuffer CBChangesRarely : register(b2)
{
    DirectionalLight g_DirLight[5];
    PointLight g_PointLight[5];
    SpotLight g_SpotLight[5];
    Material g_Material;
    matrix g_View;
    float3 g_SphereCenter;
    float g_SphereRadius;
    float3 g_EyePosW;
    float g_Pad;
}


struct VertexPosColor
{
    float3 PosL : POSITION;
    float4 Color : COLOR;
};

struct VertexPosHColor
{
    float4 PosH : SV_POSITION;
    float4 Color : COLOR;
};

struct VertexPosHLColor
{
    float4 PosH : SV_POSITION;
    float3 PosL : 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: 绘制分形三角形

通过流输出阶段,一个三角形就分裂出了三个三角形,顶点的数目翻了3倍。若规定1阶分形三角形的顶点数为3,则N阶分形三角形的顶点数为\(3^{N}\)

HLSL代码

首先是TriangleSO_VS.hlsl,它负责将顶点直接传递给几何着色器。

// TriangleSO_VS.hlsl
#include "Basic.hlsli"

VertexPosColor VS(VertexPosColor vIn)
{
    return vIn;
}

然后和上一章一样,TriangleSO_GS.hlsl中的几何着色器将一个三角形分裂成三个三角形,并且输出的顶点类型和输入的顶点是一致的。

// TriangleSO_GS.hlsl
#include "Basic.hlsli"

[maxvertexcount(9)]
void GS(triangle VertexPosColor input[3], inout TriangleStream<VertexPosColor> output)
{
    //
    // 将一个三角形分裂成三个三角形,即没有v3v4v5的三角形
    //       v1
    //       /\
    //      /  \
    //   v3/____\v4
    //    /\xxxx/\
    //   /  \xx/  \
    //  /____\/____\
    // v0    v5    v2


    VertexPosColor 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].PosL = (input[i].PosL + input[(i + 1) % 3].PosL) / 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_VS.hlslTriangle_PS.hlsl则是常规的三角形绘制:

// Triangle_VS.hlsl
#include "Basic.hlsli"

VertexPosHColor VS(VertexPosColor vIn)
{
    matrix worldViewProj = mul(mul(g_World, g_View), g_Proj);
    VertexPosHColor vOut;
    vOut.Color = vIn.Color;
    vOut.PosH = mul(float4(vIn.PosL, 1.0f), worldViewProj);
    return vOut;
}


// Triangle_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHColor pIn) : SV_Target
{
    return pIn.Color;
}


实战2: 绘制分形雪花

现在规定第一张图为一阶分形雪花,第二张为二阶分形雪花。观察二者之间的变化,可以发现前者的每一条直线变成了四条折线。其中每个尖锐角的度数都在60度,并且每条边的长度都应该是一致的。

HLSL代码

和之前一样,SnowSO_VS.hlsl中的顶点着色器阶段只用于顶点直传:

// SnowSO_VS.hlsl
#include "Basic.hlsli"

VertexPosNormalColor VS(VertexPosNormalColor vIn)
{
    return vIn;
}

然后重点就在于SnowSO_GS.hlsl的几何着色器了。这里先放出代码:

// SnowSO_GS.hlsl
#include "Basic.hlsli"

[maxvertexcount(5)]
void GS(line VertexPosColor input[2], inout LineStream<VertexPosColor> output)
{
    // 要求分形线段按顺时针排布
    // z分量必须相等,因为顶点没有提供法向量无法判断垂直上方向
    //                       v1
    //                       /\
    // ____________ =>  ____/  \____
    // i0         i1   i0  v0  v2  i1
    
    VertexPosColor v0, v1, v2;
    v0.Color = lerp(input[0].Color, input[1].Color, 0.25f);
    v1.Color = lerp(input[0].Color, input[1].Color, 0.5f);
    v2.Color = lerp(input[0].Color, input[1].Color, 0.75f);

    v0.PosL = lerp(input[0].PosL, input[1].PosL, 1.0f / 3.0f);
    v2.PosL = lerp(input[0].PosL, input[1].PosL, 2.0f / 3.0f);

    // xy平面求出它的垂直单位向量
    //     
    //     |
    // ____|_____
    float2 upDir = normalize(input[1].PosL - input[0].PosL).yx;
    float len = length(input[1].PosL.xy - input[0].PosL.xy);
    upDir.x = -upDir.x;

    v1.PosL = lerp(input[0].PosL, input[1].PosL, 0.5f);
    v1.PosL.xy += sqrt(3) / 6.0f * len * upDir;

    output.Append(input[0]);
    output.Append(v0);
    output.Append(v1);
    output.Append(v2);
    output.Append(input[1]);

}

可以发现分形雪花每升一阶,需要绘制的顶点数就变成了上一阶的4倍。

这里要求了z分量必须相等,因为使用的着色器仍把一切的顶点仍当做3D顶点来对待出来(当然你也可以写成2D的着色器)。

然后开始具体分析从直线变折线的过程,可以看到因为顶点v1所在角的度数在60度,且v0, v1, v2构成等边三角形,故v0v2,
v0v1和v1v2的边长是一致的。而且4条折线要求边长相等,故这里的i0v0和v2i1应当各占线段i0i1的1/3.

其中lerp函数是线性插值函数,数学公式如下:

\[\mathbf{p} = \mathbf{p}_0 + t(\mathbf{p}_1 - \mathbf{p}_0) \]

其中t的取值范围在[0.0f, 1.0f],并且操作对象p0和p1可以是标量,也可以是矢量,对矢量来说则是对每个分量都进行线性插值。

当t = 0.5f时,描述的就是p0和p1的中值或中点。

该函数很容易描述两点之间某一相对位置。

由于我们规定了连续线段必须按顺时针排布,我们就可以利用向量i0i1逆时针旋转90度得到对应的突出方向向量,然后标准化,乘上相应的高度值即可得到顶点v1的位置。

最后就是用于绘制的着色器代码:

// Snow_VS.hlsl
#include "Basic.hlsli"

VertexPosHColor VS(VertexPosColor vIn)
{
    matrix worldViewProj = mul(mul(g_World, g_View), g_Proj);
    VertexPosHColor vOut;
    vOut.Color = vIn.Color;
    vOut.PosH = mul(float4(vIn.PosL, 1.0f), worldViewProj);
    return vOut;
}


// Snow_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHColor pIn) : SV_Target
{
    return pIn.Color;
}


实战3: 绘制分形圆球

以下是一阶和二阶的分形圆球:

仔细观察可以看到,原先的一个三角形分裂出了四个三角形,即每升一阶,需要绘制的顶点数就变成了上一阶的4倍。

HLSL代码

SphereSO_VS.hlsl代码和SphereSO_GS.hlsl代码如下:

// SphereSO_VS.hlsl
#include "Basic.hlsli"

VertexPosNormalColor VS(VertexPosNormalColor vIn)
{
    return vIn;
}

// SphereSO_GS.hlsl

#include "Basic.hlsli"

[maxvertexcount(12)]
void GS(triangle VertexPosNormalColor input[3], inout TriangleStream<VertexPosNormalColor> output)
{
    //
    // 将一个三角形分裂成四个三角形,但同时顶点v3, v4, v5也需要在球面上
    //       v1
    //       /\
    //      /  \
    //   v3/____\v4
    //    /\xxxx/\
    //   /  \xx/  \
    //  /____\/____\
    // v0    v5    v2
    
    VertexPosNormalColor vertexes[6];

    matrix viewProj = mul(g_View, g_Proj);

    [unroll]
    for (int i = 0; i < 3; ++i)
    {
        vertexes[i] = input[i];
        vertexes[i + 3].Color = lerp(input[i].Color, input[(i + 1) % 3].Color, 0.5f);
        vertexes[i + 3].NormalL = normalize(input[i].NormalL + input[(i + 1) % 3].NormalL);
        vertexes[i + 3].PosL = g_SphereCenter + g_SphereRadius * vertexes[i + 3].NormalL;
    }
        
    output.Append(vertexes[0]);
    output.Append(vertexes[3]);
    output.Append(vertexes[5]);
    output.RestartStrip();

    output.Append(vertexes[3]);
    output.Append(vertexes[4]);
    output.Append(vertexes[5]);
    output.RestartStrip();

    output.Append(vertexes[5]);
    output.Append(vertexes[4]);
    output.Append(vertexes[2]);
    output.RestartStrip();

    output.Append(vertexes[3]);
    output.Append(vertexes[1]);
    output.Append(vertexes[4]);
}

由于v3, v4, v5也需要在球面上,我们还需要额外知道球的半径和球心位置。虽然说通过三角形三个顶点位置和法向量可以算出圆心和半径,但直接从常量缓冲区提供这两个信息会更方便一些。

要计算诸如v3顶点所在位置,我们可以先求出它的法向量,将v0和v1的法向量相加取其单位向量即为v3的法向量,然后从圆心开始加上半径长度的法向量即可得到顶点v3的位置。

剩下绘制圆的着色器代码如下:

// Sphere_VS.hlsl
#include "Basic.hlsli"

VertexPosHWNormalColor VS(VertexPosNormalColor vIn)
{
    VertexPosHWNormalColor vOut;
    matrix viewProj = mul(g_View, g_Proj);
    float4 posW = mul(float4(vIn.PosL, 1.0f), g_World);

    vOut.PosH = mul(posW, viewProj);
    vOut.PosW = posW.xyz;
    vOut.NormalW = mul(vIn.NormalL, (float3x3) g_WorldInvTranspose);
    vOut.Color = vIn.Color;
    return vOut;
}

// Sphere_PS.hlsl
#include "Basic.hlsli"

float4 PS(VertexPosHWNormalColor pIn) : SV_Target
{
    // 标准化法向量
    pIn.NormalW = normalize(pIn.NormalW);

    // 顶点指向眼睛的向量
    float3 toEyeW = normalize(g_EyePosW - 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(g_Material, g_DirLight[0], pIn.NormalW, toEyeW, ambient, diffuse, spec);

    return pIn.Color * (ambient + diffuse) + spec;
}


C++代码的变化

BasicEffect::InitAll方法的变化

现在着色器的创建按绘制类别进行分组:

bool BasicEffect::InitAll(ID3D11Device * device)
{
    if (!device)
        return false;

    if (!pImpl->m_pCBuffers.empty())
        return true;

    if (!RenderStates::IsInit())
        throw std::exception("RenderStates need to be initialized first!");

    const D3D11_SO_DECLARATION_ENTRY posColorLayout[2] = {
        { 0, "POSITION", 0, 0, 3, 0 },
        { 0, "COLOR", 0, 0, 4, 0 }
    };

    const D3D11_SO_DECLARATION_ENTRY posNormalColorLayout[3] = {
        { 0, "POSITION", 0, 0, 3, 0 },
        { 0, "NORMAL", 0, 0, 3, 0 },
        { 0, "COLOR", 0, 0, 4, 0 }
    };

    UINT stridePosColor = sizeof(VertexPosColor);
    UINT stridePosNormalColor = sizeof(VertexPosNormalColor);

    ComPtr<ID3DBlob> blob;

    // ******************
    // 流输出分裂三角形
    //
    HR(CreateShaderFromFile(L"HLSL\\TriangleSO_VS.cso", L"HLSL\\TriangleSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTriangleSOVS.GetAddressOf()));
    // 创建顶点输入布局
    HR(device->CreateInputLayout(VertexPosColor::inputLayout, ARRAYSIZE(VertexPosColor::inputLayout), blob->GetBufferPointer(),
        blob->GetBufferSize(), pImpl->m_pVertexPosColorLayout.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\TriangleSO_GS.cso", L"HLSL\\TriangleSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
        &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pTriangleSOGS.GetAddressOf()));

    // ******************
    // 绘制分形三角形
    //
    HR(CreateShaderFromFile(L"HLSL\\Triangle_VS.cso", L"HLSL\\Triangle_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTriangleVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Triangle_PS.cso", L"HLSL\\Triangle_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pTrianglePS.GetAddressOf()));


    // ******************
    // 流输出分形球体
    //
    HR(CreateShaderFromFile(L"HLSL\\SphereSO_VS.cso", L"HLSL\\SphereSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSphereSOVS.GetAddressOf()));
    // 创建顶点输入布局
    HR(device->CreateInputLayout(VertexPosNormalColor::inputLayout, ARRAYSIZE(VertexPosNormalColor::inputLayout), blob->GetBufferPointer(),
        blob->GetBufferSize(), pImpl->m_pVertexPosNormalColorLayout.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\SphereSO_GS.cso", L"HLSL\\SphereSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posNormalColorLayout, ARRAYSIZE(posNormalColorLayout),
        &stridePosNormalColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pSphereSOGS.GetAddressOf()));

    // ******************
    // 绘制球体
    //
    HR(CreateShaderFromFile(L"HLSL\\Sphere_VS.cso", L"HLSL\\Sphere_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSphereVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Sphere_PS.cso", L"HLSL\\Sphere_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSpherePS.GetAddressOf()));


    // ******************
    // 流输出分形雪花
    //
    HR(CreateShaderFromFile(L"HLSL\\SnowSO_VS.cso", L"HLSL\\SnowSO_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowSOVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\SnowSO_GS.cso", L"HLSL\\SnowSO_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShaderWithStreamOutput(blob->GetBufferPointer(), blob->GetBufferSize(), posColorLayout, ARRAYSIZE(posColorLayout),
        &stridePosColor, 1, D3D11_SO_NO_RASTERIZED_STREAM, nullptr, pImpl->m_pSnowSOGS.GetAddressOf()));

    // ******************
    // 绘制雪花
    //
    HR(CreateShaderFromFile(L"HLSL\\Snow_VS.cso", L"HLSL\\Snow_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Snow_PS.cso", L"HLSL\\Snow_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pSnowPS.GetAddressOf()));


    // ******************
    // 绘制法向量
    //
    HR(CreateShaderFromFile(L"HLSL\\Normal_VS.cso", L"HLSL\\Normal_VS.hlsl", "VS", "vs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalVS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Normal_GS.cso", L"HLSL\\Normal_GS.hlsl", "GS", "gs_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreateGeometryShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalGS.GetAddressOf()));
    HR(CreateShaderFromFile(L"HLSL\\Normal_PS.cso", L"HLSL\\Normal_PS.hlsl", "PS", "ps_5_0", blob.ReleaseAndGetAddressOf()));
    HR(device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, pImpl->m_pNormalPS.GetAddressOf()));



    pImpl->m_pCBuffers.assign({
        &pImpl->m_CBFrame, 
        &pImpl->m_CBOnResize, 
        &pImpl->m_CBRarely});

    // 创建常量缓冲区
    for (auto& pBuffer : pImpl->m_pCBuffers)
    {
        HR(pBuffer->CreateBuffer(device));
    }

    return true;
}

BasicEffect::SetRenderSplitedTriangle方法--绘制分形三角形

由于新增了流输出的阶段,这里开始接下来的每一个用于绘制的方法都需要把流输出绑定的顶点缓冲区都解除绑定。

void BasicEffect::SetRenderSplitedTriangle(ID3D11DeviceContext * deviceContext)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pTriangleVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pTrianglePS.Get(), nullptr, 0);
}

BasicEffect::SetRenderSplitedSnow方法--绘制分形雪花

void BasicEffect::SetRenderSplitedSnow(ID3D11DeviceContext * deviceContext)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pSnowVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pSnowPS.Get(), nullptr, 0);
}

BasicEffect::SetRenderSplitedSphere方法--绘制分形球体

void BasicEffect::SetRenderSplitedSphere(ID3D11DeviceContext * deviceContext)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer* nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get());
    deviceContext->VSSetShader(pImpl->m_pSphereVS.Get(), nullptr, 0);

    deviceContext->GSSetShader(nullptr, nullptr, 0);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(pImpl->m_pSpherePS.Get(), nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedTriangle方法--经过流输出保存下一阶分形三角形的顶点

为了简化设置,这里还需要提供额外的输入缓冲区和输出缓冲区。为了防止出现顶点缓冲区同时被绑定到输入装配和流输出阶段的情况,需要先清空流输出绑定的顶点缓冲区,然后将用于输入的顶点缓冲区绑定到输入装配阶段,最后才是把输出的顶点缓冲区绑定到流输出阶段。

void BasicEffect::SetStreamOutputSplitedTriangle(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetInputLayout(nullptr);
    deviceContext->SOSetTargets(0, nullptr, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());

    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pTriangleSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pTriangleSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedSnow方法--经过流输出保存下一阶分形雪花的顶点

注意这里是用LineList而不是LineStrip方式。

void BasicEffect::SetStreamOutputSplitedSnow(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosColorLayout.Get());
    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pSnowSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pSnowSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}

BasicEffect::SetStreamOutputSplitedSphere方法--经过流输出保存下一阶分形球体的顶点

void BasicEffect::SetStreamOutputSplitedSphere(ID3D11DeviceContext * deviceContext, ID3D11Buffer * vertexBufferIn, ID3D11Buffer * vertexBufferOut)
{
    // 先恢复流输出默认设置,防止顶点缓冲区同时绑定在输入和输出阶段
    UINT stride = sizeof(VertexPosNormalColor);
    UINT offset = 0;
    ID3D11Buffer * nullBuffer = nullptr;
    deviceContext->SOSetTargets(1, &nullBuffer, &offset);

    deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    deviceContext->IASetInputLayout(pImpl->m_pVertexPosNormalColorLayout.Get());

    deviceContext->IASetVertexBuffers(0, 1, &vertexBufferIn, &stride, &offset);

    deviceContext->VSSetShader(pImpl->m_pSphereSOVS.Get(), nullptr, 0);
    deviceContext->GSSetShader(pImpl->m_pSphereSOGS.Get(), nullptr, 0);

    deviceContext->SOSetTargets(1, &vertexBufferOut, &offset);

    deviceContext->RSSetState(nullptr);
    deviceContext->PSSetShader(nullptr, nullptr, 0);

}


GameApp类的变化

ID3D11DeviceContext::DrawAuto方法--绘制未知数目的图元

时隔多月,是时候请回该方法了。

这是一个唯一不需要形参就能绘制的API,它可以根据输入装配阶段绑定的缓冲区(内部存有图元数目的记录)自动进行绘制,它会经过顶点着色阶段并一直到流输出阶段。它可能会继续经过光栅化阶段到输出合并阶段,也可以不经过。

但是它的调用要求如下:

  1. 在输入装配阶段下,输入槽0需要绑定顶点缓冲区
  2. 绑定到输入槽0的顶点缓冲区需要设置绑定标签D3D11_BIND_VERTEX_BUFFERD3D11_BIND_STREAM_OUTPUT
  3. 作为流输出的缓冲区也需要设置上述同样的两个绑定标签
  4. 绑定到输入槽0的顶点缓冲区,需要存在图元数目的内部记录

关于最后一点,一般的顶点缓冲区是不会存在内部记录的。通常要求第一次流输出绘制时使用DrawDrawIndexed系列的方法,而不是DrawAuto来绘制,这样流输出缓冲区在产生运行结果的同时,其内部还会产生图元数目的记录。这样在后续的调用中,你就可以将该流输出缓冲区作为输入,然后提供另一个流输出缓冲区作为输出,最后调用DrawAuto来正确绘制了。

GameApp::ResetSplitedTriangle方法--重新建立包含1-7阶的分形三角形顶点的缓冲区

首先我们只需要给1阶的顶点缓冲区使用指定三角形的三个顶点,然后后续阶数的顶点缓冲区就根据上一阶产出的顶点缓冲区进行"绘制"。经过6次Draw方法的调用后,里面的7个顶点缓冲区都应该被初始化完毕,后续绘制的时候只需要直接绑定某一个顶点缓冲区到输入即可。

注意顶点缓冲区在创建的时候一定要加上D3D11_BIND_STREAM_OUTPUT标签。

void GameApp::ResetSplitedTriangle()
{
    // ******************
    // 初始化三角形
    //

    // 设置三角形顶点
    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;    // 这里需要允许流输出阶段通过GPU写入
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 需要额外添加流输出标签
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

    // 三角形顶点数
    m_InitVertexCounts = 3;
    // 初始化所有顶点缓冲区
    for (int i = 1; i < 7; ++i)
    {
    vbd.ByteWidth *= 3;
    HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
    m_BasicEffect.SetStreamOutputSplitedTriangle(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
    // 第一次绘制需要调用一般绘制指令,之后就可以使用DrawAuto了
    if (i == 1)
    {
    m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
    }
    else
    {
    m_pd3dImmediateContext->DrawAuto();
    }

    }
}

GameApp::ResetSplitedSnow方法--重新建立包含1-7阶的分形雪花顶点的缓冲区

由于绘制方式统一用LineList,初始阶段应当提供3条线段的6个顶点,虽然说每个顶点都被重复使用了2次。

void GameApp::ResetSplitedSnow()
{
    // ******************
    // 雪花分形从初始化三角形开始,需要6个顶点
    //

    // 设置三角形顶点
    float sqrt3 = sqrt(3.0f);
    VertexPosColor vertices[] =
    {
    { XMFLOAT3(-3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.0f, sqrt3 / 2, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.0f, sqrt3 / 2, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(-3.0f / 4, -sqrt3 / 4, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) }
    };
    // 将三角形宽度和高度都放大3倍
    for (VertexPosColor& v : vertices)
    {
    v.pos.x *= 3;
    v.pos.y *= 3;
    }

    // 设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;    // 这里需要允许流输出阶段通过GPU写入
    vbd.ByteWidth = sizeof vertices;
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 需要额外添加流输出标签
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices;
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

    // 顶点数
    m_InitVertexCounts = 6;
    // 初始化所有顶点缓冲区
    for (int i = 1; i < 7; ++i)
    {
    vbd.ByteWidth *= 4;
    HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
    m_BasicEffect.SetStreamOutputSplitedSnow(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
    // 第一次绘制需要调用一般绘制指令,之后就可以使用DrawAuto了
    if (i == 1)
    {
    m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
    }
    else
    {
    m_pd3dImmediateContext->DrawAuto();
    }
    }
}

GameApp::ResetSplitedSphere方法--重新建立包含1-7阶的分形圆球顶点的缓冲区

这里不使用Geometry类来构造一阶圆球,而是仅提供与外接正方体相交的六个顶点,包含八个三角形对应的24个顶点。

void GameApp::ResetSplitedSphere()
{
    // ******************
    // 分形球体
    //

    VertexPosNormalColor basePoint[] = {
    { XMFLOAT3(0.0f, 2.0f, 0.0f), XMFLOAT3(0.0f, 1.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(2.0f, 0.0f, 0.0f), XMFLOAT3(1.0f, 0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.0f, 0.0f, 2.0f), XMFLOAT3(0.0f, 0.0f, 1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(-2.0f, 0.0f, 0.0f), XMFLOAT3(-1.0f, 0.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.0f, 0.0f, -2.0f), XMFLOAT3(0.0f, 0.0f, -1.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    { XMFLOAT3(0.0f, -2.0f, 0.0f), XMFLOAT3(0.0f, -1.0f, 0.0f), XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f) },
    };
    int indices[] = { 0, 2, 1, 0, 3, 2, 0, 4, 3, 0, 1, 4, 1, 2, 5, 2, 3, 5, 3, 4, 5, 4, 1, 5 };

    std::vector<VertexPosNormalColor> vertices;
    for (int pos : indices)
    {
    vertices.push_back(basePoint[pos]);
    }


    // 设置顶点缓冲区描述
    D3D11_BUFFER_DESC vbd;
    ZeroMemory(&vbd, sizeof(vbd));
    vbd.Usage = D3D11_USAGE_DEFAULT;    // 这里需要允许流输出阶段通过GPU写入
    vbd.ByteWidth = (UINT)(vertices.size() * sizeof(VertexPosNormalColor));
    vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER | D3D11_BIND_STREAM_OUTPUT;    // 需要额外添加流输出标签
    vbd.CPUAccessFlags = 0;
    // 新建顶点缓冲区
    D3D11_SUBRESOURCE_DATA InitData;
    ZeroMemory(&InitData, sizeof(InitData));
    InitData.pSysMem = vertices.data();
    HR(m_pd3dDevice->CreateBuffer(&vbd, &InitData, m_pVertexBuffers[0].ReleaseAndGetAddressOf()));

//#if defined(DEBUG) | defined(_DEBUG)
//    ComPtr<IDXGraphicsAnalysis> graphicsAnalysis;
//    HR(DXGIGetDebugInterface1(0, __uuidof(graphicsAnalysis.Get()), reinterpret_cast<void**>(graphicsAnalysis.GetAddressOf())));
//    graphicsAnalysis->BeginCapture();
//#endif

    // 顶点数
    m_InitVertexCounts = 24;
    // 初始化所有顶点缓冲区
    for (int i = 1; i < 7; ++i)
    {
    vbd.ByteWidth *= 4;
    HR(m_pd3dDevice->CreateBuffer(&vbd, nullptr, m_pVertexBuffers[i].ReleaseAndGetAddressOf()));
    m_BasicEffect.SetStreamOutputSplitedSphere(m_pd3dImmediateContext.Get(), m_pVertexBuffers[i - 1].Get(), m_pVertexBuffers[i].Get());
    // 第一次绘制需要调用一般绘制指令,之后就可以使用DrawAuto了
    if (i == 1)
    {
    m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
    }
    else
    {
    m_pd3dImmediateContext->DrawAuto();
    }
    }
}

GameApp::DrawScene方法的变化

同理,在进行正常绘制的时候,由于索引1到6的顶点缓冲区内部都记录了图元数目,也可以直接用DrawAuto方法绘制到屏幕上。

void GameApp::DrawScene()
{
    assert(m_pd3dImmediateContext);
    assert(m_pSwapChain);


    m_pd3dImmediateContext->ClearRenderTargetView(m_pRenderTargetView.Get(), reinterpret_cast<const float*>(&Colors::Black));
    m_pd3dImmediateContext->ClearDepthStencilView(m_pDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);


    // 根据当前绘制模式设置需要用于渲染的各项资源
    if (m_ShowMode == Mode::SplitedTriangle)
    {
        m_BasicEffect.SetRenderSplitedTriangle(m_pd3dImmediateContext.Get());
    }
    else if (m_ShowMode == Mode::SplitedSnow)
    {
        m_BasicEffect.SetRenderSplitedSnow(m_pd3dImmediateContext.Get());
    }
    else if (m_ShowMode == Mode::SplitedSphere)
    {
        m_BasicEffect.SetRenderSplitedSphere(m_pd3dImmediateContext.Get());
    }

    // 设置线框/面模式
    if (m_IsWireFrame)
    {
        m_pd3dImmediateContext->RSSetState(RenderStates::RSWireframe.Get());
    }
    else
    {
        m_pd3dImmediateContext->RSSetState(nullptr);
    }

    // 应用常量缓冲区的变更
    m_BasicEffect.Apply(m_pd3dImmediateContext.Get());
    // 除了索引为0的缓冲区缺少内部图元数目记录,其余都可以使用DrawAuto方法
    if (m_CurrIndex == 0)
    {
        m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
    }
    else
    {
        m_pd3dImmediateContext->DrawAuto();
    }
        
    // 绘制法向量
    if (m_ShowNormal)
    {
        m_BasicEffect.SetRenderNormal(m_pd3dImmediateContext.Get());
        m_BasicEffect.Apply(m_pd3dImmediateContext.Get());
        // 除了索引为0的缓冲区缺少内部图元数目记录,其余都可以使用DrawAuto方法
        if (m_CurrIndex == 0)
        {
            m_pd3dImmediateContext->Draw(m_InitVertexCounts, 0);
        }
        else
        {
            m_pd3dImmediateContext->DrawAuto();
        }
    }


    // ******************
    // 绘制Direct2D部分
    //
    if (m_pd2dRenderTarget != nullptr)
    {
        m_pd2dRenderTarget->BeginDraw();
        std::wstring text = L"切换分形:Q-三角形(面/线框) W-雪花(线框) E-球(面/线框)\n"
            L"主键盘数字1 - 7:分形阶数,越高越精细\n"
            L"M-面/线框切换\n\n"
            L"当前阶数: " + std::to_wstring(m_CurrIndex + 1) + L"\n"
            "当前分形: ";
        if (m_ShowMode == Mode::SplitedTriangle)
            text += L"三角形";
        else if (m_ShowMode == Mode::SplitedSnow)
            text += L"雪花";
        else
            text += L"球";

        if (m_IsWireFrame)
            text += L"(线框)";
        else
            text += L"(面)";

        if (m_ShowMode == Mode::SplitedSphere)
        {
            if (m_ShowNormal)
                text += L"(N-关闭法向量)";
            else
                text += L"(N-开启法向量)";
        }



        m_pd2dRenderTarget->DrawTextW(text.c_str(), (UINT32)text.length(), m_pTextFormat.Get(),
            D2D1_RECT_F{ 0.0f, 0.0f, 600.0f, 200.0f }, m_pColorBrush.Get());
        HR(m_pd2dRenderTarget->EndDraw());
    }

    HR(m_pSwapChain->Present(0, 0));

}

现在来看一下动图感受一些这些酷炫的效果吧:

分形三角形绘制效果

分形雪花绘制效果

分形圆球绘制效果

由于文件大小限制,这里分成两个部分:

下面是带法向量的:

遗留问题

该项目使用图形调试器并退出的时候,会引发内存泄漏,而具体的泄漏对象估计是ID3D11Query,然而我也没有办法直接拿到该接口对象来释放。

DirectX11 With Windows SDK完整目录

Github项目源码

欢迎加入QQ群: 727623616 可以一起探讨DX11,以及有什么问题也可以在这里汇报。

posted @ 2018-08-11 22:32  X_Jun  阅读(3076)  评论(1编辑  收藏  举报
levels of contents