创建着色器和绘图基元

在这里,我们将演示如何使用 HLSL 源文件来编译和创建着色器,然后可用于在显示器上绘制基元。

我们使用顶点和像素着色器创建和绘制黄色三角形。 创建 Direct3D 设备、交换链和呈现目标视图后,我们从磁盘上的二进制着色器对象文件读取数据。

目标: 创建着色器和绘制基元。

先决条件

我们假设你熟悉C++。 你还需要图形编程概念的基本经验。

我们还假设你已完成 快速入门:设置 DirectX 资源并显示图像

完成时间: 20 分钟。

说明书

1. 编译 HLSL 源文件

Microsoft Visual Studio 使用 fxc.exe HLSL 代码编译器将 .hlsl 源文件(SimpleVertexShader.hlsl 和 SimplePixelShader.hlsl)编译为 .cso 二进制着色器对象文件(SimpleVertexShader.cso 和 SimplePixelShader.cso)。 有关 HLSL 代码编译器的详细信息,请参阅 Effect-Compiler 工具。 有关编译着色器代码的详细信息,请参阅 编译着色器

下面是 SimpleVertexShader.hlsl 中的代码:

struct VertexShaderInput
{
    DirectX::XMFLOAT2 pos : POSITION;
};

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
};

PixelShaderInput SimpleVertexShader(VertexShaderInput input)
{
    PixelShaderInput vertexShaderOutput;

    // For this lesson, set the vertex depth value to 0.5, so it is guaranteed to be drawn.
    vertexShaderOutput.pos = float4(input.pos, 0.5f, 1.0f);

    return vertexShaderOutput;
}

下面是 SimplePixelShader.hlsl 中的代码:

struct PixelShaderInput
{
    float4 pos : SV_POSITION;
};

float4 SimplePixelShader(PixelShaderInput input) : SV_TARGET
{
    // Draw the entire triangle yellow.
    return float4(1.0f, 1.0f, 0.0f, 1.0f);
}

2.从磁盘读取数据

我们在 DirectX 11 应用(通用 Windows)模板中使用 DirectXHelper.h 中的 DX::ReadDataAsync 函数从磁盘上的文件异步读取数据。

3.创建顶点和像素着色器

我们从 SimpleVertexShader.cso 文件中获取数据,然后将数据分配给 顶点着色器字节码 字节数组。 我们使用字节数组调用 ID3D11Device::CreateVertexShader,以创建顶点着色器(ID3D11VertexShader)。 我们在 SimpleVertexShader.hlsl 源中将顶点深度值设置为 0.5,以确保绘制三角形。 我们填充 D3D11_INPUT_ELEMENT_DESC 结构的数组来描述顶点着色器代码的布局,然后调用 ID3D11Device::CreateInputLayout 来创建布局。 该数组有一个定义顶点位置的布局元素。 我们从 SimplePixelShader.cso 文件中读取数据,并将数据分配给 pixelShaderBytecode 字节数组。 我们使用字节数组调用 ID3D11Device::CreatePixelShader 来创建像素着色器(ID3D11PixelShader)。 我们在 SimplePixelShader.hlsl 源中将像素值设置为 (1,1,1,1),以使三角形变黄色。 可以通过更改此值来更改颜色。

我们将创建用于定义简单三角形的顶点和索引缓冲区。 为此,我们首先定义三角形,接下来使用三角形定义来定义顶点和索引缓冲区(D3D11_BUFFER_DESCD3D11_SUBRESOURCE_DATA),最后为每个缓冲区调用 ID3D11Device::CreateBuffer 一次。

        auto loadVSTask = DX::ReadDataAsync(L"SimpleVertexShader.cso");
        auto loadPSTask = DX::ReadDataAsync(L"SimplePixelShader.cso");
        
        // Load the raw vertex shader bytecode from disk and create a vertex shader with it.
        auto createVSTask = loadVSTask.then([this](const std::vector<byte>& vertexShaderBytecode) {


          ComPtr<ID3D11VertexShader> vertexShader;
          DX::ThrowIfFailed(
              m_d3dDevice->CreateVertexShader(
                  vertexShaderBytecode->Data,
                  vertexShaderBytecode->Length,
                  nullptr,
                  &vertexShader
                  )
              );

          // Create an input layout that matches the layout defined in the vertex shader code.
          // For this lesson, this is simply a DirectX::XMFLOAT2 vector defining the vertex position.
          const D3D11_INPUT_ELEMENT_DESC basicVertexLayoutDesc[] =
          {
              { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
          };

          ComPtr<ID3D11InputLayout> inputLayout;
          DX::ThrowIfFailed(
              m_d3dDevice->CreateInputLayout(
                  basicVertexLayoutDesc,
                  ARRAYSIZE(basicVertexLayoutDesc),
                  vertexShaderBytecode->Data,
                  vertexShaderBytecode->Length,
                  &inputLayout
                  )
              );
        });
        
        // Load the raw pixel shader bytecode from disk and create a pixel shader with it.
        auto createPSTask = loadPSTask.then([this](const std::vector<byte>& pixelShaderBytecode) {
          ComPtr<ID3D11PixelShader> pixelShader;
          DX::ThrowIfFailed(
              m_d3dDevice->CreatePixelShader(
                  pixelShaderBytecode->Data,
                  pixelShaderBytecode->Length,
                  nullptr,
                  &pixelShader
                  )
              );
        });

        // Create vertex and index buffers that define a simple triangle.
        auto createTriangleTask = (createPSTask && createVSTask).then([this] () {

          DirectX::XMFLOAT2 triangleVertices[] =
          {
              float2(-0.5f, -0.5f),
              float2( 0.0f,  0.5f),
              float2( 0.5f, -0.5f),
          };

          unsigned short triangleIndices[] =
          {
              0, 1, 2,
          };

          D3D11_BUFFER_DESC vertexBufferDesc = {0};
          vertexBufferDesc.ByteWidth = sizeof(float2) * ARRAYSIZE(triangleVertices);
          vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
          vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
          vertexBufferDesc.CPUAccessFlags = 0;
          vertexBufferDesc.MiscFlags = 0;
          vertexBufferDesc.StructureByteStride = 0;

          D3D11_SUBRESOURCE_DATA vertexBufferData;
          vertexBufferData.pSysMem = triangleVertices;
          vertexBufferData.SysMemPitch = 0;
          vertexBufferData.SysMemSlicePitch = 0;

          ComPtr<ID3D11Buffer> vertexBuffer;
          DX::ThrowIfFailed(
              m_d3dDevice->CreateBuffer(
                  &vertexBufferDesc,
                  &vertexBufferData,
                  &vertexBuffer
                  )
              );

          D3D11_BUFFER_DESC indexBufferDesc;
          indexBufferDesc.ByteWidth = sizeof(unsigned short) * ARRAYSIZE(triangleIndices);
          indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
          indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
          indexBufferDesc.CPUAccessFlags = 0;
          indexBufferDesc.MiscFlags = 0;
          indexBufferDesc.StructureByteStride = 0;

          D3D11_SUBRESOURCE_DATA indexBufferData;
          indexBufferData.pSysMem = triangleIndices;
          indexBufferData.SysMemPitch = 0;
          indexBufferData.SysMemSlicePitch = 0;

          ComPtr<ID3D11Buffer> indexBuffer;
          DX::ThrowIfFailed(
              m_d3dDevice->CreateBuffer(
                  &indexBufferDesc,
                  &indexBufferData,
                  &indexBuffer
                  )
              );
        });

我们使用顶点和像素着色器、顶点着色器布局和顶点和索引缓冲区绘制黄色三角形。

4.绘制三角形并呈现呈现的图像

我们进入一个无休止的循环来持续呈现和显示场景。 调用 ID3D11DeviceContext::OMSetRenderTargets 将呈现目标指定为输出目标。 我们调用 ID3D11DeviceContext::ClearRenderTargetView,使用 { 0.071f, 0.04f, 0.561f, 1.0f } 将渲染目标清除为纯蓝色。

在无休止的循环中,我们在蓝色图面上绘制一个黄色三角形。

绘制黄色三角形

  1. 首先,调用 ID3D11DeviceContext::IASetInputLayout 来描述如何将顶点缓冲区数据流式传输到输入汇编程序阶段。
  2. 接下来,调用 ID3D11DeviceContext::IASetVertexBuffersID3D11DeviceContext::IASetIndexBuffer 将顶点和索引缓冲区绑定到输入汇编程序阶段。
  3. 接下来,我们调用 ID3D11DeviceContext::IASetPrimitiveTopology,并使用 D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP 值,指示输入汇编器阶段将顶点数据解释为三角形带。
  4. 接下来,我们调用 ID3D11DeviceContext::VSSetShader,以使用顶点着色器代码初始化顶点着色器阶段,并调用 ID3D11DeviceContext::PSSetShader,以使用像素着色器代码初始化像素着色器阶段。
  5. 最后,调用 ID3D11DeviceContext::DrawIndexed 来绘制三角形并将其提交到渲染管道。

我们调用 IDXGISwapChain::Present 将渲染的图像显示到窗口。

            // Specify the render target we created as the output target.
            m_d3dDeviceContext->OMSetRenderTargets(
                1,
                m_renderTargetView.GetAddressOf(),
                nullptr // Use no depth stencil.
                );

            // Clear the render target to a solid color.
            const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
            m_d3dDeviceContext->ClearRenderTargetView(
                m_renderTargetView.Get(),
                clearColor
                );

            m_d3dDeviceContext->IASetInputLayout(inputLayout.Get());

            // Set the vertex and index buffers, and specify the way they define geometry.
            UINT stride = sizeof(float2);
            UINT offset = 0;
            m_d3dDeviceContext->IASetVertexBuffers(
                0,
                1,
                vertexBuffer.GetAddressOf(),
                &stride,
                &offset
                );

            m_d3dDeviceContext->IASetIndexBuffer(
                indexBuffer.Get(),
                DXGI_FORMAT_R16_UINT,
                0
                );

            m_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

            // Set the vertex and pixel shader stage state.
            m_d3dDeviceContext->VSSetShader(
                vertexShader.Get(),
                nullptr,
                0
                );

            m_d3dDeviceContext->PSSetShader(
                pixelShader.Get(),
                nullptr,
                0
                );

            // Draw the cube.
            m_d3dDeviceContext->DrawIndexed(
                ARRAYSIZE(triangleIndices),
                0,
                0
                );

            // Present the rendered image to the window.  Because the maximum frame latency is set to 1,
            // the render loop will generally be throttled to the screen refresh rate, typically around
            // 60 Hz, by sleeping the application on Present until the screen is refreshed.
            DX::ThrowIfFailed(
                m_swapChain->Present(1, 0)
                );

摘要和后续步骤

我们使用顶点和像素着色器创建和绘制黄色三角形。

接下来,我们将创建一个轨道 3D 立方体,并向其应用照明效果。

使用基元 的深度和效果