Поделиться через


Создание шейдеров и примитивов рисования

Здесь мы покажем, как использовать исходные файлы HLSL для компиляции и создания шейдеров, которые затем можно использовать для рисования примитивов на дисплее.

Мы создадим и рисуем желтый треугольник с помощью вершин и шейдеров пикселей. После создания устройства Direct3D, цепочки буферов и представления целевого объекта отрисовки мы считываем данные из файлов объектов двоичного шейдера на диске.

Цель: создание шейдеров и рисование примитивов.

Необходимые компоненты

Предположим, что вы знакомы с C++. Вам также нужен базовый опыт работы с концепциями программирования графики.

Мы также предполагаем, что вы прошли краткое руководство по настройке ресурсов DirectX и отображению изображения.

Время завершения: 20 минут.

Instructions

1. Компиляция исходных файлов HLSL

Microsoft Visual Studio использует компилятор кода HLSL fxc.exe для компиляции исходных файлов hlsl (SimpleVertexShader.hlsl и SimplePixelShader.hlsl) в файлы объектов двоичного шейдера CSO (SimpleVertexShader.cso и SimplePixelShader.cso). Дополнительные сведения о компиляторе кода HLSL см. в разделе "Средство компилятора эффектов". Дополнительные сведения о компиляции кода шейдера см. в разделе "Компиляция шейдеров".

Ниже приведен код в 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. Чтение данных с диска

Мы используем функцию DX::ReadDataAsync из DirectXHelper.h в шаблоне приложения DirectX 11 (универсальная windows), чтобы асинхронно считывать данные из файла на диске.

3. Создание шейдеров вершин и пикселей

Мы считываем данные из файла SimpleVertexShader.cso и присваиваем данные массиву байтов вершинShaderBytecode . Мы вызываем ID3D11Device::CreateVertexShader с массивом байтов, чтобы создать шейдер вершин (ID3D11VertexShader). Мы зададим значение глубины вершины 0,5 в источнике SimpleVertexShader.hlsl, чтобы гарантировать, что наш треугольник рисуется. Мы заполняем массив структур D3D11_INPUT_ELEMENT_DESC, чтобы описать макет кода вершинного шейдера, а затем вызвать ID3D11Device::CreateInputLayout для создания макета. Массив имеет один элемент макета, определяющий позицию вершины. Мы считываем данные из файла SimplePixelShader.cso и присваиваем данные массиву байтов pixelShaderBytecode . Мы вызываем ID3D11Device::CreatePixelShader с массивом байтов, чтобы создать шейдер пикселей (ID3D11PixelShader). Мы зададим значение пикселя (1,1,1,1) в источнике SimplePixelShader.hlsl, чтобы сделать наш треугольник желтым. Вы можете изменить цвет, изменив это значение.

Мы создадим буферы вершин и индексов, определяющие простой треугольник. Для этого сначала мы определим треугольник, затем опишите буферы вершин и индексов (D3D11_BUFFER_DESC и D3D11_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::IASetVertexBuffers и ID3D11DeviceContext::IASetIndexBuffer для привязки буферов вершин и индексов к этапу входного сборщика.
  3. Затем мы вызываем ID3D11DeviceContext::IASetPrimitiveTopology со значением D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP, чтобы указать для этапа входного сборщика данные вершин в виде полосы треугольников.
  4. Затем мы вызываем ID3D11DeviceContext::VSSetShader, чтобы инициализировать этап шейдера вершин с кодом шейдера вершин и ID3D11DeviceContext::P SSetShader, чтобы инициализировать этап шейдера пикселей с кодом шейдера пикселей.
  5. Наконец, мы вызываем ID3D11DeviceContext::D rawIndexed , чтобы нарисовать треугольник и отправить его в конвейер отрисовки.

Мы вызываем IDXGISwapChain::P resent, чтобы представить отображаемое изображение в окне.

            // 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)
                );

Сводка и дальнейшие действия

Мы создали и нарисовали желтый треугольник с помощью вершин и шейдеров пикселей.

Затем мы создадим орбитальный трехмерный куб и применим к нему эффекты освещения.

Использование глубины и эффектов для примитивов