Платформа отрисовки I: введение в отрисовку

Примечание

Этот раздел является частью серии учебников Создание простой игры универсальная платформа Windows (UWP) с помощью DirectX. Раздел по этой ссылке задает контекст для ряда.

На данный момент мы рассмотрели, как структурировать игру универсальная платформа Windows (UWP) и как определить конечный автомат для обработки потока игры. Теперь пришло время узнать, как разрабатывать платформу отрисовки. Давайте посмотрим, как пример игры отрисовывает сцену игры с помощью Direct3D 11.

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

Отрисовка игровой графики на экране означает в основном отрисовку последовательности кадров на экране. В каждом кадре необходимо отрисовать объекты, которые видны в сцене, в зависимости от представления.

Для отрисовки кадра необходимо передать оборудованию требуемую информацию о сцене, чтобы ее можно было отобразить на экране. Если вы хотите, чтобы на экране что-нибудь отображалось, начинать отрисовку необходимо сразу же после запуска игры.

Задачи

Настройка базовой платформы отрисовки для отображения выходных данных графики для игры UWP DirectX. Вы можете свободно разделить это на эти три шага.

  1. Установите подключение к графическому интерфейсу.
  2. Создайте ресурсы, необходимые для рисования графики.
  3. Отображение графики путем отрисовки кадра.

В этом разделе объясняется, как отрисовывается графика, охватывающих шаги 1 и 3.

Платформа отрисовки II. Отрисовка игры охватывает шаг 2. Настройка платформы отрисовки и подготовка данных перед отрисовкой.

Начало работы

Рекомендуется ознакомиться с основными понятиями графики и отрисовки. Если вы не знакомы с Direct3D и рендерингом, ознакомьтесь с кратким описанием графических и рендеринговых терминов, используемых в этом разделе, в разделе Термины и понятия.

Для этой игры класс GameRenderer представляет отрисовщик для этого примера игры. Отрисовщик отвечает за создание и поддержание всех объектов Direct3D 11 и Direct2D , используемых для создания визуальных элементов игры. Он также сохраняет ссылку на объект Simple3DGame , используемый для получения списка объектов для отрисовки, а также состояние игры для hud.

В этой части учебника мы подробно рассмотрим отрисовку трехмерных объектов в игре.

Подключение к графическому интерфейсу

Сведения о доступе к оборудованию для отрисовки см. в разделе Определение платформы приложений UWP игры .

Метод App::Initialize

Функция std::make_shared , как показано ниже, используется для создания shared_ptrdx::D eviceResources, которая также предоставляет доступ к устройству.

В Direct3D 11 устройство используется для выделения памяти под объекты и их уничтожения, отрисовки примитивов, а также обмена данными с графической картой через графический драйвер.

void Initialize(CoreApplicationView const& applicationView)
{
    ...

    // At this point we have access to the device. 
    // We can create the device-dependent resources.
    m_deviceResources = std::make_shared<DX::DeviceResources>();
}

Отображение графики путем отрисовки кадра

При запуске игры необходимо отрисовать игровую сцену. Инструкции по отрисовке запускаются в методе GameMain::Run , как показано ниже.

Это простой поток.

  1. Обновление
  2. Прорисовка
  3. Настоящее время

Метод GameMain::Run

void GameMain::Run()
{
    while (!m_windowClosed)
    {
        if (m_visible) // if the window is visible
        {
            switch (m_updateState)
            {
            ...
            default:
                CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);
                Update();
                m_renderer->Render();
                m_deviceResources->Present();
                m_renderNeeded = false;
            }
        }
        else
        {
            CoreWindow::GetForCurrentThread().Dispatcher().ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
        }
    }
    m_game->OnSuspending();  // Exiting due to window close, so save state.
}

Update

Дополнительные сведения об обновлении состояний игры в методе GameMain::Update см. в разделе Управление потоком игры.

Render

Отрисовка реализуется путем вызова метода GameRenderer::Render из GameMain::Run.

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

Примечание

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

После запуска игры и загрузки ресурсов мы обновляем матрицу проекции один раз за каждый проход отрисовки. Объекты из каждого представления слегка отличаются друг от друга. Далее мы настроим конвейер отрисовки графики.

Примечание

Подробнее о том, как загружаются ресурсы, см. в статье Создание и загрузка графических ресурсов DirectX.

В этом примере игры отрисовщик предназначен для использования стандартного макета вершин для всех объектов. Это упрощает проектирование шейдеров и позволяет легко изменять шейдеры независимо от геометрии объектов.

Метод GameRenderer::Render

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

Далее мы задаем контекст Direct3D для использования буферов констант, определенных ранее, которые используются этапами конвейера вершинного шейдера и конвейера пиксельного шейдера .

Примечание

Подробнее об определении буферов констант см. в статье Платформа отрисовки II: отрисовка игры.

Поскольку для всех шейдеров в конвейере используется один и тот же входной макет и набор буферов констант, они задаются по одному разу на кадр.

void GameRenderer::Render()
{
    bool stereoEnabled{ m_deviceResources->GetStereoState() };

    auto d3dContext{ m_deviceResources->GetD3DDeviceContext() };
    auto d2dContext{ m_deviceResources->GetD2DDeviceContext() };

    int renderingPasses = 1;
    if (stereoEnabled)
    {
        renderingPasses = 2;
    }

    for (int i = 0; i < renderingPasses; i++)
    {
        // Iterate through the number of rendering passes to be completed.
        // 2 rendering passes if stereo is enabled.
        if (i > 0)
        {
            // Doing the Right Eye View.
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetViewRight() };

            // Resets render targets to the screen.
            // OMSetRenderTargets binds 2 things to the device.
            // 1. Binds one render target atomically to the device.
            // 2. Binds the depth-stencil view, as returned by the GetDepthStencilView method, to the device.
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-omsetrendertargets

            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());

            // Clears the depth stencil view.
            // A depth stencil view contains the format and buffer to hold depth and stencil info.
            // For more info about depth stencil view, go to: 
            // https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-stencil-view--dsv-
            // A depth buffer is used to store depth information to control which areas of 
            // polygons are rendered rather than hidden from view. To learn more about a depth buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/depth-buffers
            // A stencil buffer is used to mask pixels in an image, to produce special effects. 
            // The mask determines whether a pixel is drawn or not,
            // by setting the bit to a 1 or 0. To learn more about a stencil buffer,
            // go to: https://learn.microsoft.com/windows/uwp/graphics-concepts/stencil-buffers

            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // Direct2D -- discussed later
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmapRight());
        }
        else
        {
            // Doing the Mono or Left Eye View.
            // As compared to the right eye:
            // m_deviceResources->GetBackBufferRenderTargetView instead of GetBackBufferRenderTargetViewRight
            ID3D11RenderTargetView* const targets[1] = { m_deviceResources->GetBackBufferRenderTargetView() };

            // Same as the Right Eye View.
            d3dContext->OMSetRenderTargets(1, targets, m_deviceResources->GetDepthStencilView());
            d3dContext->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH, 1.0f, 0);

            // d2d -- Discussed later under Adding UI
            d2dContext->SetTarget(m_deviceResources->GetD2DTargetBitmap());
        }

        const float clearColor[4] = { 0.5f, 0.5f, 0.8f, 1.0f };

        // Only need to clear the background when not rendering the full 3D scene since
        // the 3D world is a fully enclosed box and the dynamics prevents the camera from
        // moving outside this space.
        if (i > 0)
        {
            // Doing the Right Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetViewRight(), clearColor);
        }
        else
        {
            // Doing the Mono or Left Eye View.
            d3dContext->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), clearColor);
        }

        // Render the scene objects
        if (m_game != nullptr && m_gameResourcesLoaded && m_levelResourcesLoaded)
        {
            // This section is only used after the game state has been initialized and all device
            // resources needed for the game have been created and associated with the game objects.
            if (stereoEnabled)
            {
                // When doing stereo, it is necessary to update the projection matrix once per rendering pass.

                auto orientation = m_deviceResources->GetOrientationTransform3D();

                ConstantBufferChangeOnResize changesOnResize;
                // Apply either a left or right eye projection, which is an offset from the middle
                XMStoreFloat4x4(
                    &changesOnResize.projection,
                    XMMatrixMultiply(
                        XMMatrixTranspose(
                            i == 0 ?
                            m_game->GameCamera().LeftEyeProjection() :
                            m_game->GameCamera().RightEyeProjection()
                            ),
                        XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                        )
                    );

                d3dContext->UpdateSubresource(
                    m_constantBufferChangeOnResize.get(),
                    0,
                    nullptr,
                    &changesOnResize,
                    0,
                    0
                    );
            }

            // Update variables that change once per frame.
            ConstantBufferChangesEveryFrame constantBufferChangesEveryFrameValue;
            XMStoreFloat4x4(
                &constantBufferChangesEveryFrameValue.view,
                XMMatrixTranspose(m_game->GameCamera().View())
                );
            d3dContext->UpdateSubresource(
                m_constantBufferChangesEveryFrame.get(),
                0,
                nullptr,
                &constantBufferChangesEveryFrameValue,
                0,
                0
                );

            // Set up the graphics pipeline. This sample uses the same InputLayout and set of
            // constant buffers for all shaders, so they only need to be set once per frame.
            // For more info about the graphics or rendering pipeline, see
            // https://learn.microsoft.com/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

            // IASetInputLayout binds an input-layout object to the input-assembler (IA) stage. 
            // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
            // Set up the Direct3D context to use this vertex layout. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetinputlayout
            d3dContext->IASetInputLayout(m_vertexLayout.get());

            // VSSetConstantBuffers sets the constant buffers used by the vertex shader pipeline stage.
            // Set up the Direct3D context to use these constant buffers. For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-vssetconstantbuffers

            ID3D11Buffer* constantBufferNeverChanges{ m_constantBufferNeverChanges.get() };
            d3dContext->VSSetConstantBuffers(0, 1, &constantBufferNeverChanges);
            ID3D11Buffer* constantBufferChangeOnResize{ m_constantBufferChangeOnResize.get() };
            d3dContext->VSSetConstantBuffers(1, 1, &constantBufferChangeOnResize);
            ID3D11Buffer* constantBufferChangesEveryFrame{ m_constantBufferChangesEveryFrame.get() };
            d3dContext->VSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            ID3D11Buffer* constantBufferChangesEveryPrim{ m_constantBufferChangesEveryPrim.get() };
            d3dContext->VSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);

            // Sets the constant buffers used by the pixel shader pipeline stage. 
            // For more info, see
            // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-pssetconstantbuffers

            d3dContext->PSSetConstantBuffers(2, 1, &constantBufferChangesEveryFrame);
            d3dContext->PSSetConstantBuffers(3, 1, &constantBufferChangesEveryPrim);
            ID3D11SamplerState* samplerLinear{ m_samplerLinear.get() };
            d3dContext->PSSetSamplers(0, 1, &samplerLinear);

            for (auto&& object : m_game->RenderObjects())
            {
                // The 3D object render method handles the rendering.
                // For more info, see Primitive rendering below.
                object->Render(d3dContext, m_constantBufferChangesEveryPrim.get());
            }
        }

        // Start of 2D rendering
        ...
    }
}

Отрисовка примитивов

При отрисовке сцены необходимо перебрать все объекты, которые должны быть отрисованы. Указанные ниже шаги повторяются для каждого объекта (примитива).

  • Обновите буфер констант (m_constantBufferChangesEveryPrim) матрицей преобразования мира модели и сведениями о материале.
  • M_constantBufferChangesEveryPrim содержит параметры для каждого объекта. Она включает матрицу преобразования объекта в мир, а также свойства материала, такие как цвет и показатель отражения для вычислений освещения.
  • Задайте контекст Direct3D для использования макета входной вершины для потоковой передачи данных объекта сетки в этап входного ассемблер (IA) конвейера отрисовки.
  • Задайте контекст Direct3D для использования буфера индекса на этапе IA. Предоставляется информация о примитивах: тип, порядок данных.
  • Вызывается метод рисования для рисования индексированного, не являющегося экземпляром примитива. Метод GameObject::Render обновляет буфер констант, добавляя данные, характерные для конкретного примитива. В результате происходит вызов метода DrawIndexed в контексте для рисования геометрии каждого примитива. В частности, этот вызов метода рисования помещает команды и данные в очередь графического процессора (GPU) с параметрами, заданными данными буфера констант. При каждом вызове метода рисования выполняется вершинный шейдер — по одному разу для каждой вершины, а затем построитель текстуры — по одному разу для каждого пикселя каждого треугольника в примитиве. Текстуры являются частью состояния, которое построитель текстуры использует для отрисовки.

Ниже приведены причины использования нескольких буферов констант.

  • В игре используется несколько буферов констант, но эти буферы нужно обновлять только один раз для каждого примитива. Как уже говорилось, буферы констант можно рассматривать как входные данные для шейдеров, которые выполняются для каждого примитива. Некоторые данные являются статическими (m_constantBufferNeverChanges); некоторые данные являются постоянными для кадра (m_constantBufferChangesEveryFrame), например положение камеры; и некоторые данные относятся к примитиву, например его цвет и текстуры (m_constantBufferChangesEveryPrim).
  • Обработчик игры разделяет эти входные данные по разным буферам констант, чтобы оптимизировать пропускную способность памяти, используемую ЦП и GPU. Такой подход также помогает свести к минимуму объем данных, необходимых GPU для отслеживания. У GPU большая очередь команд, и каждый раз, когда игра вызывает метод Draw, эта команда добавляется в очередь вместе со связанными с ней данными. Когда игра обновляет буфер констант примитива и выдает следующую команду Draw, графический драйвер добавляет эту команду и связанные с ней данные в очередь. Если игра рисует 100 примитивов, в очереди может накопиться 100 копий данных буфера констант. Чтобы свести к минимуму количество данных, которое игра отправляет GPU, игра использует отдельный буфер констант примитива, который содержит только обновления для каждого примитива.

Метод GameObject::Render

void GameObject::Render(
    _In_ ID3D11DeviceContext* context,
    _In_ ID3D11Buffer* primitiveConstantBuffer
    )
{
    if (!m_active || (m_mesh == nullptr) || (m_normalMaterial == nullptr))
    {
        return;
    }

    ConstantBufferChangesEveryPrim constantBuffer;

    // Put the model matrix info into a constant buffer, in world matrix.
    XMStoreFloat4x4(
        &constantBuffer.worldMatrix,
        XMMatrixTranspose(ModelMatrix())
        );

    // Check to see which material to use on the object.
    // If a collision (a hit) is detected, GameObject::Render checks the current context, which 
    // indicates whether the target has been hit by an ammo sphere. If the target has been hit, 
    // this method applies a hit material, which reverses the colors of the rings of the target to 
    // indicate a successful hit to the player. Otherwise, it applies the default material 
    // with the same method. In both cases, it sets the material by calling Material::RenderSetup, 
    // which sets the appropriate constants into the constant buffer. Then, it calls 
    // ID3D11DeviceContext::PSSetShaderResources to set the corresponding texture resource for the 
    // pixel shader, and ID3D11DeviceContext::VSSetShader and ID3D11DeviceContext::PSSetShader 
    // to set the vertex shader and pixel shader objects themselves, respectively.

    if (m_hit && m_hitMaterial != nullptr)
    {
        m_hitMaterial->RenderSetup(context, &constantBuffer);
    }
    else
    {
        m_normalMaterial->RenderSetup(context, &constantBuffer);
    }

    // Update the primitive constant buffer with the object model's info.
    context->UpdateSubresource(primitiveConstantBuffer, 0, nullptr, &constantBuffer, 0, 0);

    // Render the mesh.
    // See MeshObject::Render method below.
    m_mesh->Render(context);
}

Метод MeshObject::Render

void MeshObject::Render(_In_ ID3D11DeviceContext* context)
{
    // PNTVertex is a struct. stride provides us the size required for all the mesh data
    // struct PNTVertex
    //{
    //  DirectX::XMFLOAT3 position;
    //  DirectX::XMFLOAT3 normal;
    //  DirectX::XMFLOAT2 textureCoordinate;
    //};
    uint32_t stride{ sizeof(PNTVertex) };
    uint32_t offset{ 0 };

    // Similar to the main render loop.
    // Input-layout objects describe how vertex buffer data is streamed into the IA pipeline stage.
    ID3D11Buffer* vertexBuffer{ m_vertexBuffer.get() };
    context->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

    // IASetIndexBuffer binds an index buffer to the input-assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetindexbuffer.
    context->IASetIndexBuffer(m_indexBuffer.get(), DXGI_FORMAT_R16_UINT, 0);

    // Binds information about the primitive type, and data order that describes input data for the input assembler stage.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-iasetprimitivetopology.
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Draw indexed, non-instanced primitives. A draw API submits work to the rendering pipeline.
    // For more info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed.
    context->DrawIndexed(m_indexCount, 0, 0);
}

Метод DeviceResources::P resent

Мы вызываем метод DeviceResources::P resent для отображения содержимого, помещенного в буферы.

Коллекция буферов, которая используется для отображения кадров пользователю, называется "цепочка буферов". Каждый раз, когда приложение представляет новый кадр для отображение, первый буфер в цепочке буферов занимает место отображаемого буфера. Этот процесс называется заменой или переключением. Подробнее: Цепочки буферов.

  • Метод Present интерфейса IDXGISwapChain1 указывает DXGI блокировать, пока не произойдет вертикальная синхронизация (VSync), переводя приложение в спящий режим до следующей виртуальной синхронизации. Это гарантирует, что вы не будете тратить циклы отрисовки кадров, которые никогда не будут отображаться на экране.
  • Метод DiscardView интерфейса ID3D11DeviceContext3 удаляет содержимое целевого объекта отрисовки. Эта операция допустима только в случае, когда существующее содержимое будет полностью перезаписано. Если используются грязное или прокручиваемые прямоугольника, этот вызов следует удалить.

Совет

Чтобы добиться плавной частоты кадров, необходимо убедиться, что объем работы по отрисовке кадра соответствует времени между виртуальными синхронизациями.

// Present the contents of the swap chain to the screen.
void DX::DeviceResources::Present()
{
    // The first argument instructs DXGI to block until VSync, putting the application
    // to sleep until the next VSync. This ensures we don't waste any cycles rendering
    // frames that will never be displayed to the screen.
    HRESULT hr = m_swapChain->Present(1, 0);

    // Discard the contents of the render target.
    // This is a valid operation only when the existing contents will be entirely
    // overwritten. If dirty or scroll rects are used, this call should be removed.
    m_d3dContext->DiscardView(m_d3dRenderTargetView.get());

    // Discard the contents of the depth stencil.
    m_d3dContext->DiscardView(m_d3dDepthStencilView.get());

    // If the device was removed either by a disconnection or a driver upgrade, we 
    // must recreate all device resources.
    if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
    {
        HandleDeviceLost();
    }
    else
    {
        winrt::check_hresult(hr);
    }
}

Дальнейшие действия

В этом разделе объясняется, как графика отрисовывается на экране, и в нем приводится краткое описание некоторых используемых терминов отрисовки (ниже). Дополнительные сведения о рендеринге см. в разделе О платформе отрисовки II. Отрисовка игры , а также о подготовке данных, необходимых перед отрисовкой.

Термины и основные понятия

Простая игровая сцена

Простая игровая сцена состоит из нескольких объектов с несколькими источниками света.

Форма объекта определяется набором координат X, Y, Z в пространстве. Фактическое местоположение отрисовки в игровом мире можно определить, применив к позиционным координатам X, Y, Z матрицу преобразования. Он также может иметь набор координат текстуры (вы и V), которые указывают, как материал применяется к объекту. Это определяет свойства поверхности объекта и дает возможность видеть, имеет ли объект грубую поверхность (например, теннисный мяч) или гладкую глянцевую поверхность (например, шар для боулинга).

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

Конвейер отрисовки

Конвейер отрисовки — это процесс, с помощью которого данные трехмерной сцены преобразуются в изображение, отображаемое на экране. В Direct3D 11 этот конвейер является программируемым. Вы можете адаптировать его этапы под свои потребности в отрисовке. Этапы, на которых используются общих ядра шейдеров, можно программировать с помощью языка программирования HLSL. Он также называется конвейером отрисовки графики или просто конвейером.

Чтобы создать этот конвейер, необходимо ознакомиться с этими сведениями.

Подробнее: Общие сведения о конвейере отрисовки Direct3D 11 и Графический конвейер.

HLSL

HLSL — это высокоуровневый язык шейдеров для DirectX. С помощью HLSL можно создавать программируемые шейдеры C для конвейера Direct3D. Подробнее: HLSL.

Шейдеры

Шейдер можно рассматривать как набор инструкций, определяющих, как поверхность объекта отображается при отрисовки. Шейдеры, запрограммированные с использованием HLSL, называются шейдерами HLSL. Файлы исходного кода для шейдеров [HLSL])(#hlsl) имеют .hlsl расширение файла. Эти шейдеры можно компилировать во время сборки или во время выполнения, а также устанавливать во время выполнения на соответствующий этап конвейера. Скомпилированный объект шейдера имеет .cso расширение файла.

Шейдеры Direct3D 9 можно разрабатывать с помощью модели 1 шейдера, модели шейдера 2 и модели 3; Шейдеры Direct3D 10 могут быть разработаны только для модели 4 шейдеров. Шейдеры Direct3D 11 могут разрабатываться с использованием Shader Model 5. Шейдеры Direct3D 11.3 и Direct3D 12 могут разрабатываться с использованием Shader Model 5.1; для Direct3D 12 также может использоваться Shader Model 6.

Вершинные шейдеры и построители текстур

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

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

Шейдерные этапы

Последовательность упомянутых выше шейдеров, определенная для обработки потока примитивов, называется шейдерными этапами конвейера отрисовки. Фактические этапы зависят от версии Direct3D, но обычно это этапы обработки вершин, геометрии и пикселей. Также существуют другие этапы — например, шейдеры поверхностей и шейдеры доменов для тесселяции, а также шейдер вычислений. Все эти этапы полностью программируются с помощью HLSL. Подробнее: Графический конвейер.

Различные форматы файлов шейдеров

Ниже приведены расширения файлов кода шейдера.

  • Файл с расширением содержит исходный .hlsl код [HLSL])(#hlsl).
  • Файл с расширением .cso содержит скомпилированный объект шейдера.
  • Файл с расширением .h является файлом заголовка, но в контексте кода шейдера этот файл заголовка определяет массив байтов, содержащий данные шейдера.
  • Файл с расширением .hlsli содержит формат буферов констант. В примере игры используется файл Shaders>ConstantBuffers.hlsli.

Примечание

Шейдер внедряется путем загрузки .cso файла во время выполнения или путем добавления файла в исполняемый .h код. Но вы не будете использовать оба для одного и того же шейдера.

Подробнее о DirectX

Direct3D 11 — это набор API- интерфейсов, которые помогут нам создавать графику для приложений с интенсивным использованием графики, таких как игры, где мы хотим иметь хорошие графические карта для обработки интенсивных вычислений. В этом разделе кратко рассматриваются понятия, связанные с программированием графики для Direct3D 11: ресурс, подресурс, устройство и контекст устройства.

Ресурс

Ресурсы (также известные как ресурсы устройства) можно рассматривать как сведения о том, как отрисовка объекта, например текстура, положение или цвет. Ресурсы предоставляют данные для конвейера и определяют, что будет отображаться во время сцены. Ресурсы можно загружать с игрового носителя или создавать динамически во время выполнения.

Ресурс — это, по сути, область памяти, к которой конвейер Direct3D может осуществлять доступ. Чтобы конвейер получал доступ к памяти эффективным образом, данные, предоставляемые конвейеру (например, входные геометрические данные, ресурсы шейдеров и текстуры), должны храниться в ресурсах. Существуют два типа ресурсов, из которых образуются все ресурсы Direct3D: буфер и текстура. На каждом этапе конвейера может быть активно до 128 ресурсов. Дополнительные сведения см. в статье о ресурсах DSC.

Подресурс

Под ресурсом понимается подмножество ресурса. Direct3D может ссылаться на весь ресурс или на подмножества ресурса. Подробнее: Подресурс.

Трафарет глубины

Ресурс трафарета глубины содержит формат и буфер для хранения сведений о глубине и трафарете. Он создается с использованием ресурса текстуры. Подробнее о том, как создать ресурс трафарета глубины, см. в статье Настройка функции трафарета глубины. Доступ к ресурсу трафарета глубины осуществляется через представление трафарета глубины, реализуемое с помощью интерфейса ID3D11DepthStencilView.

Сведения о глубине сообщают нам, какие области многоугольников находятся позади других, чтобы мы могли определить, какие из них скрыты. Информация о трафарете позволяет понять, какие пиксели маскируются. Ее можно использовать для создания специальных эффектов, поскольку она определяет, рисуется пиксель или нет (устанавливает бит равным 1 или 0).

Дополнительные сведения см. в разделе Представление трафарета глубины, буфер глубины и буфер трафарета.

Целевой объект отрисовки

Целевой объект отрисовки — это ресурс, в который производится запись данных по окончании этапа отрисовки. Обычно он создается с помощью метода ID3D11Device::CreateRenderTargetView с использованием заднего буфера цепочки буферов (который также является ресурсом) в качестве входного параметра.

Каждый целевой объект отрисовки также должен иметь соответствующее представление трафарета глубины, потому что мы используем метод OMSetRenderTargets для задания целевого объекта отрисовки, прежде чем использовать его, и этому методу требуется в том числе представление трафарета глубины. Доступ к ресурсу целевого объекта отрисовки осуществляется через представление целевого объекта отрисовки, реализуемого с помощью интерфейса ID3D11RenderTargetView.

Устройство

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

Если говорить точнее, устройство Direct3D — это компонент Direct3D, выполняющий отрисовку графики. Устройство инкапсулирует и сохраняет состояние отрисовки, выполняет операции преобразования и освещения и растеризует изображение на поверхность. Подробнее: Устройства

Устройство представлено интерфейсом ID3D11Device . Другими словами, интерфейс ID3D11Device представляет виртуальный видеоадаптер и используется для создания ресурсов, принадлежащих устройству.

Существуют разные версии ID3D11Device. ID3D11Device5 является последней версией и добавляет новые методы в ID3D11Device4. Подробнее о том, как Direct3D взаимодействует с оборудованием, см. в статье Архитектура модели драйверов устройств с Windows (WDDM).

Каждое приложение должно иметь по крайней мере одно устройство; большинство приложений создают только одно. Создайте устройство для одного из драйверов оборудования, установленных на компьютере, вызвав D3D11CreateDevice или D3D11CreateDeviceAndSwapChain и указав тип драйвера с флагом D3D_DRIVER_TYPE . Каждое устройство можно использовать один или несколько контекстов устройств, в зависимости от требуемой функциональности. Подробнее: Функция D3D11CreateDevice.

Контекст устройства

Контекст устройства используется для задания состояния конвейера и создания команд отрисовки с использованием ресурсов , принадлежащих устройству.

Direct3D 11 реализует два типа контекста устройств: один для немедленной отрисовки и другой для отложенной отрисовки; оба контекста представляются с помощью интерфейса ID3D11DeviceContext.

Интерфейсы ID3D11DeviceContext бывают разных версий; в ID3D11DeviceContext4 добавлены новые методы относительно ID3D11DeviceContext3.

ID3D11DeviceContext4 появился в Windows 10 Creators Update и является последней версией интерфейса ID3D11DeviceContext . Приложения, предназначенные для Windows 10 Creators Update и более поздних версий, должны использовать этот интерфейс вместо более ранних версий. Подробнее: ID3D11DeviceContext4.

DX::DeviceResources

Класс DX::D eviceResources находится в файлахDeviceResources.cpp.h/ и управляет всеми ресурсами устройств DirectX.

Буфер

Буферный ресурс — это коллекция полностью типизированных данных, сгруппированных в элементы. Буферы можно использовать для хранения самых разных данных, в том числе векторов положений, нормальных векторов, координат текстур (в буфере вершин), индексов (в буфере индексов) или состояния устройства. Элементы буфера могут включать упакованные значения данных (например, значения поверхности R8G8B8A8 ), одно 8-разрядные целые числа или четыре 32-разрядных значения с плавающей запятой.

Существует три типа буферов: буфер вершин, буфер индексов и буфер констант.

Буфер вершин

Содержит данные вершин, используемые для определения геометрии. К данным вершин относятся координаты положения, данные цвета, данные координат текстуры, данные нормали и т. д.

Буфер индексов

Содержит целочисленные указатели для буферов вершин и используется для более эффективной отрисовки примитивов. Буфер индексов содержит последовательный набор 16-разрядных или 32-разрядных индексов; каждый индекс используется для идентификации вершины в буфере вершин.

Буфер констант или буфер констант шейдера

Позволяет эффективно поставлять данные шейдера в конвейер. Буферы констант можно использовать в качестве входных данных для шейдеров, которые выполняются для каждого примитива и сохраняют результаты этапа потокового вывода конвейера отрисовки. На концептуальном уровне буфер констант аналогичен одноэлементному буферу вершин.

Проектирование и реализация буферов

Вы можете спроектировать буферы на основе типа данных. Например, как в нашем примере игры, один буфер создается для статических данных, другой — для данных, которые являются константой в кадре, а другой — для данных, относящихся к примитиву.

Все типы буферов инкапсулируются интерфейсом ID3D11Buffer; создать буферный ресурс можно путем вызова метода ID3D11Device::CreateBuffer. Однако прежде чем к буферу можно будет осуществить доступ, его необходимо привязать к конвейеру. Для чтения буферы могут быть привязаны одновременно к нескольким этапам конвейера. Буфер также можно привязать к одному этапу конвейера для записи; однако один и тот же буфер не может быть привязан как для чтения, так и для записи одновременно.

Таким образом можно привязать буферы.

  • На этапе ассемблатора ввода путем вызова методов ID3D11DeviceContext , таких как ID3D11DeviceContext::IASetVertexBuffers и ID3D11DeviceContext::IASetIndexBuffer.
  • На этап потокового вывода путем вызова ID3D11DeviceContext::SOSetTargets.
  • На стадии шейдера путем вызова методов шейдера, таких как ID3D11DeviceContext::VSSetConstantBuffers.

Подробнее: Вводные сведения о буферах в Direct3D 11.

DXGI

Графическая инфраструктура Microsoft DirectX (DXGI) — это подсистема, которая инкапсулирует некоторые низкоуровневые задачи, необходимые Direct3D. При использовании DXGI в многопоточных приложениях необходимо соблюдать особую осторожность, чтобы не возникало взаимоблокировок. Дополнительные сведения см. в разделе Многопоточность и DXGI.

Уровень компонентов

Уровень компонентов — это концепция, появившаяся в Direct3D 11 для обработки всего разнообразия видеоадаптеров в новых и существующих компьютерах. Уровень функций — это четко определенный набор функций графического процессора (GPU).

Каждый видеоадаптер реализует определенный уровень функциональности DirectX в зависимости от установленных графических процессоров. В предыдущих версиях Microsoft Direct3D можно было узнать версию Direct3D, реализуемую видеоадаптером, а затем программировать приложение соответствующим образом.

Используя уровень компонентов, при создании устройства вы можете попытаться создать устройство для уровня компонентов, который вы хотите запросить. Если создать устройство удалось, этот уровень компонентов существует; если нет, оборудование не поддерживает этот уровень компонентов. Вы можете попытаться повторно создать устройство на более низком уровне функций или выйти из приложения. Например, для уровня компонентов 12_0 требуется Direct3D 11.3 или Direct3D 12 и модель шейдера 5.1. Подробнее: Уровни компонентов Direct3D: обзор каждого уровня компонентов.

Используя уровни компонентов, можно разработать приложение для Direct3D 9, Microsoft Direct3D 10 или Direct3D 11, а затем запустить его на оборудовании 9, 10 или 11 (за некоторыми исключениями). Подробнее: Уровни компонентов Direct3D.

Стереоскопическая отрисовка

Стереоскопическая отрисовка используется для усиления иллюзии глубины. Она предполагает использование двух изображений — одного для левого глаза, другого для правого — для отображения сцены на экране дисплея.

С математической точки зрения для достижения этого эффекта применяется матрица стереопроекции, которая представляет собой небольшое горизонтальное смещение обычной матрицы монопроекции вправо и влево.

Мы выполнили два прохода отрисовки для достижения стерео отрисовки в этом примере игры.

  • Привязались к правому целевому объекту отрисовки, применили правую проекцию и нарисовали объект примитива.
  • Привязались к левому целевому объекту отрисовки, применили левую проекцию и нарисовали объект примитива.

Камера и пространство координат

Игра снабжена кодом для обновления мира в его собственной системе координат (которая иногда называется "пространство мира" или "пространство сцены"). Все объекты, включая камеру, расположены и ориентированы в этом пространстве. Подробнее: Системы координат.

Вершинный шейдер выполняет преобразование координат модели в координаты устройства с помощью следующего алгоритма (где V — вектор, а M — матрица):

V(device) = V(model) x M(model-to-world) x M(world-to-view) x M(view-to-device)

  • M(model-to-world) — это матрица преобразования для координат модели в мировые координаты, также известная как матрица преобразования Мира. Предоставляется примитивом.
  • M(world-to-view) — это матрица преобразования для мировых координат для просмотра координат, также известная как матрица преобразования Представления.
    • Получить ее можно из матрицы представления камеры. Он определяется положением камеры вместе с векторами взгляда ( взгляд на вектор, который указывает непосредственно на сцену из камеры, и вектор вверх , перпендикулярно ему).
    • В примере игры m_viewMatrix является матрицей преобразования представления и вычисляется с помощью Camera::SetViewParams.
  • M(view-to-device) — это матрица преобразования для координат представления координат устройства, также известная как матрица преобразования проекции.
    • Получить ее можно из проекции камеры. Он предоставляет сведения о том, сколько пространства на самом деле видно в финальной сцене. Матрица преобразования проекции определяется полем зрения (FoV), пропорциями и плоскостями обрезки.
    • В примере игры m_projectionMatrix определяет преобразование в координаты проекции, вычисленное с помощью Camera::SetProjParams (для стереопроецирования используются две матрицы проекции — по одной для просмотра каждого глаза).

Код шейдера в VertexShader.hlsl загружается с этими векторами и матрицами из буферов констант и выполняет это преобразование для каждой вершины.

Преобразование координат

В Direct3D используется три преобразования для перевода координат трехмерной модели в координаты пикселей (пространство экрана). Эти преобразования — мировое преобразование, видовое преобразование и проекционное преобразование. Дополнительные сведения см. в разделе Общие сведения о преобразовании.

Матрица мирового преобразования

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

Матрица видового преобразования

Видовое преобразование определяет положение наблюдателя в мировом пространстве и обеспечивает преобразование вершин в пространство камеры. В пространстве камеры камера, или наблюдатель, находится в начале координат и смотрит в направлении положительной полуоси Z. Подробнее: Видовое преобразование.

Матрица проекционного преобразования

Проекционное преобразование превращает видимое пространство (усеченную пирамиду) в кубовидную фигуру. Усеченная пирамида видимости — это трехмерный объем в сцене, позиционируемый относительно камеры окна просмотра. Окно просмотра — это двумерный прямоугольник, на который проецируется трехмерная сцена. Подробнее: Окна просмотра и обрезка

Так как ближний конец видимого пространства меньше дальнего, это приводит к развертыванию объектов, которые находятся ближе к камере — так перспектива применяется к сцене. Поэтому объекты, которые находятся ближе к игроку, выглядят больше; Объекты, которые находятся дальше, кажутся меньшими.

С математической точки зрения преобразование проекции — это матрица, которая обычно является проекцией масштаба и перспективы. Она функционирует подробно объективу камеры. Подробнее: Проекционное преобразование.

Состояние дискретизатора

Состояние дискретизатора определяет, как дискретизируются данные текстур, с использованием режимов адресации текстур, фильтрации и уровня детализации. Выборка выполняется каждый раз, когда пиксель текстуры (или тексель) считывается из текстуры.

Текстура содержит массив текселей. Положение каждого текселя обозначается параметром (u,v), где u — это ширина, а v — высота, и сопоставляется в диапазоне от 0 до 1 на основе ширины и высоты текстуры. Полученные координаты текстуры используются для адресации текселя при дискретизации текстуры.

Когда координаты текстуры меньше 0 или больше 0, режим адресации текстуры определяет, как координата текстуры адресует положение пикселя. Например, при использовании TextureAddressMode.Clampлюбая координата за пределами диапазона 0–1 приводится к максимальному значению (1) и минимальному значению (0) перед дискретизацией.

Если текстура слишком велика или слишком мала для многоугольника, то текстура фильтруется в соответствии с пространством. Фильтр увеличения увеличивает текстуру, а фильтр уменьшения уменьшает ее так, чтобы она поместилась в меньшую область. При увеличении текстуры тексель повторяется для одного или нескольких адресов, что дает более размытое изображение. Минификация текстуры сложнее, так как требует объединения нескольких значений текселя в одно значение. Это может привести к искажениям контура (алиасингу), в зависимости от данных текстуры. Самый популярный подход к уменьшению — это использование MIP-карты. MIP-карта — это многоуровневая текстура. Размер каждого уровня на 2 меньше предыдущего уровня до текстуры 1x1. При использовании уменьшения игра выбирает уровень MIP-карты, ближайший к необходимому ей размеру, во время отрисовки.

Класс BasicLoader

BasicLoader — это простой класс-загрузчик, который обеспечивает функциональность для загрузки шейдеров, текстур и сеток из файлов на диске. Он предоставляет и синхронные, и асинхронные методы. В этом примере игры BasicLoader.h/.cpp файлы находятся в папке Служебные программы .

Подробнее: Basic Loader.