Создание игр » DirectX 9, Featured » Vertex Declaration
Vertex Declaration
Vertex Declaration в Direct3D создаётся с помощью функции CreateVertexDeclaration, которой, в качестве аргументов, передаётся массив объектов типа D3DVERTEXELEMENT9, каждый из которых определяет отдельную запись (тип данных) в формате вершин модели – такие как позиция вершины, нормаль, текстурные координаты и так далее. Далее, при рендеринге объектов через Direct3D Device, до вызова отрисовки примитивов, созданный Vertex Declaration устанавливается с помощью функции SetVertexDeclaration и после этого графический конвейер сможет верно парсить данные нашей модели и передавать их на обработку в vertex shader, который, в свою очередь, после обработки, сможет их передавать в pixel shader.
Создание и использование Vertex Declaration
В примерах исходников к одному из предыдущих уроков, Vertex buffer, Index buffer практика, я уже использовал функцию создания Vertex Declaration и, там же, использовал созданную декларацию для рендера модели. Вот код делкарации из урока Per-pixel lighting:
D3DVERTEXELEMENT9 dwDeclMyVertex[] = { {0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0}, // первая декларация {0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0}, // вторая декларация {0, 24, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 1}, // третья декларация {0, 36, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 2}, // четвёртая декларация {0, 48, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0}, // пятая декларация {0, 64, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0}, // шестая декларация D3DDECL_END() // седьмая декларация }; |
Поля Vertex Declaration
Каждая запись (элемент массива) в Vertex Declaration является указанием DirectX о том, какие конкретно данные находятся в каком конкретно месте нашей структуры vertex’a. Когда мы устанавливаем поток данных (Vertex buffer) с помощью функции SetStreamSource, мы лишь указываем DirectX сам источник данных (т.е. наш Vertex Buffer, это второй параметр функции) и размер “данных вертекса” в байтах (третий параметр функции). Но этого мало для того, что бы DirectX мог работать с нашими данными – мы так же должны указать какие конкретно данные находятся в нашем вертексе и где конкретно (смещение от начала структуры) они лежат.
Каждая запись в массиве Vertex Declaration это структура D3DVERTEXELEMENT9, которая состоит из следующих частей:
- Stream – номер потока, из которого будут читаться данные. Номер потока, это первый аргумент функции установки потока данных SetStreamSource. По идее, мы можем установить сразу несколько потоков (скажем 3: №0, №1 и №2) и указать DirectX, что вертекс должен собираться по данным из нескольких потоков.
- Offset – смещение относительно начала текущего элемента данных (в байтах).
- Type – какой тип данных находится по указанному Offset (например, трёхкомпонентный вектор или четырёхкомпонентый вектор или DWORD)
- Method – используется для тесселятора. Мы его использовать не планируем (ибо в DirectX 9 он кривой, неудобный, тормозной и нормально не настраивается), потому всегда D3DDECLMETHOD_DEFAULT
- Usage – как будут использоваться данные. Например, мы можем указать, что этот элемент это текстурные координаты… или цвет… или позиция вертекса… или что-то ещё
- UsageIndex – индекс. В DirectX каждый вертекс может иметь данные (позиция, нормаль, цвет, текстурные координаты) в разных каналах. Например: TEXCOORD0, TEXCOORD1, TEXCOORD2. Вот это как раз индекс – 0, 1, 2 и т.д.
Что бы далее не объяснять абстрактно, давайте просто разберём приведённый выше пример декларации по строкам. Как Вы видите из комментариев, которые я добавил к коду, всего наш Vertex Declaration состоит из 7 записей-деклараций элементов.
- Первый элемент Vertex Declaration читается из 0’го потока (первый ноль) по смещению 0 (второй ноль), это тип данных трёхмерный вектор (D3DDECLTYPE_FLOAT3), который будет использоваться в шейдере как позиция вертекса (D3DDECLUSAGE_POSITION) с индексом 0, т.е. как POSITION0 (или просто POSITION)
- Второй элемент в Vertex Declaration (да и все остальные тоже) читается из потока №0, по смещению 12 (т.к. позиция это трёхмерный вектор, а трёхмерный вектор это 3 float’а, а каждый float занимает 4 байта, что в итоге даёт 3*4=12 байт), это тоже трёхмерный вектор (D3DDECLTYPE_FLOAT3), в шейдере он будет использоваться в качестве нормали (D3DDECLUSAGE_NORMAL) с индексом ноль
- Третий элемент Vertex Declaration, по смещению 24 (т.к. до него уже 2 трёхмерных вектора, 2*(3*4) = 24 байта), это трёхмерный вектор (D3DDECLTYPE_FLOAT3), который тоже будет использоваться как нормаль (D3DDECLUSAGE_NORMAL), но уже с индексом 1 (единица в конце)
- Далее аналогично, но это NORMAL2
- По смещению 48 байт от начала “данных вертекса” хранятся текстурные координаты (D3DDECLUSAGE_TEXCOORD), которые представлены четёрхмерным вектором (D3DDECLTYPE_FLOAT4, я запаковал 2 канала по 2 координаты в один 4хмерный вектор), индекс текстурных координат =0
- Последним в Vertex Declaration в идёт DWORD (D3DDECLTYPE_D3DCOLOR это DWORD), который в вертексе будет использоваться как цвет (D3DDECLUSAGE_COLOR) с индексом 0
- Завершающий D3DDECL_END() должен быть в конце каждого Vertex Declaration – не забывайте его указывать
Уфф… Кто-нибудь что-нибудь понял? ))) Надеюсь, что да )
Наверное, у Вас возник закономерный вопрос “а зачем иметь возможность использовать несколько разных Stream одновременно?”. Я пока не хочу сильно углубляться в этот вопрос. Приведу лишь один пример. Например, вы можете хранить в одном потоке (Stream) геометрию, а в другом – цвета вершин. При этом имея 1 геометрию и 2 потока с цветами, вы можете получить две разных модели (одна с одними цветами, а другая с другими). Когда у вас будет много моделей и каждая сможет иметь кучу цветов (например, один домик, но когда он стоит в лесу он обычный, когда он стоит на болоте у него фундамент становится более зелёным, а когда на воде – более тёмным) – это даст существенную экономию памяти (вместо того, что бы хранить 3 разных домика, мы будем хранить 1 доминк + 3 набора цветов к нему… это в сумме по затратам памяти где-то в 2-2.5 раза меньше, чем хранить 3 разных домика). Кроме того, потоки нужны для инстансинга, но geometry instancing это вообще отдельная тема и касаться я её сейчас не буду – по нему потом будет отдельный урок.
Вот Вам “домашнее задание” для закрепления материала: напишите Vertex Declaration для использования вот такой структуры в качестве данных вертекса:
struct HomeVertex { D3DXVECTOR4 texCoord0;// текстурные координаты №0 D3DXVECTOR4 pos;// позиция вертекса DWORD unused; // не используется в шейдере D3DXVECTOR4 data;// тоже не используется DWORD color0;// цвет №0 DWORD color1;// цвет №1 D3DXVECTOR2 texCoord1;// текстурные координаты №1 }; |
Исходников к этому уроку нет – они есть у Вас с прошлых уроков, Вы можете поэксперементировать с ними.
Раздел: DirectX 9, Featured · Теги: Direct3D, Создание движка
Возможно дурацкий вопрос, но как же оно всё у нас работало и без указания деклараций (Vertex Declaration)?
Спасибо за урок!
Последние демки все были с использованием Vertex Declaration. А спрайты – используют FVF (SetFVF) – это устаревший, гораздо менее гибкий и менее удобный, но пока ещё работающий (только в ДХ9 и не выше) способ.
»crosslinked«
Это я и хотел узнать – спасибо.
Здравствуйте!
Эту структуру вертекса нужно повторить во входной структуре к вертексному шейдеру, чтобы он ее правильно понимал я правильно понял? Тогда мне остается непонятным почему в VertexDeclaration цвет задает как DWORD, а на входе вертексного шейдера он уже как float4? Спрашиваю, потому что не получается из за чего-то передать цвет вертекса в шейдер.
Граф. коневейер сам производит конвертацию из DWORD во флоаты. В декларации указано, что у нас цвет это DWORD, а на вход шейдера все данные приходят как float – потому просиходит автоматическая конвертация (она совершенно бесплатна).