Добавление визуального содержимого в пример Marble Maze

В этом документе описывается, как игра Marble Maze использует Direct3D и Direct2D в среде приложения универсальная платформа Windows (UWP), чтобы вы могли узнать шаблоны и адаптировать их при работе с собственным содержимым игры. Сведения о том, как визуальные игровые компоненты вписываются в общую структуру приложения Marble Maze, см . в статье "Структура приложения Marble Maze".

Мы выполнили следующие основные шаги, как мы разработали визуальные аспекты Marble Maze:

  1. Создайте базовую платформу, которая инициализирует среды Direct3D и Direct2D.
  2. Используйте программы редактирования изображений и моделей для проектирования 2D-ресурсов и трехмерных ресурсов, которые отображаются в игре.
  3. Убедитесь, что 2D и трехмерные ресурсы правильно загружаются и отображаются в игре.
  4. Интегрируйте вершины и шейдеры пикселей, повышающие качество визуального качества игровых активов.
  5. Интегрируйте логику игры, например анимацию и входные данные пользователя.

Мы также сосредоточились сначала на добавлении трехмерных ресурсов, а затем на 2D-ресурсах. Например, мы сосредоточились на основной игровой логике, прежде чем добавить систему меню и таймер.

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

Примечание.

Пример кода, соответствующий этому документу, найден в примере игры DirectX Marble Maze.

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

  • Добавление игрового содержимого обычно включает множество шагов. Эти действия также часто требуют итерации. Разработчики игр часто сосредоточены на добавлении трехмерного игрового содержимого, а затем на добавлении 2D-содержимого.
  • Охватите больше клиентов и предоставьте им весь большой опыт, поддерживая максимальный диапазон графического оборудования как можно скорее.
  • Четко отделяйте форматы времени разработки и времени выполнения. Структурируйте ресурсы во время разработки, чтобы обеспечить максимальную гибкость и включить быстрые итерации содержимого. Форматирование и сжатие ресурсов для загрузки и отрисовки как можно эффективнее во время выполнения.
  • Вы создаете устройства Direct3D и Direct2D в приложении UWP так же, как и в классическом классическом приложении Windows. Одно из важных различий заключается в том, как цепочка буферов связана с окном вывода.
  • При разработке игры убедитесь, что выбранный формат сетки поддерживает ключевые сценарии. Например, если для игры требуется столкновение, убедитесь, что вы можете получить данные о столкновении из сетки.
  • Отделите логику игры от логики отрисовки, сначала обновив все объекты сцены перед их отрисовкой.
  • Обычно вы рисуете трехмерные объекты сцены, а затем все трехмерные объекты, которые отображаются перед сценой.
  • Синхронизируйте рисование с вертикальным пустым, чтобы убедиться, что игра не тратит время на рисование кадров, которые никогда не будут отображаться на экране. Вертикальное пустое — это время между завершением рисования одного кадра в мониторе и началом следующего кадра.

Начало работы с графикой DirectX

Когда мы планировали игру Marble Maze универсальная платформа Windows (UWP), мы выбрали C++ и Direct3D 11.1, так как они являются отличным выбором для создания трехмерных игр, требующих максимального контроля над отрисовкой и высокой производительностью. DirectX 11.1 поддерживает оборудование от DirectX 9 до DirectX 11 и, следовательно, помогает повысить эффективность работы клиентов, так как вам не нужно переписать код для каждой из предыдущих версий DirectX.

Marble Maze использует Direct3D 11.1 для отрисовки трехмерных игровых активов, а именно мрамора и лабиринта. Marble Maze также использует Direct2D, DirectWrite и компонент образов Windows (WIC) для рисования 2D игровых ресурсов, таких как меню и таймер.

Разработка игр требует планирования. Если вы не знакомы с графикой DirectX, рекомендуется прочитать DirectX: приступая к работе с основными понятиями создания игры UWP DirectX. Как вы читаете этот документ и работаете с исходным кодом Marble Maze, вы можете ознакомиться со следующими ресурсами для получения подробных сведений о графике DirectX:

  • Графика Direct3D 11: описывает Direct3D 11, мощный аппаратный трехмерный графический API для отрисовки трехмерной геометрии на платформе Windows.
  • Direct2D: описывает Direct2D, аппаратно-ускоренный, 2D-графический API, обеспечивающий высокую производительность и высококачественную отрисовку для 2D геометрии, растровых изображений и текста.
  • DirectWrite: описывает DirectWrite, который поддерживает высококачественную отрисовку текста.
  • Компонент образов Windows: описывает WIC, расширяемую платформу, которая предоставляет низкоуровневый API для цифровых изображений.

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

Direct3D 11 представляет парадигму именованных уровней компонентов. Уровень компонентов — это хорошо определенный набор функциональных возможностей GPU. Используйте уровни функций, чтобы нацелиться на игру для запуска на более ранних версиях оборудования Direct3D. Marble Maze поддерживает уровень компонентов 9.1, так как он не требует дополнительных функций из более высоких уровней. Рекомендуется поддерживать максимальный диапазон оборудования и масштабировать игровое содержимое, чтобы ваши клиенты имели высокий или низкий уровень работы компьютеров. Дополнительные сведения об уровнях компонентов см. в разделе Direct3D 11 на оборудовании Downlevel.

Инициализация Direct3D и Direct2D

Устройство представляет адаптер отображения. Вы создаете устройства Direct3D и Direct2D в приложении UWP так же, как и в классическом классическом приложении Windows. Основное различие заключается в том, как вы подключаете цепочку буферов Direct3D к системе окон.

Класс DeviceResources является основой для управления Direct3D и Direct2D. Этот класс обрабатывает общую инфраструктуру, а не ресурсы, относящиеся к игре. Marble Maze определяет класс MarbleMazeMain для обработки ресурсов, относящихся к игре, который имеет ссылку на объект DeviceResources , чтобы предоставить ему доступ к Direct3D и Direct2D.

Во время инициализации конструктор DeviceResources создает независимые от устройства ресурсы и устройства Direct3D и Direct2D.

// Initialize the Direct3D resources required to run. 
DX::DeviceResources::DeviceResources() :
    m_screenViewport(),
    m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1),
    m_d3dRenderTargetSize(),
    m_outputSize(),
    m_logicalSize(),
    m_nativeOrientation(DisplayOrientations::None),
    m_currentOrientation(DisplayOrientations::None),
    m_dpi(-1.0f),
    m_deviceNotify(nullptr)
{
    CreateDeviceIndependentResources();
    CreateDeviceResources();
}

Класс DeviceResources отделяет эту функцию, чтобы она была проще реагировать при изменении среды. Например, он вызывает метод CreateWindowSizeDependentResources при изменении размера окна.

Инициализация фабрик Direct2D, DirectWrite и WIC

Метод DeviceResources::CreateDeviceIndependentResources создает фабрики для Direct2D, DirectWrite и WIC. В графике DirectX фабрики являются отправной точкой для создания графических ресурсов. Marble Maze указывает D2D1_FACTORY_TYPE_SINGLE_THREADED , так как выполняет все рисование на основном потоке.

// These are the resources required independent of hardware. 
void DX::DeviceResources::CreateDeviceIndependentResources()
{
    // Initialize Direct2D resources.
    D2D1_FACTORY_OPTIONS options;
    ZeroMemory(&options, sizeof(D2D1_FACTORY_OPTIONS));

#if defined(_DEBUG)
    // If the project is in a debug build, enable Direct2D debugging via SDK Layers.
    options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
#endif

    // Initialize the Direct2D Factory.
    DX::ThrowIfFailed(
        D2D1CreateFactory(
            D2D1_FACTORY_TYPE_SINGLE_THREADED,
            __uuidof(ID2D1Factory2),
            &options,
            &m_d2dFactory
            )
        );

    // Initialize the DirectWrite Factory.
    DX::ThrowIfFailed(
        DWriteCreateFactory(
            DWRITE_FACTORY_TYPE_SHARED,
            __uuidof(IDWriteFactory2),
            &m_dwriteFactory
            )
        );

    // Initialize the Windows Imaging Component (WIC) Factory.
    DX::ThrowIfFailed(
        CoCreateInstance(
            CLSID_WICImagingFactory2,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_wicFactory)
            )
        );
}

Создание устройств Direct3D и Direct2D

Метод DeviceResources::CreateDeviceResources вызывает D3D11CreateDevice для создания объекта устройства, представляющего адаптер отображения Direct3D. Так как Marble Maze поддерживает уровень компонентов 9.1 и выше, метод DeviceResources::CreateDeviceResources задает уровни 9.1–1 в массиве featureLevels . Direct3D описывает список по порядку и предоставляет приложению первый уровень компонентов, доступный. Таким образом, записи массива D3D_FEATURE_LEVEL перечислены от самого высокого до самого низкого, чтобы приложение получите самый высокий уровень возможностей. Метод DeviceResources::CreateDeviceResources получает устройство Direct3D 11.1, запрашивая устройство Direct3D 11, возвращаемое из D3D11CreateDevice.

// This flag adds support for surfaces with a different color channel ordering
// than the API default. It is required for compatibility with Direct2D.
UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
    if (DX::SdkLayersAvailable())
    {
        // If the project is in a debug build, enable debugging via SDK Layers 
        // with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
    }
#endif

// This array defines the set of DirectX hardware feature levels this app will support.
// Note the ordering should be preserved.
// Don't forget to declare your application's minimum required feature level in its
// description.  All applications are assumed to support 9.1 unless otherwise stated.
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_1,
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_3,
    D3D_FEATURE_LEVEL_9_2,
    D3D_FEATURE_LEVEL_9_1
};

// Create the Direct3D 11 API device object and a corresponding context.
ComPtr<ID3D11Device> device;
ComPtr<ID3D11DeviceContext> context;

HRESULT hr = D3D11CreateDevice(
    nullptr,                    // Specify nullptr to use the default adapter.
    D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
    0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
    creationFlags,              // Set debug and Direct2D compatibility flags.
    featureLevels,              // List of feature levels this app can support.
    ARRAYSIZE(featureLevels),   // Size of the list above.
    D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for UWP apps.
    &device,                    // Returns the Direct3D device created.
    &m_d3dFeatureLevel,         // Returns feature level of device created.
    &context                    // Returns the device immediate context.
    );

if (FAILED(hr))
{
    // If the initialization fails, fall back to the WARP device.
    // For more information on WARP, see:
    // https://go.microsoft.com/fwlink/?LinkId=286690
    DX::ThrowIfFailed(
        D3D11CreateDevice(
            nullptr,
            D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device.
            0,
            creationFlags,
            featureLevels,
            ARRAYSIZE(featureLevels),
            D3D11_SDK_VERSION,
            &device,
            &m_d3dFeatureLevel,
            &context
            )
        );
}

// Store pointers to the Direct3D 11.1 API device and immediate context.
DX::ThrowIfFailed(
    device.As(&m_d3dDevice)
    );

DX::ThrowIfFailed(
    context.As(&m_d3dContext)
    );

Метод DeviceResources::CreateDeviceResources затем создает устройство Direct2D. Direct2D использует инфраструктуру графики Microsoft DirectX (DXGI) для взаимодействия с Direct3D. DXGI позволяет совместно использовать поверхности памяти видео между графическими средами выполнения. Marble Maze использует базовое устройство DXGI с устройства Direct3D для создания устройства Direct2D из фабрики Direct2D.

// Create the Direct2D device object and a corresponding context.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
    m_d3dDevice.As(&dxgiDevice)
    );

DX::ThrowIfFailed(
    m_d2dFactory->CreateDevice(dxgiDevice.Get(), &m_d2dDevice)
    );

DX::ThrowIfFailed(
    m_d2dDevice->CreateDeviceContext(
        D2D1_DEVICE_CONTEXT_OPTIONS_NONE,
        &m_d2dContext
        )
    );

Дополнительные сведения о DXGI и взаимодействии между Direct2D и Direct3D см. в разделе "Обзор DXGI" и "Общие сведения о взаимодействии Direct2D и Direct3D".

Связывание Direct3D с представлением

Метод DeviceResources::CreateWindowSizeDependentResources создает графические ресурсы, зависящие от заданного размера окна, например цепочки буферов и целевых объектов отрисовки Direct3D и Direct2D. Одним из важных способов отличия приложения DirectX UWP от классического приложения является то, как цепочка буферов связана с окном вывода. Цепочка буферов отвечает за отображение буфера, на котором устройство отрисовывается на мониторе. Структура приложения Marble Maze описывает, как система окон для приложения UWP отличается от классического приложения. Так как приложение UWP не работает с объектами HWND , Marble Maze должно использовать метод IDXGIFactory2::CreateSwapChainForCoreWindow , чтобы связать выходные данные устройства с представлением. В следующем примере показана часть метода DeviceResources::CreateWindowSizeDependentResources, создающего цепочку буферов .

// Obtain the final swap chain for this window from the DXGI factory.
DX::ThrowIfFailed(
    dxgiFactory->CreateSwapChainForCoreWindow(
        m_d3dDevice.Get(),
        reinterpret_cast<IUnknown*>(m_window.Get()),
        &swapChainDesc,
        nullptr,
        &m_swapChain
        )
    );

Чтобы свести к минимуму потребление энергии, что важно сделать на устройствах с питанием от батареи, таких как ноутбуки и планшеты, метод DeviceResources::CreateWindowSizeDependentResources вызывает метод IDXGIDevice1::SetMaximumFrameLatency, чтобы убедиться, что игра отображается только после вертикального пустого. Синхронизация с вертикальным пустым описана более подробно в разделе "Презентация сцены " в этом документе.

// Ensure that DXGI does not queue more than one frame at a time. This both 
// reduces latency and ensures that the application will only render after each
// VSync, minimizing power consumption.
DX::ThrowIfFailed(
    dxgiDevice->SetMaximumFrameLatency(1)
    );

Метод DeviceResources::CreateWindowSizeDependentResources инициализирует графические ресурсы таким образом, что работает для большинства игр.

Примечание.

Представление терминов имеет другое значение в среда выполнения Windows, чем в Direct3D. В среда выполнения Windows представление ссылается на коллекцию параметров пользовательского интерфейса для приложения, включая область отображения и поведение входных данных, а также поток, который он использует для обработки. При создании представления необходимо указать конфигурацию и параметры. Процесс настройки представления приложения описан в структуре приложения Marble Maze. В Direct3D представление терминов имеет несколько значений. Представление ресурсов определяет подресурсы, к которым может получить доступ ресурс. Например, если объект текстуры связан с представлением ресурсов шейдера, этот шейдер может позже получить доступ к текстуре. Одним из преимуществ представления ресурсов является то, что данные можно интерпретировать различными способами на разных этапах в конвейере отрисовки. Дополнительные сведения о представлениях ресурсов см. в разделе "Представления ресурсов". При использовании в контексте матрицы преобразования представления или преобразования представления представление ссылается на расположение и ориентацию камеры. Преобразование представления перемещает объекты во всем мире по расположению и ориентации камеры. Дополнительные сведения о преобразованиях представлений см. в разделе "Преобразование представления" (Direct3D 9). Как Marble Maze использует представления ресурсов и матриц, подробно описаны в этом разделе.

 

Загрузка ресурсов сцены

Marble Maze использует класс BasicLoader, объявленный в BasicLoader.h, для загрузки текстур и шейдеров. Marble Maze использует класс SDKMesh для загрузки трехмерных сетк для лабиринта и мрамора.

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

Загрузка 2D-наложения и пользовательского интерфейса

В Marble Maze наложение — это изображение, которое отображается в верхней части экрана. Наложение всегда отображается перед сценой. В Marble Maze наложение содержит логотип Windows и пример игры DirectX Marble Maze. Управление наложением выполняется классом SampleOverlay, который определен в SampleOverlay.h. Хотя мы используем наложение в рамках примеров Direct3D, этот код можно адаптировать для отображения любого изображения, отображаемого перед сценой.

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

Пользовательский интерфейс состоит из 2D-компонентов, таких как меню и экраны головы (HUD), которые отображаются перед сценой. Marble Maze определяет следующие элементы пользовательского интерфейса:

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

Marble Maze определяет элементы пользовательского интерфейса для конкретной игры в UserInterface.h. Marble Maze определяет класс ElementBase как базовый тип для всех элементов пользовательского интерфейса. Класс ElementBase определяет такие атрибуты, как размер, положение, выравнивание и видимость элемента пользовательского интерфейса. Он также определяет, как обновляются и отрисовываются элементы.

class ElementBase
{
public:
    virtual void Initialize() { }
    virtual void Update(float timeTotal, float timeDelta) { }
    virtual void Render() { }

    void SetAlignment(AlignType horizontal, AlignType vertical);
    virtual void SetContainer(const D2D1_RECT_F& container);
    void SetVisible(bool visible);

    D2D1_RECT_F GetBounds();

    bool IsVisible() const { return m_visible; }

protected:
    ElementBase();

    virtual void CalculateSize() { }

    Alignment       m_alignment;
    D2D1_RECT_F     m_container;
    D2D1_SIZE_F     m_size;
    bool            m_visible;
};

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

Примечание.

Так как XAML позволяет более легко создавать сложные пользовательские интерфейсы, например те, которые находятся в играх моделирования и стратегии, рассмотрите, следует ли использовать XAML для определения пользовательского интерфейса. Сведения о разработке пользовательского интерфейса в XAML в игре DirectX UWP см. в разделе "Расширение примера игры", который относится к примеру игры DirectX 3D.

 

Загрузка шейдеров

Marble Maze использует метод BasicLoader::LoadShader для загрузки шейдера из файла.

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

Marble Maze использует вершины и шейдеры пикселей. Шейдер вершин всегда работает на одной входной вершине и создает одну вершину в качестве выходных данных. Шейдер пикселей принимает числовые значения, данные текстуры, интерполированные значения на вершины и другие данные для получения цвета пикселя в качестве выходных данных. Так как шейдер преобразует один элемент одновременно, графическое оборудование, которое предоставляет несколько конвейеров шейдеров, может параллельно обрабатывать наборы элементов. Число параллельных конвейеров, доступных gpu, может быть значительно больше, чем число, доступное ЦП. Поэтому даже базовые шейдеры могут значительно повысить пропускную способность.

Метод MarbleMazeMain::LoadDeferredResources загружает один шейдер вершин и один шейдер пикселей после загрузки наложения. Версии этих шейдеров во время разработки определяются в BasicVertexShader.hlsl и BasicPixelShader.hlsl соответственно. Marble Maze применяет эти шейдеры как к мячу, так и к лабиринту во время этапа отрисовки.

Проект Marble Maze включает как hlsl (формат времени разработки), так и cso (формат времени выполнения) файлов шейдеров. Во время сборки Visual Studio использует компилятор эффектов fxc.exe для компиляции исходного файла hlsl в двоичный шейдер CSO. Дополнительные сведения о средстве компилятора эффектов см. в разделе "Средство компилятора эффектов".

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

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

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

float4 main(sPSInput input) : SV_TARGET
{
    float3 lightDirection = float3(0, 0, -1);
    float3 ambientColor = float3(0.43, 0.31, 0.24);
    float3 lightColor = 1 - ambientColor;
    float spotRadius = 50;

    // Basic ambient (Ka) and diffuse (Kd) lighting from above.
    float3 N = normalize(input.norm);
    float NdotL = dot(N, lightDirection);
    float Ka = saturate(NdotL + 1);
    float Kd = saturate(NdotL);

    // Spotlight.
    float3 vec = input.worldPos - marblePosition;
    float dist2D = sqrt(dot(vec.xy, vec.xy));
    Kd = Kd * saturate(spotRadius / dist2D);

    // Shadowing from ball.
    if (input.worldPos.z > marblePosition.z)
        Kd = Kd * saturate(dist2D / (marbleRadius * 1.5));

    // Diffuse reflection of light off ball.
    float dist3D = sqrt(dot(vec, vec));
    float3 V = normalize(vec);
    Kd += saturate(dot(-V, N)) * saturate(dot(V, lightDirection))
        * saturate(marbleRadius / dist3D);

    // Final composite.
    float4 diffuseTexture = Texture.Sample(Sampler, input.tex);
    float3 color = diffuseTexture.rgb * ((ambientColor * Ka) + (lightColor * Kd));
    return float4(color * lightStrength, diffuseTexture.a);
}

Предупреждение

Скомпилированный шейдер пикселей содержит 32 арифметических инструкций и 1 инструкции текстуры. Этот шейдер должен хорошо работать на настольных компьютерах или более высокопроизводительных планшетах. Однако некоторые компьютеры могут не обрабатывать этот шейдер и по-прежнему предоставлять интерактивную частоту кадров. Рассмотрим типичное оборудование целевой аудитории и создайте шейдеры для удовлетворения возможностей этого оборудования.

 

Метод MarbleMazeMain::LoadDeferredResources использует метод BasicLoader::LoadShader для загрузки шейдеров. В следующем примере загружается шейдер вершин. Формат времени выполнения для этого шейдера — BasicVertexShader.cso. Переменная элемента m_vertexShader — это объект ID3D11VertexShader .

BasicLoader^ loader = ref new BasicLoader(m_deviceResources->GetD3DDevice());

D3D11_INPUT_ELEMENT_DESC layoutDesc [] =
{
    { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
m_vertexStride = 44; // must set this to match the size of layoutDesc above

Platform::String^ vertexShaderName = L"BasicVertexShader.cso";
loader->LoadShader(
    vertexShaderName,
    layoutDesc,
    ARRAYSIZE(layoutDesc),
    &m_vertexShader,
    &m_inputLayout
    );

Переменная элемента m_inputLayout — это объект ID3D11InputLayout . Объект макета ввода инкапсулирует входное состояние этапа сборщика входных данных (IA). Одно из заданий этапа IA заключается в том, чтобы сделать шейдеры более эффективными с помощью системных значений, также известных как семантика, для обработки только тех примитивов или вершин, которые еще не обработаны.

Используйте метод ID3D11Device::CreateInputLayout для создания макета ввода из массива описаний входных элементов. Массив содержит один или несколько входных элементов; каждый входной элемент описывает один элемент вершинных данных из одного буфера вершин. Весь набор описаний входных элементов описывает все элементы данных вершин из всех буферов вершин, которые будут привязаны к этапу IA.

LayoutDesc в приведенном выше фрагменте кода показывает описание макета, которое использует Marble Maze. Описание макета описывает буфер вершин, содержащий четыре элемента данных вершины. Важными частями каждой записи в массиве являются семантические имена, формат данных и смещение байтов. Например, элемент POSITION указывает позицию вершин в пространстве объектов. Он начинается с смещения 0 байта и содержит три компонента с плавающей запятой (в общей сложности 12 байтов). Элемент NORMAL задает обычный вектор. Он начинается с смещения 12 байтов, так как он отображается непосредственно после позиции в макете, для которой требуется 12 байтов. Элемент NORMAL содержит четырехкомпонентное 32-разрядное целое число без знака.

Сравните макет ввода со структурой sVSInput , определенной шейдером вершин, как показано в следующем примере. Структура sVSInput определяет элементы POSITION, NORMAL и TEXCOORD0 . Среда выполнения DirectX сопоставляет каждый элемент макета с входной структурой, определенной шейдером.

struct sVSInput
{
    float3 pos : POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
};

struct sPSInput
{
    float4 pos : SV_POSITION;
    float3 norm : NORMAL;
    float2 tex : TEXCOORD0;
    float3 worldPos : TEXCOORD1;
};

sPSInput main(sVSInput input)
{
    sPSInput output;
    float4 temp = float4(input.pos, 1.0f);
    temp = mul(temp, model);
    output.worldPos = temp.xyz / temp.w;
    temp = mul(temp, view);
    temp = mul(temp, projection);
    output.pos = temp;
    output.tex = input.tex;
    output.norm = mul(float4(input.norm, 0.0f), model).xyz;
    return output;
}

Семантика документа подробно описывает каждую из доступных семантик.

Примечание.

В макете можно указать дополнительные компоненты, которые не используются для предоставления общего доступа к одному макету нескольких шейдеров. Например, элемент TANGENT не используется шейдером. Элемент TANGENT можно использовать, если вы хотите экспериментировать с такими методами, как обычное сопоставление. Используя обычное сопоставление, также известное как сопоставление ударов, можно создать эффект ударов на поверхностях объектов. Дополнительные сведения о сопоставлении ударов см. в разделе "Сопоставление ударов" (Direct3D 9).

 

Дополнительные сведения о стадии входной сборки см. в разделе "Этап входных сборок" и "Начало работы с этапом входных сборок".

Процесс использования вершин и шейдеров пикселей для отрисовки сцены описан в разделе "Отрисовка сцены " далее в этом документе.

Создание буфера констант

Буфер Direct3D группируют коллекцию данных. Буфер констант — это тип буфера, который можно использовать для передачи данных шейдерам. Marble Maze использует буфер константы для хранения представления модели (или мира) и матриц проекции для активного объекта сцены.

В следующем примере показано, как метод MarbleMazeMain::LoadDeferredResources создает буфер констант, который будет хранить данные матрицы позже. В примере создается структура D3D11_BUFFER_DESC, которая использует флаг D3D11_BIND_CONSTANT_BUFFER для указания использования в качестве буфера констант. В этом примере эта структура передается в метод ID3D11Device::CreateBuffer . Переменная m_constantBuffer — это объект ID3D11Buffer .

// Create the constant buffer for updating model and camera data.
D3D11_BUFFER_DESC constantBufferDesc = {0};

// Multiple of 16 bytes
constantBufferDesc.ByteWidth = ((sizeof(ConstantBuffer) + 15) / 16) * 16;

constantBufferDesc.Usage               = D3D11_USAGE_DEFAULT;
constantBufferDesc.BindFlags           = D3D11_BIND_CONSTANT_BUFFER;
constantBufferDesc.CPUAccessFlags      = 0;
constantBufferDesc.MiscFlags           = 0;

// This will not be used as a structured buffer, so this parameter is ignored.
constantBufferDesc.StructureByteStride = 0;

DX::ThrowIfFailed(
    m_deviceResources->GetD3DDevice()->CreateBuffer(
        &constantBufferDesc,
        nullptr,    // leave the buffer uninitialized
        &m_constantBuffer
        )
    );

Метод MarbleMazeMain::Update позже обновляет объекты ConstantBuffer , один для лабиринта и один для мрамора. Затем метод MarbleMazeMain::Render привязывает каждый объект ConstantBuffer к буферу констант перед отображением каждого объекта. В следующем примере показана структура ConstantBuffer , которая находится в MarbleMazeMain.h.

// Describes the constant buffer that draws the meshes.
struct ConstantBuffer
{
    XMFLOAT4X4 model;
    XMFLOAT4X4 view;
    XMFLOAT4X4 projection;

    XMFLOAT3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Чтобы лучше понять, как буферы констант сопоставляются с кодом шейдера, сравните структуру ConstantBuffer в MarbleMazeMain.h с буфером константы ConstantBuffer , который определяется шейдером вершин в BasicVertexShader.hlsl:

cbuffer ConstantBuffer : register(b0)
{
    matrix model;
    matrix view;
    matrix projection;
    float3 marblePosition;
    float marbleRadius;
    float lightStrength;
};

Макет структуры ConstantBuffer соответствует объекту cbuffer. Переменная cbuffer указывает регистр b0, что означает, что данные буфера констант хранятся в регистре 0. Метод MarbleMazeMain::Render указывает регистр 0 при активации буфера константы. Этот процесс подробно описан далее в этом документе.

Дополнительные сведения о буферах констант см. в разделе "Введение в буферы" в Direct3D 11. Дополнительные сведения о регистрации ключевое слово см. в разделе "Регистрация".

Загрузка сетки

Marble Maze использует пакет SDK-Mesh в качестве формата времени выполнения, так как этот формат предоставляет базовый способ загрузки данных сетки для примеров приложений. Для использования в рабочей среде следует использовать формат сетки, соответствующий конкретным требованиям игры.

Метод MarbleMazeMain::LoadDeferredResources загружает данные сетки после загрузки вершин и шейдеров пикселей. Сетка — это коллекция данных вершин, которые часто включают такие сведения, как позиции, обычные данные, цвета, материалы и координаты текстур. Сетки обычно создаются в программном обеспечении 3D-разработки и поддерживаются в файлах, которые отделены от кода приложения. Мрамор и лабиринт являются двумя примерами сеток, которые использует игра.

Marble Maze использует класс SDKMesh для управления сетками. Этот класс объявлен в SDKMesh.h. ПАКЕТ SDKMesh предоставляет методы для загрузки, отрисовки и уничтожения данных сетки.

Важно!

Marble Maze использует формат SDK-Mesh и предоставляет класс SDKMesh только для иллюстрации. Хотя формат SDK-Mesh полезен для обучения, и для создания прототипов, это очень базовый формат, который может не соответствовать требованиям большинства разработчиков игр. Рекомендуется использовать формат сетки, соответствующий конкретным требованиям игры.

 

В следующем примере показано, как метод MarbleMazeMain::LoadDeferredResources использует метод SDKMesh::Create для загрузки данных сетки для лабиринта и мяча.

// Load the meshes.
DX::ThrowIfFailed(
    m_mazeMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\maze1.sdkmesh",
        false
        )
    );

DX::ThrowIfFailed(
    m_marbleMesh.Create(
        m_deviceResources->GetD3DDevice(),
        L"Media\\Models\\marble2.sdkmesh",
        false
        )
    );

Загрузка данных о столкновении

Хотя в этом разделе не уделяется внимание тому, как Marble Maze реализует моделирование физики между мрамором и лабиринтом, обратите внимание, что геометрия сетки для системы физики считывается при загрузке сетки.

// Extract mesh geometry for the physics system.
DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_walls",
        m_collision.m_wallTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_Floor",
        m_collision.m_groundTriList
        )
    );

DX::ThrowIfFailed(
    ExtractTrianglesFromMesh(
        m_mazeMesh,
        "Mesh_floorSides",
        m_collision.m_floorTriList
        )
    );

m_physics.SetCollision(&m_collision);
float radius = m_marbleMesh.GetMeshBoundingBoxExtents(0).x / 2;
m_physics.SetRadius(radius);

Способ загрузки данных столкновений в значительной степени зависит от используемого формата времени выполнения. Дополнительные сведения о том, как Marble Maze загружает геометрию столкновения из файла SDK-Mesh, см . в методе MarbleMazeMazeMain::ExtractTrianglesFromMesh в исходном коде.

Обновление состояния игры

Marble Maze отделяет логику игры от логики отрисовки, сначала обновив все объекты сцены перед отрисовкой.

Структура приложения Marble Maze описывает основной цикл игры. Обновление сцены, которая входит в цикл игры, происходит после обработки событий и входных данных Windows, а также перед отображением сцены. Метод MarbleMazeMain::Update обрабатывает обновление пользовательского интерфейса и игры.

Обновление пользовательского интерфейса

Метод MarbleMazeMain::Update вызывает метод UserInterface::Update для обновления состояния пользовательского интерфейса.

UserInterface::GetInstance().Update(
    static_cast<float>(m_timer.GetTotalSeconds()), 
    static_cast<float>(m_timer.GetElapsedSeconds()));

Метод UserInterface::Update обновляет каждый элемент в коллекции пользовательского интерфейса.

void UserInterface::Update(float timeTotal, float timeDelta)
{
    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        (*iter)->Update(timeTotal, timeDelta);
    }
}

Классы, производные от ElementBase (определенный в UserInterface.h), реализуют метод Update для выполнения конкретных действий. Например, метод StopwatchTimer::Update обновляет истекшее время по указанному количеству и обновляет текст, отображаемый позже.

void StopwatchTimer::Update(float timeTotal, float timeDelta)
{
    if (m_active)
    {
        m_elapsedTime += timeDelta;

        WCHAR buffer[16];
        GetFormattedTime(buffer);
        SetText(buffer);
    }

    TextElement::Update(timeTotal, timeDelta);
}

Обновление сцены

Метод MarbleMazeMain::Update обновляет игру на основе текущего состояния компьютера состояния (GameState, хранящегося в m_gameState). Если игра находится в активном состоянии (GameState::InGameActive), Marble Maze обновляет камеру, чтобы следовать мрамору, обновляет матрицу представления части буферов констант и обновляет моделирование физики.

В следующем примере показано, как метод MarbleMazeMain::Update обновляет положение камеры. Marble Maze использует переменную m_resetКамера для флага, что камера должна быть сброшена непосредственно над мрамором. Камера сбрасывается, когда игра начинается, или мрамор падает через лабиринт. Если основное меню или экран с высокой оценкой активен, камера устанавливается в постоянном расположении. В противном случае Marble Maze использует параметр timeDelta для интерполяции положения камеры между текущими и целевыми позициями. Целевая позиция немного выше и перед мрамором. Использование истекшего времени кадра позволяет камере постепенно следовать, или преследовать, мрамор.

static float eyeDistance = 200.0f;
static XMFLOAT3A eyePosition = XMFLOAT3A(0, 0, 0);

// Gradually move the camera above the marble.
XMFLOAT3A targetEyePosition;
XMStoreFloat3A(
    &targetEyePosition, 
    XMLoadFloat3A(&marblePosition) - (XMLoadFloat3A(&g) * eyeDistance));

if (m_resetCamera)
{
    eyePosition = targetEyePosition;
    m_resetCamera = false;
}
else
{
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&eyePosition) 
            + ((XMLoadFloat3A(&targetEyePosition) - XMLoadFloat3A(&eyePosition)) 
                * min(1, static_cast<float>(m_timer.GetElapsedSeconds()) * 8)
            )
    );
}

// Look at the marble. 
if ((m_gameState == GameState::MainMenu) || (m_gameState == GameState::HighScoreDisplay))
{
    // Override camera position for menus.
    XMStoreFloat3A(
        &eyePosition, 
        XMLoadFloat3A(&marblePosition) + XMVectorSet(75.0f, -150.0f, -75.0f, 0.0f));

    m_camera->SetViewParameters(
        eyePosition, 
        marblePosition, 
        XMFLOAT3(0.0f, 0.0f, -1.0f));
}
else
{
    m_camera->SetViewParameters(eyePosition, marblePosition, XMFLOAT3(0.0f, 1.0f, 0.0f));
}

В следующем примере показано, как метод MarbleMazeMain::Update обновляет буферы констант для мрамора и лабиринта. Модель лабиринта или мир всегда остается матрицей идентификации. За исключением основной диагонали, элементы которых являются всеми, матрица идентификации — квадратная матрица, состоящая из нуля. Матрица модели мрамора основана на ее матрице позиций раз вращение матрицы.

// Update the model matrices based on the simulation.
XMStoreFloat4x4(&m_mazeConstantBufferData.model, XMMatrixIdentity());

XMStoreFloat4x4(
    &m_marbleConstantBufferData.model, 
    XMMatrixTranspose(
        XMMatrixMultiply(
            marbleRotationMatrix, 
            XMMatrixTranslationFromVector(XMLoadFloat3A(&marblePosition))
        )
    )
);

// Update the view matrix based on the camera.
XMFLOAT4X4 view;
m_camera->GetViewMatrix(&view);
m_mazeConstantBufferData.view = view;
m_marbleConstantBufferData.view = view;

Сведения о том, как метод MarbleMazeMain::Update считывает входные данные пользователя и имитирует движение мрамора, см. в разделе "Добавление входных и интерактивных данных" в пример Marble Maze.

Отрисовка сцены

При отрисовки сцены эти шаги обычно включаются.

  1. Задайте текущий буфер целевого набора элементов глубины отрисовки.
  2. Снимите представления отрисовки и набора элементов.
  3. Подготовьте вершины и шейдеры пикселей для рисования.
  4. Отрисовка трехмерных объектов в сцене.
  5. Отрисовка любого 2D-объекта, который вы хотите отобразить перед сценой.
  6. Представление отрисованного изображения монитору.

Метод MarbleMazeMain::Render привязывает целевые и глубинные представления элементов отрисовки, очищает эти представления, рисует сцену, а затем рисует наложение.

Подготовка целевых объектов отрисовки

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

В следующем примере показано, как метод MarbleMazeMain::Render вызывает метод ID3D11DeviceContext::OMSetRenderTargets , чтобы задать целевой объект отрисовки и буфер элементов глубины в качестве текущих.

auto context = m_deviceResources->GetD3DDeviceContext();

// Reset the viewport to target the whole screen.
auto viewport = m_deviceResources->GetScreenViewport();
context->RSSetViewports(1, &viewport);

// Reset render targets to the screen.
ID3D11RenderTargetView *const targets[1] = 
    { m_deviceResources->GetBackBufferRenderTargetView() };

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

// Clear the back buffer and depth stencil view.
context->ClearRenderTargetView(
    m_deviceResources->GetBackBufferRenderTargetView(), 
    DirectX::Colors::Black);

context->ClearDepthStencilView(
    m_deviceResources->GetDepthStencilView(), 
    D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 
    1.0f, 
    0);

Интерфейсы ID3D11RenderTargetView и ID3D11DepthStencilView поддерживают механизм представления текстур, предоставляемый Direct3D 10 и более поздних версий. Дополнительные сведения о представлениях текстур см. в разделе "Представления текстур" (Direct3D 10). Метод OMSetRenderTargets подготавливает этап слияния выходных данных конвейера Direct3D. Дополнительные сведения о стадии слияния выходных данных см. в разделе "Этап слияния выходных данных".

Подготовка вершин и шейдеров пикселей

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

  1. Задайте входной макет шейдера в качестве текущего макета.
  2. Задайте вершины и шейдеры пикселей в качестве текущих шейдеров.
  3. Обновите все буферы констант с данными, которые необходимо передать шейдерам.

Важно!

Marble Maze использует одну пару вершин и шейдеров пикселей для всех трехмерных объектов. Если в игре используется несколько пар шейдеров, эти действия необходимо выполнить каждый раз, когда вы рисуете объекты, использующие разные шейдеры. Чтобы уменьшить затраты, связанные с изменением состояния шейдера, рекомендуется группировать вызовы отрисовки для всех объектов, использующих одни и те же шейдеры.

 

В разделе "Загрузка шейдеров " в этом документе описывается создание входного макета при создании шейдера вершин. В следующем примере показано, как метод MarbleMazeMain::Render использует метод ID3D11DeviceContext::IASetInputLayout , чтобы задать этот макет в качестве текущего макета.

m_deviceResources->GetD3DDeviceContext()->IASetInputLayout(m_inputLayout.Get());

В следующем примере показано, как метод MarbleMazeMain::Render использует метод ID3D11DeviceContext::VSSetShader и ID3D11DeviceContext::P SSetShader для задания вершин и шейдеров пикселей в качестве текущих шейдеров соответственно.

// Set the vertex shader stage state.
m_deviceResources->GetD3DDeviceContext()->VSSetShader(
    m_vertexShader.Get(),   // use this vertex shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetShader(
    m_pixelShader.Get(),    // use this pixel shader
    nullptr,                // don't use shader linkage
    0);                     // don't use shader linkage

m_deviceResources->GetD3DDeviceContext()->PSSetSamplers(
    0,                          // starting at the first sampler slot
    1,                          // set one sampler binding
    m_sampler.GetAddressOf());  // to use this sampler

После того как MarbleMazeMain::Render задает шейдеры и их входной макет, он использует метод ID3D11DeviceContext::UpdateSubresource для обновления буфера константы с помощью модели, представления и матриц проекции для лабиринта. Метод UpdateSubresource копирует данные матрицы из памяти ЦП в память GPU. Помните, что компоненты модели и представления структуры ConstantBuffer обновляются в методе MarbleMazeMain::Update . Метод MarbleMazeMain::Render вызывает метод ID3D11DeviceContext::VSSetConstantBuffers и ID3D11DeviceContext::P SSetConstantBuffers , чтобы задать этот буфер констант в качестве текущего.

// Update the constant buffer with the new data.
m_deviceResources->GetD3DDeviceContext()->UpdateSubresource(
    m_constantBuffer.Get(),
    0,
    nullptr,
    &m_mazeConstantBufferData,
    0,
    0);

m_deviceResources->GetD3DDeviceContext()->VSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

m_deviceResources->GetD3DDeviceContext()->PSSetConstantBuffers(
    0,                                  // starting at the first constant buffer slot
    1,                                  // set one constant buffer binding
    m_constantBuffer.GetAddressOf());   // to use this buffer

Метод MarbleMazeMain::Render выполняет аналогичные действия для подготовки отрисовки мрамора.

Отрисовка лабиринта и мрамора

После активации текущих шейдеров можно нарисовать объекты сцены. Метод MarbleMazeMain::Render вызывает метод SDKMesh::Render для отрисовки сетки лабиринта.

m_mazeMesh.Render(
    m_deviceResources->GetD3DDeviceContext(), 
    0, 
    INVALID_SAMPLER_SLOT, 
    INVALID_SAMPLER_SLOT);

Метод MarbleMazeMain::Render выполняет аналогичные действия для отрисовки мрамора.

Как упоминание ранее в этом документе, класс SDKMesh предоставляется для демонстрационных целей, но мы не рекомендуем использовать его в игре с качеством рабочей среды. Однако обратите внимание, что метод SDKMesh::RenderMesh, который вызывается пакетом SDKMesh::Render, использует методы ID3D11DeviceContext::IASetVertexBuffers и ID3D11DeviceContext::IASetIndexBuffer для задания текущих буферов вершин и индексов, определяющих сетку, и метод ID3D11DeviceContext::D rawIndexed для рисования буферов. Дополнительные сведения о работе с буферами вершин и индексов см. в статье "Введение в буферы" в Direct3D 11.

Рисование пользовательского интерфейса и наложение

После рисования трехмерных объектов сцены Marble Maze рисует 2D-элементы пользовательского интерфейса, которые отображаются перед сценой.

Метод MarbleMazeMain::Render заканчивается путем рисования пользовательского интерфейса и наложения.

// Draw the user interface and the overlay.
UserInterface::GetInstance().Render(m_deviceResources->GetOrientationTransform2D());

m_deviceResources->GetD3DDeviceContext()->BeginEventInt(L"Render Overlay", 0);
m_sampleOverlay->Render();
m_deviceResources->GetD3DDeviceContext()->EndEvent();

Метод UserInterface::Render использует объект ID2D1DeviceContext для рисования элементов пользовательского интерфейса. Этот метод задает состояние рисования, рисует все активные элементы пользовательского интерфейса, а затем восстанавливает предыдущее состояние документа.

void UserInterface::Render(D2D1::Matrix3x2F orientation2D)
{
    m_d2dContext->SaveDrawingState(m_stateBlock.Get());
    m_d2dContext->BeginDraw();
    m_d2dContext->SetTransform(orientation2D);

    m_d2dContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE);

    for (auto iter = m_elements.begin(); iter != m_elements.end(); ++iter)
    {
        if ((*iter)->IsVisible())
            (*iter)->Render();
    }

    // We ignore D2DERR_RECREATE_TARGET here. This error indicates that the device
    // is lost. It will be handled during the next call to Present.
    HRESULT hr = m_d2dContext->EndDraw();
    if (hr != D2DERR_RECREATE_TARGET)
    {
        DX::ThrowIfFailed(hr);
    }

    m_d2dContext->RestoreDrawingState(m_stateBlock.Get());
}

Метод SampleOverlay::Render использует аналогичный метод для рисования растрового изображения наложения.

Презентация сцены

После рисования всех 2D-объектов сцены и трехмерной сцены Marble Maze представляет отрисованное изображение монитору. Он синхронизирует рисование с вертикальным пустым, чтобы гарантировать, что время не тратится на кадры рисования, которые никогда не будут отображаться на экране. Marble Maze также обрабатывает изменения устройства при представлении сцены.

После возврата метода MarbleMazeMain::Render цикл игры вызывает метод DX::D eviceResources::P resent, чтобы отправить отрисованное изображение на монитор или отображение. Метод DX::D eviceResources::P resent вызывает IDXGISwapChain::P resent для выполнения текущей операции, как показано в следующем примере:

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

В этом примере m_swapChain является объектом IDXGISwapChain1 . Инициализация этого объекта описана в разделе инициализации Direct3D и Direct2D в этом документе.

Первый параметр IDXGISwapChain::P resent, SyncInterval, указывает количество вертикальных пустых, ожидающих представления кадра. Marble Maze указывает 1, чтобы он ждал следующего вертикального пустого.

Метод IDXGISwapChain::P resent возвращает код ошибки, указывающий, что устройство было удалено или не выполнено. В этом случае Marble Maze повторно инициализирует устройство.

// 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)
{
    HandleDeviceLost();
}
else
{
    DX::ThrowIfFailed(hr);
}

Следующие шаги

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