Создаём компьютерную игру
eng   рус

Вывод треугольника в Direct3D 11

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

Direct3D

Direct3D используется для рендернига трёхмерной графики. Рендеринг - это процесс создания двухмерного изображения из 3д сцены.

Для разработки приложений Direct3D 11 требуется DirectX 11 SDK. SDK расшифровывается как Software Development Kit - набор разработки программного обеспечения. Какое-то время назад нужно было скачивать SDK отдельно с сайта Microsoft, но сейчас DirectX SDK входит в состав Windows SDK (с момента выхода Windows 8).

Direct3D 11.3 (последняя версия DirectX 11) состоит из следующих частей: Direct3D Graphics, DXGI, HLSL, DDS. DDS (графический формат) больше не используется, HLSL (шейдеры) мы обсудим во второй части урока. Начнём же мы с первых двух частей.

Direct3D Graphics

Это основная часть Direct3D. Она включает в себя интерфейсы и ресурсы для 3д рендеринга и используется для настройки графического конвейера. Эти интерфейсы начинаются с префикса ID3D11, например: ID3D11Device, ID3D11DeviceContext.

Эта часть построена на DXGI.

DXGI

DXGI - DirectX Graphics Infrastructure (графическая инфраструктура DirectX). DXGI - это низкоуровневые команды. DXGI позволяет Direct3D напрямую общаться с видеокартой. Разработчик может использовать напрямую какие-то части DXGI, другие же скрыты высокоуровневыми интерфейсами. DXGI интерфейсы начинаются с префикса. DXGI содержит важный интерфейс IDXGISwapChain. Этот интерфейс позволяет показать финальное изображение в окне.

Важные интерфейсы Direct3D 11.3

Устройство D3D11 это виртуальное представление видеокарты в нашей программе. Устройства D3D11 представлены интерфейсом ID3D11Device. Устройства D3D11 могут создавать различные ресурсы.

Контекст устройства (Device Context). Его интерфейс - ID3D11DeviceContext. Контекст отвечает за рендеринг. Данный интерфейс содержит комманды для создания 3д сцены.

Цепочка обмена (Swap Chain). Интерфейс IDXGISwapChain. Содержит буферы в которые рендерится сцена. Цепочка обмена очень важная концепция, поэтому важно понимать как она работает.

IDXGISwapChain

Когда создаётся цепочка обмена, она привязывается к окну (HWND) и при этом создаётся устройство d3d11 (ID3D11Device). Любая цепочка обмена состоит из одного или более буферов. Буфер в данном случае это всего лишь прямоугольное изображение (в памяти это просто массив). Размер буферов должен совпадать с клиентской областью окна. Каждая цепочка обмена должна иметь основной буфер (front buffer) и какое-то количество фоновых (back buffers) - ноль и более.

Рендеринг 3д сцены происходит много раз в секунду. Созданное 2д изображение копируется в один из фоновых буферов цепочки обмена. Когда фоновый буфер заполнен, он меняется местами с основным. Это называется представлением (presenting). После этого содержимое фонового буфера становится видимым в окне приложения. Главная цель цепочки обмена - показать отрендеренное изображение на экране.

Теперь мы готовы к инициализации приложения Direct3D.

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

1. Вначале нужно создать устройство d3d11. Затем связать контекст устройства и цепочку обмена с устройством.
2. Потом создать целевой объект отрисовки (Render-target View - RTV). RTV - это изображение, куда рендерится 3д сцена.

После выполнения этих пунктов можно начинать загрузку ресурсов программы.

Создание ID3D11Device

В приложении DirectX все действия выполняются интерфейсами. За исколючением одного. Первый интерфейс создаётся функцией. Главный интерфейс в Direct3D 11 - это ID3D11Device. Получить этот интерфейс можно с помощью двух функций: D3D11CreateDevice, D3D11CreateDeviceAndSwapChain. Первая функция создаёт только устройство. Вторая создаёт и устройство d3d11, и цепочку обмена. Мы пойдём по простому пути и воспользуемся D3D11CreateDeviceAndSwapChain. Прототип выглядит так:

Функция D3D11CreateDeviceAndSwapChain

HRESULT D3D11CreateDeviceAndSwapChain( _In_ IDXGIAdapter *pAdapter, _In_ D3D_DRIVER_TYPE DriverType, _In_ HMODULE Software, _In_ UINT Flags, _In_ const D3D_FEATURE_LEVEL *pFeatureLevels, _In_ UINT FeatureLevels, _In_ UINT SDKVersion, _In_ const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc, _Out_ IDXGISwapChain **ppSwapChain, _Out_ ID3D11Device **ppDevice, _Out_ D3D_FEATURE_LEVEL *pFeatureLevel, _Out_ ID3D11DeviceContext **ppImmediateContext );

Функция получает 12 аргументов. Она создаёт три интерфейса и одну переменную.

1. IDXGIAdapter *pAdapter - указатель на видеоадаптер. Видеокарта по умолчанию будет использована, когда этот аргумент равен NULL. В примерах всегда будет NULL.

2. D3D_DRIVER_TYPE DriverType - тип драйвера. Одно из значений перечисления D3D_DRIVER_TYPE. Мы будем использовать D3D_DRIVER_TYPE_HARDWARE, т.е. будем предполать, что все команды выполняются железом. Другие значения можно использовать если ваша видеокарта не поддерживает DirectX 11.

3. HMODULE Software - указатель на библиотеку DLL, которая использует программный растеризатор. Используется когда предыдущий параметр равен D3D_DRIVER_TYPE_SOFTWARE. Мы будем передавать NULL.

4. UINT Flags - набор флагов из перечисления D3D11_CREATE_DEVICE_FLAG. Основной флаг - D3D11_CREATE_DEVICE_DEBUG. Это значение запускает отладочный уровень (Debug Layer) - содержит описание ошибок и дополнительные условия связывания ресурсов и шейдеров. Direct3D 11 во время работы состоит из двух уровней: уровень ядра и отладочный уровень. Уровень ядра - это как раз Direct3D API. При использовании отладочного уровня можно получить дополнительную информацию. Мы рассмотрим отладочный уровень в отдельном уроке, а до этого будем использовать только уровень ядра. Поэтому данному параметру зададим 0.

5. const D3D_FEATURE_LEVEL *pFeatureLevels - массив уровней функциональности. На данный момент самый высокий - D3D_FEATURE_LEVEL_11_1 (для одиннадцатой версии). Мы будем использовать его. Этот параметр принимает указатель на массив. В массиве должны содержаться разные уровни функциональности, начиная с наивысшего. Если видеокарта не поддерживает самый высокий уровень, DirectX попытается создать устройство d3d11 со следующим уровнем функциональности в массиве. Если передать NULL, то будет использоваться следующий массив:

{ 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, };

В примере я использовал переменную вместо массива. Это равнозначно передаче массива с одним элементом.

6. UINT FeatureLevels - количество элементов в массиве из предыдущего параметра. Опять же, если передавать один элемент, то в предыдущем параметре вместо массива можно передать указатель на переменную D3D_FEATURE_LEVEL.

7. UINT SDKVersion - версия SDK. Для Direct3D 11 нужно передавать D3D11_SDK_VERSION.

8. const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc - указатель на структуру, описывающую цепочку обмена.

9. IDXGISwapChain **ppSwapChain - указатель на интерфейс цепочки обмена.

10. ID3D11Device **ppDevice - указатель на интерфейс устройства D3D11.

11. D3D_FEATURE_LEVEL *pFeatureLevel - в этой переменной будет сохранён используемый уровень функциональности. Все дальнейшие уроки будут предполагать использование D3D_FEATURE_LEVEL_11_1. Это значение зависит от пятого аргумента.

12. ID3D11DeviceContext **ppImmediateContext - указатель на контекст устройства d3d11.

После вызова функции D3D11CreateDeviceAndSwapChain мы получим переменную с уровнем функциональности и три интерфейса: IDXGISwapChain, ID3D11Device, ID3D11DeviceContext. Восьмой аргуметнт - указатель на структуру DXGI_SWAP_CHAIN_DESC. Эту структуру нужно заполнить перед вызовом D3D11CreateDeviceAndSwapChain. Взглянем на определение этой структуры:

DXGI_SWAP_CHAIN_DESC structure

typedef struct DXGI_SWAP_CHAIN_DESC { DXGI_MODE_DESC BufferDesc; DXGI_SAMPLE_DESC SampleDesc; DXGI_USAGE BufferUsage; UINT BufferCount; HWND OutputWindow; BOOL Windowed; DXGI_SWAP_EFFECT SwapEffect; UINT Flags; } DXGI_SWAP_CHAIN_DESC;

DXGI_SWAP_CHAIN_DESC говорит, какие свойства должна иметь цепочка обмена.

1. DXGI_MODE_DESC BufferDesc - структура, описывающая буферы: BufferDesc.Width - width, BufferDesc.Height - высота, BufferDesc.Format - формат пикселей в буфере, BufferDesc.ScanlineOrdering - метод растеризации - сейчас не важно, BufferDesc.Scaling - метод масштабирования - не важно, BufferDesc.RefreshRate - частота обновления в герцах, имеет два поля: BufferDesc.RefreshRate.Numerator - числитель, и BufferDesc.RefreshRate.Denominator - знаменатель.

2. DXGI_SAMPLE_DESC SampleDesc - параметры мультисэмплинга. Структура состоит из двух полей: количество и качество. По умолчанию для качества используется 0, а для количества - 1.

3. DXGI_USAGE BufferUsage - это поле задаёт как будет использоваться буфер, а также доступ центрального процессора к буферу. Мы будем использовать буфер для рендеринга 3д сцены в окно, поэтому будем использовать значение DXGI_USAGE_RENDER_TARGET_OUTPUT.

4. UINT BufferCount - количество буферов.

5. HWND OutputWindow - окно, куда будет выводиться финальное двухмерное изображение.

6. BOOL Windowed - задаёт оконный режим.

7. DXGI_SWAP_EFFECT SwapEffect - поле задаёт, что будет происходить с содержимым буфера после выполнения команды Present. Значение по умолчанию - DXGI_SWAP_EFFECT_DISCARD. В следующих уроках мы узнаем больше об этом поле.

8. UINT Flags - набор флагов, описывающих поведение цепочки обмена. Будем задавать 0.

Создание устройства D3D11 и цепочки обмена

Посмотрим на код:

ID3D11Device* dev; // устройство d3d11 ID3D11DeviceContext* devContext; // контекст устройства IDXGISwapChain* sc; // цепочка обмена DXGI_SWAP_CHAIN_DESC sсd; ZeroMemory(&sсd, sizeof(sсd)); sсd.BufferCount = 2; sсd.BufferDesc.Width = 500; sсd.BufferDesc.Height = 500; sсd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sсd.BufferDesc.RefreshRate.Numerator = 60; sсd.BufferDesc.RefreshRate.Denominator = 1; sсd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sсd.OutputWindow = hWnd; sсd.SampleDesc.Count = 1; sсd.SampleDesc.Quality = 0; sсd.Windowed = TRUE; D3D_FEATURE_LEVEL FeatureLevels = D3D_FEATURE_LEVEL_11_1; UINT numLevels = 1; D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &FeatureLevels, numLevels, D3D11_SDK_VERSION, &sсd, &sc, &dev, NULL, &devContext);

Здесь мы заполняем описание DXGI_SWAP_CHAIN_DESC и вызываем D3D11CreateDeviceAndSwapChain function.

Создание RTV (Render-Target View)

Теперь нам нужно понять ответственность каждого интерфейса и зачем нам нужен RTV. Устройство D3D11 создаёт различные ресурсы. Цепочка обмена представляет содержимое фонового буфера на экран. Контекст устройства выполняет весь рендеринг. Контекст устройства "рисует" графику в буфер, а цепочка обмена показывает картинку в окне. Но есть одна проблема. Контекст устройства не может работать с цепочкой обмена напрямую. Для этой задачи нам нужен render-target view. Контекст устройства для рендеринга использует интерфейс ID3D11RenderTargetView. Поэтому нам нужно привязать этот интерфейс к фоновому буферу цепочки обмена. Для начала нужно получить адрес фонового буфера:

HRESULT IDXGISwapChain::GetBuffer( [in] UINT Buffer, [in] REFIID riid, [in, out] void **ppSurface );

UINT Buffer - индекс буфера. Если для цепочки обмена мы задали DXGI_SWAP_EFFECT_DISCARD, то этот аргумент всегда будет получать 0.

REFIID riid - здесь мы указываем тип интерфейса, который мы хотим получить. Нам нужно получить интерфейс ID3D11Texture2D (2д изображение). Тип этого аргумента относится к COM. Мы будем разбирать COM в отдельном уроке. Пока же мы зададим этот аргумент как __uuidof(ID3D11Texture2D).

void **ppSurface - указатель на интерфейс (в нашем случае - ID3D11Texture2D).

После вызова метода GetBuffer, мы получим интерфейс ID3D11Texture2D, у которого есть доступ к фоновому буферу цепочки обмена. Теперь, используя этот интерфейс мы можем создать RTV.:

HRESULT ID3D11Device::CreateRenderTargetView( [in] ID3D11Resource *pResource, [in] const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, [out] ID3D11RenderTargetView **ppRTView );

ID3D11Resource *pResource - интерфейс, который мы получили в предыдущем методе.

const D3D11_RENDER_TARGET_VIEW_DESC *pDesc - описание RTV. Просто передадим NULL.

ID3D11RenderTargetView **ppRTView - адрес ID3D11RenderTargetView. Этот интерфейс будет использоваться для рендеринга контекстом устройства d3d11.

Код создания RTV:

ID3D11RenderTargetView* view; ID3D11Texture2D* backBuffer; sc->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBuffer); dev->CreateRenderTargetView(backBuffer, NULL, &view);

Итак, мы подготовили устройство, контекст устройства и цепочку обмена для рендеринга. Теперь самое время узнать, что такое шейдеры:

HLSL - шейдеры в DirectX

Шейдер - важнейшая часть современной компьютерной графики. DirectX использует HLSL (High-Level Shader Language - высокоуровневый язык шейдеров) для шейдеров. Шейдер - это просто программа, которая выполняется видеокартой. Мы можем добавить шейдер в нашу программу следующим образом: создать отдельный файл с кодом шейдера, прочитать его в нашей программе и скомпилировать.

Существуют разные типы шейдеров. При этом обязательных только два: вершинный и пиксельный.

Шейдеры хранятся в отдельных файлах с расширением .hlsl. Для данного урока я добавил в проект файл shaders.hlsl. В нём хранятся оба шейдера. Мы будем хранить вершинный и пиксельный шейдеры в одном файле, а также самостоятельно их компилировать, поэтому нужно исключить этот файл из сборки. Щёлкните правой кнопкой на файле shaders.hlsl и выберите свойства (properties). Затем измените значение для "Excluded From Build" на "Yes".

Теперь взглянем на код шейдеров:

float4 VS(float4 position : POSITION) : SV_Position { return position; } float4 PS() : SV_Target { return float4(0.0f, 0.0f, 0.0f, 1.0f); }

Код на HLSL похож на C/C++. Здесь две функции: VS (vertex shader) и PS (pixel shader).

VS принимает один аргумент - position с типом float4. float4 - это массив из четырёх элементов. В HLSL много типов, помогающих упростить математику. POSITION, SV_Position, SV_Target - примеры семантики. Данные, которые передаются между стадиями графического конвейера, являются обобщёнными (generic). Семантика даёт данным смысл. Бывают входящие и исходящие переменные. Семантику для исходящих данных мы задаём после списка аргументов, т.е. POSITION относится в входящим данным, а SV_Position - к исходящим (то, что мы вернём из функции).

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

Где-то между вершинным и пиксельным шейдером GPU делает растеризацию. Поэтому пиксельный шейдер получает пиксельные данные, а не вершину.

Пиксельный шейдер может выводить данные только с помощью SV_Target или SV_Depth семантик. SV_Target говорит, что эти данные будут помещены в фоновый буфер (точнее в render target). Каждый пиксель мы "заливаем" чёрным цветом. Обратите внимание, что только пиксели внутри треугольника будут окрашены. Т.е. пиксельный шейдер будет выполнен только для пикселей треугольника.

Возвращаемся назад в программу.

Компиляция шейдеров

В DirectX 11 под Windows 10 шейдеры можно скомпилировать тремя способами. Два из них используют устройство D3D11. Третий - это функция D3DCompileFromFile function, которой мы и воспользуемся, так как этом самый быстрый и простой способ.

На заметку: в документации говорится, что приложения которые используют функцию D3DCompileFromFile нельзя размещать в Windows Store.

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

#include <D3Dcompiler.h> #pragma comment(lib,"D3dcompiler.lib")

Функция D3DCompileFromFile

D3DCompileFromFile компилирует HLSL шейдеры. Она принимает файл с исходным кодом шейдера и возвращает (предпоследний аргумент) объект интерфейса ID3DBlob (blob - Binary Large Object - по сути это просто большой массив данных):

HRESULT WINAPI D3DCompileFromFile( LPCWSTR pFileName, const D3D_SHADER_MACRO *pDefines, ID3DInclude *pInclude, LPCSTR pEntrypoint, LPCSTR pTarget, UINT Flags1, UINT Flags2, ID3DBlob **ppCode, ID3DBlob **ppErrorMsgs );

1. pFileName - имя файла с исходным кодом шейдера.

2. pDefines - массив макросов шейдера. Пока задаём NULL.

3. pInclude - этот аргумент задаётся если в файле есть директива #include. Задаём NULL.

4. pEntrypoint - точка входа - имя функции с кодом шейдера. По умолчанию Visual Studio будет искать main. Мы же используем VS и PS.

5. pTarget - версия и тип шейдера: вычислительный (compute), доменный (domain), геометрический (geometry), hull (поверхностный), pixel (пиксельный), vertex (вершинный). Пока нас интересуют два последних. В DirectX 11 нужно использовать пятую версия. Задаём значения: vs_5_0 для вершинного и ps_5_0 для пиксельного.

6-7. Флаги. Задаём пока NULL.

8. ID3DBlob объект, в котором будет храниться скомпилированный шейдер.

9. ppErrorMsgs - сообщения об ошибках будут храниться в этом объекте. Задаём NULL.

Теперь можно скомпилировать шейдеры:

ID3DBlob* vsBlob = nullptr; D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "VS", "vs_5_0", 0, 0, &vsBlob, NULL); ID3DBlob* psBlob = nullptr; D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "PS", "ps_5_0", 0, 0, &psBlob, NULL);

Вершинный шейдер мы компилируем в vsBlob, а пиксельный - в psBlob.

Создание шейдерных объектов в DirectX 11

После компиляции шейдеров, можно создать шейдерные объекты. Шейдерный объект (Shader Object) - это представление шейдера в программе. ID3D11Device использует разные методы для создания объектов разных типов. Нам нужно два метода ID3D11Device::CreateVertexShader и ID3D11Device::CreatePixelShader. Они похожи (отличается только последний аргумент). Посмотрим на первый метод:

HRESULT CreateVertexShader( const void *pShaderBytecode, SIZE_T BytecodeLength, ID3D11ClassLinkage *pClassLinkage, ID3D11VertexShader **ppVertexShader );

1. pShaderBytecode - адрес скомпилированного шейдера в памяти.

2. BytecodeLength - размер скомпилированного шейдера.

ID3DBlob интерфейс имеет два метода: GetBufferPointer - возвращает адрес скомпилированного шейдера, GetBufferSize - возвращает размер. Эти методы не имеют аргументов.

3. pClassLinkage - аргумент используемый для связывания. Задаём NULL.

4. ppVertexShader - в этой переменной будет сохранён шейдерный объект.

Теперь код:

ID3D11VertexShader* vs; ID3D11PixelShader* ps; dev->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), NULL, &vs); dev->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), NULL, &ps);

После этого можно указать конвейеру, какие шейдерные объекты использовать:

devContext->VSSetShader(vs, NULL, NULL); devContext->PSSetShader(ps, NULL, NULL);

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

Подготовка вершин треугольника

Мы подготовили Direct3D для рендеринга. Теперь мы можем отправлять данные в графический конвейер. Мы создадим геометрию треугольника и отправим её в GPU для отрисовки.

Direct3D может работать с разнымим примитивами. Сейчас нам нужны только треугольники. Каждый примитив состоит из вершин, треугольник состоит из трёх. Каждая вершина состоит из компонент - (x, y, z, w). Пока для последней компоненты мы будем задавать 1. Четвёртая компонента нужна, чтобы сделать математику проще.

Когда мы отправляем массив вершин в GPU, вершины проходят через графический конвейер (Rendering Pipeline). Сначала они проходят вершинный шейдер. В нашем случае вершинный шейдер ничего не делает и просто передаёт вершину дальше. В конце вершинного шейдера координаты должны быть в диапазоне от -1 до 1. Каждая вершина проходит вершинный шейдер.

Создадим геометрию. Нам потребуется структура для вершин:

struct Vertex { XMFLOAT4 position; }; Vertex vertices[] = { XMFLOAT4(0.0f, 0.5f, 0.0f,1.0f), XMFLOAT4(0.5f, -0.5f, 0.0f,1.0f), XMFLOAT4(-0.5f, -0.5f, 0.0f,1.0f), };

Каждая вершина имеет поле с типом XMFLOAT4. XMFLOAT4 определён в directxmath.h. Это структура и она имеет четыре поля: x, y, z, w. Важно использовать типы DirectXMath, так как там определено много структур и функций преобразования в данные, которые быстро обрабатываются в GPU, мы поговорим об этом позже.

В данном примере мы задаём массив с тремя элементами. Треугольник задан в плоскости xy, компоненты z равны 0. Мы задаём перспективные проекционные координаты (perspective projected coordinates) - финальная форма координат - так они должны выглядеть после вершинного шейдера. Центр экрана - (0, 0). Диапазон значений от -1 до +1. Позже мы рассмотрим разные виды координат.

Теперь нам нужно связать нашу геометрию с графическим конвейером. Наши данные можно представить в GPU с помощью интерфейса ID3D11Buffer:

ID3D11Buffer* vertexBuffer = nullptr; D3D11_BUFFER_DESC bd = {}; bd.ByteWidth = sizeof(Vertex) * 3; bd.Usage = D3D11_USAGE_DEFAULT; bd.BindFlags = D3D11_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA InitData = {}; InitData.pSysMem = vertices; dev->CreateBuffer(&bd, &InitData, &vertexBuffer);

Вначале мы заполняем структурную переменную D3D11_BUFFER_DESC. ByteWidth - размер нашего буфера. Поле usage говорит, как буфер будет использоваться GPU и CPU. Мы зададим значение D3D11_USAGE_DEFAULT . BindFlags говорит, что будет в буфере. Нам нужен вершинный буфер. CPUAccessFlags задаёт, как CPU может получить доступ к буферу. Нам не нужен доступ к этому буферу из CPU, поэтому передадим 0.

После этого мы создаём структурную переменную D3D11_SUBRESOURCE_DATA. Эта структура используется для создания буферов. pSysMem указывает на начало массива вершин.

ID3D11Device::CreateBuffer создаёт буфер. В нашем случае - вершинный. Этот метод сохранит адрес буфера в указателе последнего аргумента.

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

UINT stride = sizeof(Vertex); UINT offset = 0; devContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);

Первый аргумент в ID3D11DeviceContext::IASetVertexBuffers - первый входящий слот для связывания. Пока передаём 0. Второй аргумент говорит, сколько вершинных буферов мы хотим связать. Третий аргумент - адрес массива вершинных буферов. Четвёртый аргумент - шаг (stride) - это смещение между отдельными элементами вершинного буфера. Последний аргумент - смещение к элементу, с которого нужно начать вывод

Последнее что нам нужно сделать перед основным циклом - задать какую геометрию мы хотим выводить:

devContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

Мы будем выводить список треугольников. Мы также можем выводить другие примитивы (отрезки, точки) или треугольники в другом формате. Мы рассмотрим другие варианты позже.

Главный цикл с Direct3d

const float greyColor[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; while (msg.message != WM_QUIT) { if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } devContext->ClearRenderTargetView(rtv, greyColor); devContext->Draw(3, 0); sc->Present(0, 0); }

После проверки сообщений мы очищаем RTV и заливаем весь буфер серым цветом. Затем мы делаем вызов Draw. Первый аргумент - количество вершин, которое мы хотим нарисовать, второй - с какой вершины начать.

И, наконец, мы представляем фоновый буфер цепочкой обмена:

sc->Present(0, 0);

Первый аргумент - синхронизация, второй - флаги. Передаём нули.

Заключение

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

Комментарии:

No comments yet