Платформа отрисовки II: отрисовка игры

Примечание

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

В разделе Платформа отрисовки I мы рассмотрели, как сведения о сцене и выводятся на экран для отображения. Теперь мы сделаем шаг назад и узнаем, как подготовить данные для отрисовки.

Примечание

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

Назначение

Краткое обобщение задачи. Требуется понять, как настроить базовую платформу отрисовки для отображения вывода графики для игр UWP на базе DirectX. Мы можем гибко сгруппировать необходимые процедуры в эти три шага.

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

В разделе Платформа отрисовки I: введение в отрисовку описано, как выполняется отрисовка графики, что охватывает шаги 1 и 3.

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

Проектирование обработчика

Обработчик отвечает за создание и поддержание всех объектов D3D11 и D2D, используемых для создания визуальных элементов игры. Класс GameRenderer — обработчик для этого примера игры и он спроектирован с учетом потребностей отрисовки этой игры.

Ниже перечислены некоторые основные понятия, которые можно использовать, чтобы спроектировать обработчик для вашей игры:

  • Так как API Direct3D 11 определены как API-интерфейсы COM, необходимо предоставить ссылки ComPtr на объекты, определяемые этими API. Эти объекты автоматически освобождаются, когда последняя ссылка выходит за пределы области при завершении работы приложения. Дополнительные сведения см. в статье ComPtr. Пример этих объектов: буферы констант, объекты шейдера — вершинный шейдер, пиксельный шейдер и объекты ресурсов шейдеров.
  • Буферы констант определяются в этом классе для хранения различных данных, необходимых для отрисовки.
    • Используйте несколько буферов констант с различной частотой обновления для уменьшения количества данных, которые необходимо посылать GPU для каждого кадра. В этом примере константы разделяются по разным буферам на основании частоты их обновления. Это лучшая методика программирования в Direct3D.
    • В этом примере игры определены 4 буфера констант.
      1. m_constantBufferNeverChanges содержит параметры освещения. Он задается однократно в методе FinalizeCreateGameDeviceResources и никогда не меняется.
      2. m_constantBufferChangeOnResize содержит матрицу проекции. Матрица проекции зависит от размера и пропорций окна. Он задается в CreateWindowSizeDependentResources и затем обновляется после загрузки ресурсов в методе FinalizeCreateGameDeviceResources. Если выполняется отрисовка в трехмерном пространстве, он также изменяется дважды для каждого кадра.
      3. m_constantBufferChangesEveryFrame содержит матрицу представления. Эта матрица зависит от положения камеры и направления взгляда (перпендикулярно проекции) и изменяется только один раз за каждый кадр в методе Render. Это было описано ранее в разделе Платформа отрисовки I: введение в отрисовку в описании метода GameRenderer::Render.
      4. m_constantBufferChangesEveryPrim содержит матрицу модели и свойства материала каждого примитива. Матрица модели преобразует вершины из локальных координат в мировые. Эти константы являются специфическими для каждого примитива и обновляются при каждом вызове метода рисования. Это было описано ранее в разделе Платформа отрисовки I: введение в отрисовку в подразделе Отрисовка примитивов.
  • В этом классе также определяются объекты ресурсов шейдеров, которые содержат текстуры для примитивов.
    • Некоторые текстуры предварительно определены (DDS — это формат файлов, который можно использовать для хранения сжатых и несжатых текстур. Текстуры DDS используются для стен и пола мира, а также сфер боеприпасов.)
    • В этом примере игры объектами ресурсов шейдера являются : m_sphereTexture, m_cylinderTexture, m_ceilingTexture, m_floorTexturem_wallsTexture.
  • Объекты шейдера определены в этом классе для расчета наших примитивов и текстур.
    • В этом примере игры объектами шейдера являются m_vertexShader, m_vertexShaderFlat и m_pixelShaderm_pixelShaderFlat.
    • Вершинный шейдер обрабатывает примитивы и базовое освещение, а построитель текстуры (иногда называемый фрагментным шейдером) обрабатывает текстуры и все пиксельные эффекты.
    • Есть две версии этих шейдеров (обычные и плоские) для визуализации различных примитивов. Причина наличия различных версий в том, что плоские версии гораздо проще и не используют световые блики, как и любые попиксельные эффекты освещения. Они используются для текстур стен и ускоряют прорисовку на менее мощных устройствах.

GameRenderer.h

Теперь рассмотрим код в объекте класса отрисовщика примера игры.

// Class handling the rendering of the game
class GameRenderer : public std::enable_shared_from_this<GameRenderer>
{
public:
    GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources);

    void CreateDeviceDependentResources();
    void CreateWindowSizeDependentResources();
    void ReleaseDeviceDependentResources();
    void Render();
    // --- end of async related methods section

    winrt::Windows::Foundation::IAsyncAction CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game);
    void FinalizeCreateGameDeviceResources();
    winrt::Windows::Foundation::IAsyncAction LoadLevelResourcesAsync();
    void FinalizeLoadLevelResources();

    Simple3DGameDX::IGameUIControl* GameUIControl() { return &m_gameInfoOverlay; };

    DirectX::XMFLOAT2 GameInfoOverlayUpperLeft()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.left, m_gameInfoOverlayRect.top);
    };
    DirectX::XMFLOAT2 GameInfoOverlayLowerRight()
    {
        return DirectX::XMFLOAT2(m_gameInfoOverlayRect.right, m_gameInfoOverlayRect.bottom);
    };
    bool GameInfoOverlayVisible() { return m_gameInfoOverlay.Visible(); }
    // --- end of rendering overlay section
...
private:
    // Cached pointer to device resources.
    std::shared_ptr<DX::DeviceResources>        m_deviceResources;

    ...

    // Shader resource objects
    winrt::com_ptr<ID3D11ShaderResourceView>    m_sphereTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_cylinderTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_ceilingTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_floorTexture;
    winrt::com_ptr<ID3D11ShaderResourceView>    m_wallsTexture;

    // Constant buffers
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferNeverChanges;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangeOnResize;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryFrame;
    winrt::com_ptr<ID3D11Buffer>                m_constantBufferChangesEveryPrim;

    // Texture sampler
    winrt::com_ptr<ID3D11SamplerState>          m_samplerLinear;

    // Shader objects: Vertex shaders and pixel shaders
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShader;
    winrt::com_ptr<ID3D11VertexShader>          m_vertexShaderFlat;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShader;
    winrt::com_ptr<ID3D11PixelShader>           m_pixelShaderFlat;
    winrt::com_ptr<ID3D11InputLayout>           m_vertexLayout;
};

Конструктор

Далее давайте рассмотрим конструктор GameRenderer примера игры и сравним его с конструктором Sample3DSceneRenderer , предоставленным в шаблоне приложения DirectX 11.

// Constructor method of the main rendering class object
GameRenderer::GameRenderer(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
    m_gameInfoOverlay(deviceResources),
    m_gameHud(deviceResources, L"Windows platform samples", L"DirectX first-person game sample")
{
    // m_gameInfoOverlay is a GameHud object to render text in the top left corner of the screen.
    // m_gameHud is Game info rendered as an overlay on the top-right corner of the screen,
    // for example hits, shots, and time.

    CreateDeviceDependentResources();
    CreateWindowSizeDependentResources();
}

Создание и загрузка графических ресурсов DirectX

В примере игры (и в шаблоне Приложения DirectX 11 (универсальное приложение Для Windows) Visual Studio создание и загрузка ресурсов игры реализуется с помощью следующих двух методов, которые вызываются из конструктора GameRenderer :

Метод CreateDeviceDependentResources

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

В нашем примере игры эти операции над объектами сцены вместо этого распределены между методами CreateGameDeviceResourcesAsync и FinalizeCreateGameDeviceResources.

Что входит в этот метод для этого примера игры?

  • Экземпляры переменных (m_gameResourcesLoaded = false и m_levelResourcesLoaded = false), которые указывают, были ли ресурсы загружены перед переходом на отрисовку, так как мы загружаем их асинхронно.
  • Поскольку отрисовка наложения и элементов HUD производятся в отдельных объектах класса, здесь необходимо вызвать методы GameHud::CreateDeviceDependentResources и GameInfoOverlay::CreateDeviceDependentResources.

Ниже представлен код для GameRenderer::CreateDeviceDependentResources.

// This method is called in GameRenderer constructor when it's created in GameMain constructor.
void GameRenderer::CreateDeviceDependentResources()
{
    // instantiate variables that indicate whether resources were loaded.
    m_gameResourcesLoaded = false;
    m_levelResourcesLoaded = false;

    // game HUD and overlay are design as separate class objects.
    m_gameHud.CreateDeviceDependentResources();
    m_gameInfoOverlay.CreateDeviceDependentResources();
}

Ниже приведен список методов, используемых для создания и загрузки ресурсов.

  • CreateDeviceDependentResources
    • CreateGameDeviceResourcesAsync (добавлено)
    • FinalizeCreateGameDeviceResources (добавлено)
  • CreateWindowSizeDependentResources

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

Создание обработчика

GameRenderer создается в конструкторе GameMain. Он также вызывает два других метода, CreateGameDeviceResourcesAsync и FinalizeCreateGameDeviceResources, добавляемые поддержки при создании и загрузке ресурсов.

GameMain::GameMain(std::shared_ptr<DX::DeviceResources> const& deviceResources) : ...
{
    m_deviceResources->RegisterDeviceNotify(this);

    // Creation of GameRenderer
    m_renderer = std::make_shared<GameRenderer>(m_deviceResources);

    ...

    ConstructInBackground();
}

winrt::fire_and_forget GameMain::ConstructInBackground()
{
    ...

    // Asynchronously initialize the game class and load the renderer device resources.
    // By doing all this asynchronously, the game gets to its main loop more quickly
    // and in parallel all the necessary resources are loaded on other threads.
    m_game->Initialize(m_controller, m_renderer);

    co_await m_renderer->CreateGameDeviceResourcesAsync(m_game);

    // The finalize code needs to run in the same thread context
    // as the m_renderer object was created because the D3D device context
    // can ONLY be accessed on a single thread.
    // co_await of an IAsyncAction resumes in the same thread context.
    m_renderer->FinalizeCreateGameDeviceResources();

    InitializeGameState();

    ...
}

Метод CreateGameDeviceResourcesAsync

CreateGameDeviceResourcesAsync вызывается из метода конструктора GameMain в цикле create_task , так как мы загружаем ресурсы игры асинхронно.

CreateDeviceResourcesAsync — метод, выполняющийся как отдельный набор асинхронных задач для загрузки ресурсов игры. Так как от него ожидается выполнение в отдельном потоке, у него есть доступ только к методам устройств Direct3D 11 (определенным ID3D11Device), но не методам контекста устройств (определенным на ID3D11DeviceContext); поэтому он не выполняет никакой визуализации.

Метод FinalizeCreateGameDeviceResources выполняется в основном потоке, и у него имеется доступ к методам контекста устройств Direct3D 11.

Принцип:

  • Используйте только методы ID3D11Device в CreateGameDeviceResourcesAsync, поскольку они не привязаны к потокам, то есть могут выполняться в любом потоке. Также предполагается, что они не будут выполняться в том же потоке, где создан GameRenderer.
  • Не используйте здесь методы из ID3D11DeviceContext, так как они должны выполняться в одном потоке и в том же потоке, что GameRenderer.
  • Используйте этот метод, чтобы создать буферы констант.
  • Используйте этот метод для загрузки текстур (например, DDS-файлов) и сведения шейдера (например, CSO-файлы) в шейдеры.

Этот метод используется в следующих целях:

  • Создайте 4 буфера констант: m_constantBufferNeverChanges, m_constantBufferChangeOnResize, m_constantBufferChangesEveryFramem_constantBufferChangesEveryPrim
  • Создание объекта состояния дискретизатора, инкапсулирующего информацию о дискретизации для текстуры
  • Создание группы задач, включающей все асинхронные задачи, созданные методом. Она ожидает завершения всех этих асинхронных задач и вызывает FinalizeCreateGameDeviceResources.
  • Создание загрузчика с помощью Basic Loader. Добавление операций асинхронной загрузки загрузчика в виде задач в ранее созданную группу задач.
  • Для загрузки используются такие методы, как BasicLoader::LoadShaderAsync и BasicLoader::LoadTextureAsync :
    • скомпилированные объекты шейдера (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso и PixelShaderFlat.cso). Дополнительные сведения см. в разделе Различные форматы файлов шейдера.
    • текстуры игры (Assets\seafloor.dds, metal_texture.dds, cellceiling.dds, cellfloor.dds, cellwall.dds).
IAsyncAction GameRenderer::CreateGameDeviceResourcesAsync(_In_ std::shared_ptr<Simple3DGame> game)
{
    auto lifetime = shared_from_this();

    // Create the device dependent game resources.
    // Only the d3dDevice is used in this method. It is expected
    // to not run on the same thread as the GameRenderer was created.
    // Create methods on the d3dDevice are free-threaded and are safe while any methods
    // in the d3dContext should only be used on a single thread and handled
    // in the FinalizeCreateGameDeviceResources method.
    m_game = game;

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    // Define D3D11_BUFFER_DESC. See
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc
    D3D11_BUFFER_DESC bd;
    ZeroMemory(&bd, sizeof(bd));

    // Create the constant buffers.
    bd.Usage = D3D11_USAGE_DEFAULT;
    ...

    // Create the constant buffers: m_constantBufferNeverChanges, m_constantBufferChangeOnResize,
    // m_constantBufferChangesEveryFrame, m_constantBufferChangesEveryPrim
    // CreateBuffer is used to create one of these buffers: vertex buffer, index buffer, or 
    // shader-constant buffer. For CreateBuffer API ref info, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer.
    winrt::check_hresult(
        d3dDevice->CreateBuffer(&bd, nullptr, m_constantBufferNeverChanges.put())
        );

    ...

    // Define D3D11_SAMPLER_DESC. For API ref, see
    // https://learn.microsoft.com/windows/win32/api/d3d11/ns-d3d11-d3d11_sampler_desc.
    D3D11_SAMPLER_DESC sampDesc;

    // ZeroMemory fills a block of memory with zeros. For API ref, see
    // https://learn.microsoft.com/previous-versions/windows/desktop/legacy/aa366920(v=vs.85).
    ZeroMemory(&sampDesc, sizeof(sampDesc));

    sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
    sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
    sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
    ...

    // Create a sampler-state object that encapsulates sampling information for a texture.
    // The sampler-state interface holds a description for sampler state that you can bind to any 
    // shader stage of the pipeline for reference by texture sample operations.
    winrt::check_hresult(
        d3dDevice->CreateSamplerState(&sampDesc, m_samplerLinear.put())
        );

    // Start the async tasks to load the shaders and textures.

    // Load compiled shader objects (VertextShader.cso, VertexShaderFlat.cso, PixelShader.cso, and PixelShaderFlat.cso).
    // The BasicLoader class is used to convert and load common graphics resources, such as meshes, textures, 
    // and various shader objects into the constant buffers. For more info, see
    // https://learn.microsoft.com/windows/uwp/gaming/complete-code-for-basicloader.
    BasicLoader loader{ d3dDevice };

    std::vector<IAsyncAction> tasks;

    uint32_t numElements = ARRAYSIZE(PNTVertexLayout);

    // Load shaders asynchronously with the shader and pixel data using the
    // BasicLoader::LoadShaderAsync method. Push these method calls into a list of tasks.
    tasks.push_back(loader.LoadShaderAsync(L"VertexShader.cso", PNTVertexLayout, numElements, m_vertexShader.put(), m_vertexLayout.put()));
    tasks.push_back(loader.LoadShaderAsync(L"VertexShaderFlat.cso", nullptr, numElements, m_vertexShaderFlat.put(), nullptr));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShader.cso", m_pixelShader.put()));
    tasks.push_back(loader.LoadShaderAsync(L"PixelShaderFlat.cso", m_pixelShaderFlat.put()));

    // Make sure the previous versions if any of the textures are released.
    m_sphereTexture = nullptr;
    ...

    // Load Game specific textures (Assets\\seafloor.dds, metal_texture.dds, cellceiling.dds,
    // cellfloor.dds, cellwall.dds).
    // Push these method calls also into a list of tasks.
    tasks.push_back(loader.LoadTextureAsync(L"Assets\\seafloor.dds", nullptr, m_sphereTexture.put()));
    ...

    // Simulate loading additional resources by introducing a delay.
    tasks.push_back([]() -> IAsyncAction { co_await winrt::resume_after(GameConstants::InitialLoadingDelay); }());

    // Returns when all the async tasks for loading the shader and texture assets have completed.
    for (auto&& task : tasks)
    {
        co_await task;
    }
}

Метод FinalizeCreateGameDeviceResources

Метод FinalizeCreateGameDeviceResources вызывается после выполнения всех задач загрузки ресурсов, которые находятся в методе CreateGameDeviceResourcesAsync.

  • Инициализируйте constantBufferNeverChanges со значениями положения и цвета источников света. Загружает исходные данные в буферы констант посредством вызова метода контекста устройства к ID3D11DeviceContext::UpdateSubresource.
  • После завершения загрузки асинхронно загружаемых ресурсов следует связать их с соответствующими объектами в игре.
  • Для каждого объекта в игре создайте сетку и материал с помощью загруженных текстур. Затем привяжите сетку и материал к игровому объекту.
  • Для игровых объектов в виде мишени текстура, состоящая из цветных концентрических кругов с числовыми значениями поверх них, не загружается из файла текстуры. Вместо этого она процедурно создается с помощью кода в TargetTexture.cpp. Класс TargetTexture создает необходимые ресурсы для рисования текстуры в находящийся вне экрана ресурс во время инициализации. Полученная текстура затем связывается с соответствующими игровыми объектами в виде мишеней.

В FinalizeCreateGameDeviceResources и CreateWindowSizeDependentResources есть похожие фрагменты кода для следующих элементов:

  • Используйте SetProjParams для обеспечения того, что камера использует верную матрицу проекции. Подробнее см. в статье Камера и пространство координат.
  • Обрабатывайте поворот экрана путем последующего умножения матрицы 3D-вращения на матрицу проекции камеры. Затем обновите буфер констант ConstantBufferChangeOnResize, используя полученную матрицу проекции.
  • Задайте m_gameResourcesLoadedлогическую глобальную переменную, чтобы указать, что ресурсы теперь загружены в буферы, готовые к следующему шагу. Помните, что мы инициализировали эту переменную со значением FALSE в методе-конструкторе GameRenderer с помощью метода GameRenderer::CreateDeviceDependentResources.
  • Если этот m_gameResourcesLoaded имеет значение TRUE, может выполняться отрисовка объектов сцены. Это рассматривается в разделе Платформа отрисовки I: введение в отрисовку в описании метода GameRenderer::Render.
// This method is called from the GameMain constructor.
// Make sure that 2D rendering is occurring on the same thread as the main rendering.
void GameRenderer::FinalizeCreateGameDeviceResources()
{
    // All asynchronously loaded resources have completed loading.
    // Now associate all the resources with the appropriate game objects.
    // This method is expected to run in the same thread as the GameRenderer
    // was created. All work will happen behind the "Loading ..." screen after the
    // main loop has been entered.

    // Initialize the Constant buffer with the light positions
    // These are handled here to ensure that the d3dContext is only
    // used in one thread.

    auto d3dDevice = m_deviceResources->GetD3DDevice();

    ConstantBufferNeverChanges constantBufferNeverChanges;
    constantBufferNeverChanges.lightPosition[0] = XMFLOAT4(3.5f, 2.5f, 5.5f, 1.0f);
    ...
    constantBufferNeverChanges.lightColor = XMFLOAT4(0.25f, 0.25f, 0.25f, 1.0f);

    // CPU copies data from memory (constantBufferNeverChanges) to a subresource 
    // created in non-mappable memory (m_constantBufferNeverChanges) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method. For UpdateSubresource API ref info, 
    // go to: https://msdn.microsoft.com/library/windows/desktop/ff476486.aspx
    // To learn more about what a subresource is, go to:
    // https://msdn.microsoft.com/library/windows/desktop/ff476901.aspx

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferNeverChanges.get(),
        0,
        nullptr,
        &constantBufferNeverChanges,
        0,
        0
        );

    // For the objects that function as targets, they have two unique generated textures.
    // One version is used to show that they have never been hit and the other is 
    // used to show that they have been hit.
    // TargetTexture is a helper class to procedurally generate textures for game
    // targets. The class creates the necessary resources to draw the texture into 
    // an off screen resource at initialization time.

    TargetTexture textureGenerator(
        d3dDevice,
        m_deviceResources->GetD2DFactory(),
        m_deviceResources->GetDWriteFactory(),
        m_deviceResources->GetD2DDeviceContext()
        );

    // CylinderMesh is a class derived from MeshObject and creates a ID3D11Buffer of
    // vertices and indices to represent a canonical cylinder (capped at
    // both ends) that is positioned at the origin with a radius of 1.0,
    // a height of 1.0 and with its axis in the +Z direction.
    // In the game sample, there are various types of mesh types:
    // CylinderMesh (vertical rods), SphereMesh (balls that the player shoots), 
    // FaceMesh (target objects), and WorldMesh (Floors and ceilings that define the enclosed area)

    auto cylinderMesh = std::make_shared<CylinderMesh>(d3dDevice, (uint16_t)26);
    ...

    // The Material class maintains the properties that represent how an object will
    // look when it is rendered.  This includes the color of the object, the
    // texture used to render the object, and the vertex and pixel shader that
    // should be used for rendering.

    auto cylinderMaterial = std::make_shared<Material>(
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(0.8f, 0.8f, 0.8f, .5f),
        XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f),
        15.0f,
        m_cylinderTexture.get(),
        m_vertexShader.get(),
        m_pixelShader.get()
        );

    ...

    // Attach the textures to the appropriate game objects.
    // We'll loop through all the objects that need to be rendered.
    for (auto&& object : m_game->RenderObjects())
    {
        if (object->TargetId() == GameConstants::WorldFloorId)
        {
            // Assign a normal material for the floor object.
            // This normal material uses the floor texture (cellfloor.dds) that was loaded asynchronously from
            // the Assets folder using BasicLoader::LoadTextureAsync method in the earlier 
            // CreateGameDeviceResourcesAsync loop

            object->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.5f, 0.5f, 0.5f, 1.0f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 1.0f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    150.0f,
                    m_floorTexture.get(),
                    m_vertexShaderFlat.get(),
                    m_pixelShaderFlat.get()
                    )
                );
            // Creates a mesh object called WorldFloorMesh and assign it to the floor object.
            object->Mesh(std::make_shared<WorldFloorMesh>(d3dDevice));
        }
        ...
        else if (auto cylinder = dynamic_cast<Cylinder*>(object.get()))
        {
            cylinder->Mesh(cylinderMesh);
            cylinder->NormalMaterial(cylinderMaterial);
        }
        else if (auto target = dynamic_cast<Face*>(object.get()))
        {
            const int bufferLength = 16;
            wchar_t str[bufferLength];
            int len = swprintf_s(str, bufferLength, L"%d", target->TargetId());
            auto string{ winrt::hstring(str, len) };

            winrt::com_ptr<ID3D11ShaderResourceView> texture;
            textureGenerator.CreateTextureResourceView(string, texture.put());
            target->NormalMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            texture = nullptr;
            textureGenerator.CreateHitTextureResourceView(string, texture.put());
            target->HitMaterial(
                std::make_shared<Material>(
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.8f, 0.8f, 0.8f, 0.5f),
                    XMFLOAT4(0.3f, 0.3f, 0.3f, 1.0f),
                    5.0f,
                    texture.get(),
                    m_vertexShader.get(),
                    m_pixelShader.get()
                    )
                );

            target->Mesh(targetMesh);
        }
        ...
    }

    // The SetProjParams method calculates the projection matrix based on input params and
    // ensures that the camera has been initialized with the right projection
    // matrix.  
    // The camera is not created at the time the first window resize event occurs.

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();
    m_game->GameCamera().SetProjParams(
        XM_PI / 2,
        renderTargetSize.Width / renderTargetSize.Height,
        0.01f,
        100.0f
        );

    // Make sure that the correct projection matrix is set in the ConstantBufferChangeOnResize buffer.

    // Get the 3D rotation transform matrix. We are handling screen rotations directly to eliminate an unaligned 
    // fullscreen copy. So it is necessary to post multiply the 3D rotation matrix to the camera's projection matrix
    // to get the projection matrix that we need.

    auto orientation = m_deviceResources->GetOrientationTransform3D();

    ConstantBufferChangeOnResize changesOnResize;

    // The matrices are transposed due to the shader code expecting the matrices in the opposite
    // row/column order from the DirectX math library.

    // XMStoreFloat4x4 takes a matrix and writes the components out to sixteen single-precision floating-point values at the given address. 
    // The most significant component of the first row vector is written to the first four bytes of the address, 
    // followed by the second most significant component of the first row, and so on. The second row is then written out in a 
    // like manner to memory beginning at byte 16, followed by the third row to memory beginning at byte 32, and finally 
    // the fourth row to memory beginning at byte 48. For more API ref info, go to: 
    // https://msdn.microsoft.com/library/windows/desktop/microsoft.directx_sdk.storing.xmstorefloat4x4.aspx

    XMStoreFloat4x4(
        &changesOnResize.projection,
        XMMatrixMultiply(
            XMMatrixTranspose(m_game->GameCamera().Projection()),
            XMMatrixTranspose(XMLoadFloat4x4(&orientation))
            )
        );

    // UpdateSubresource method instructs CPU to copy data from memory (changesOnResize) to a subresource 
    // created in non-mappable memory (m_constantBufferChangeOnResize ) which was created in the earlier 
    // CreateGameDeviceResourcesAsync method.

    m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
        m_constantBufferChangeOnResize.get(),
        0,
        nullptr,
        &changesOnResize,
        0,
        0
        );

    // Finally we set the m_gameResourcesLoaded as TRUE, so we can start rendering.
    m_gameResourcesLoaded = true;
}

Метод CreateWindowSizeDependentResource

Методы CreateWindowSizeDependentResources вызываются при каждом изменении размера окна, ориентации, отрисовки в стереорежиме и разрешения. В примере игры обновляется матрица проекции в ConstantBufferChangeOnResize.

Ресурсы размера окна обновляются следующим образом.

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

В этом примере игры несколько вызовов методов совпадают с методом FinalizeCreateGameDeviceResources . Пошаговый анализ кода приводится в предыдущем разделе.

Изменение отрисовки размера окна наложения и элементов HUD рассматривается в разделе Добавление пользовательского интерфейса.

// Initializes view parameters when the window size changes.
void GameRenderer::CreateWindowSizeDependentResources()
{
    // Game HUD and overlay window size rendering adjustments are done here
    // but they'll be covered in the UI section instead.

    m_gameHud.CreateWindowSizeDependentResources();

    ...

    auto d3dContext = m_deviceResources->GetD3DDeviceContext();
    // In Sample3DSceneRenderer::CreateWindowSizeDependentResources, we had:
    // Size outputSize = m_deviceResources->GetOutputSize();

    auto renderTargetSize = m_deviceResources->GetRenderTargetSize();

    ...

    m_gameInfoOverlay.CreateWindowSizeDependentResources(m_gameInfoOverlaySize);

    if (m_game != nullptr)
    {
        // Similar operations as the last section of FinalizeCreateGameDeviceResources method
        m_game->GameCamera().SetProjParams(
            XM_PI / 2, renderTargetSize.Width / renderTargetSize.Height,
            0.01f,
            100.0f
            );

        XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();

        ConstantBufferChangeOnResize changesOnResize;
        XMStoreFloat4x4(
            &changesOnResize.projection,
            XMMatrixMultiply(
                XMMatrixTranspose(m_game->GameCamera().Projection()),
                XMMatrixTranspose(XMLoadFloat4x4(&orientation))
                )
            );

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

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

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