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


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

Замечание

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

До сих пор мы рассмотрели, как структурировать игру на платформе Universal Windows Platform (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_ptr для DX::DeviceResources, который также предоставляет доступ к устройству.

В 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.
}

Обновление

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

Отображать

Отрисовка реализуется путем вызова метода 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 для отслеживания. Графический процессор имеет большую очередь команд, и каждый раз, когда игра вызывает Рисование, эта команда помещается в очередь вместе с данными, связанными с ней. Когда игра обновляет примитивный буфер констант и выдает следующую команду рисования, графический драйвер добавляет эту следующую команду и связанные данные в очередь. Если игра рисует 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::Present

Мы вызываем метод DeviceResources::Present, чтобы отобразить содержимое, помещенное в буферы.

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

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

Подсказка

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

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

Дальнейшие шаги

В этом разделе объясняется, как отрисовывается графика на дисплее, и она содержит краткое описание некоторых терминов отрисовки, используемых (ниже). Узнайте больше о отрисовке в framework 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 можно разрабатывать на модели шейдера 5. Direct3D 11.3 и Direct3D 12 можно разрабатывать на модели шейдера 5.1, а Direct3D 12 также можно разрабатывать на модели шейдера 6.

Шейдеры вершин и шейдеры пикселей

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

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

Этапы шейдера

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

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

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

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

Замечание

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

Более глубокое понимание DirectX

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

Ресурс

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

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

Подресурс

Термин подресурс ссылается на подмножество ресурса. Direct3D может ссылаться на весь ресурс или ссылаться на подмножества ресурса. Дополнительные сведения см. в разделе Subresource.

Глубина/трафарет

Ресурс глубины и трафарета содержит формат и буфер для хранения информации о глубине и трафарете. Он создается с помощью ресурса текстуры. Дополнительные сведения о создании ресурса глубины и трафарета см. в разделе Настройка функциональности Depth-Stencil. Доступ к ресурсу набора элементов глубины осуществляется через представление элементов глубины, реализованное с помощью интерфейса 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::D eviceResources

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

Буфер

Ресурс буфера — это коллекция полностью типизированных данных, сгруппированных в элементы. Буферы можно использовать для хранения различных данных, включая векторы положения, обычные векторы, координаты текстур в буфере вершин, индексы в буфере индекса или состоянии устройства. Элементы буфера могут включать упакованные значения данных (например, R8G8B8A8 значения поверхности), однобитовые целые числа или четыре 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 в зависимости от установленных GPU. В предыдущих версиях 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 использует три преобразования для изменения координат трехмерной модели в пиксельные координаты (пространство экрана). Эти преобразования — это преобразование мира, преобразование представления и преобразование проекции. Дополнительные сведения см. вобзоре преобразования .

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

Преобразование мира изменяет координаты из пространства модели, где вершины определяются относительно локального происхождения модели, к мировому пространству, где вершины определяются относительно источника, общего для всех объектов в сцене. По сути, мировая трансформация размещает модель в мире; отсюда и его название. Для получения дополнительной информации см. «World transform».

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

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

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

Преобразование проекции преобразует вид frustum в фигуру кубоида. Объём проекции — это трёхмерное пространственное тело в сцене, расположенное относительно камеры видового экрана. Окно просмотра — это прямоугольник 2D, в который проецируется трехмерная сцена. Дополнительные сведения см. в разделе Области просмотра и обрезка

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

Математически проекционное преобразование — это матрица, которая обычно является как масштабной, так и перспективной проекцией. Он работает как объектив камеры. Дополнительные сведения см. в преобразования проекции.

Состояние sampler

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

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

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

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

Класс BasicLoader

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

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