Гибкий формат вершин FVF - Flexible Vertex Format
Дата создания: 2010-03-09 13:35:16
Последний раз редактировалось: 2012-02-08 11:04:36
- Предварительные уроки:
- Вершинные буферы. Перейти.
- Формат x. Перейти.
- Системы счисления. Двоичные числа. Перейти.
В Direct3D с помощью гибкого формата вершин (flexible - гибкий, vertex - вершина) можно сконструировать вершины с различной структурой.
Для начала давайте вспомним, как в Direct3D осуществляется вывод трёхмерных объектов на экран. Допустим, мы уже создали матрицы преобразования и координаты вершин в локальном пространстве. Тогда нам осталось сделать следующее:
// Глобальное пространство видимости struct vertex { float x,y,z; unsigned long color; }; // функция WinMain, до основного цикла: IDirect3DVertexBuffer9* vb = NULL; dev->CreateVertexBuffer( 4* sizeof(vertex), D3DUSAGE_WRITEONLY, D3DFVF_XYZ|D3DFVF_DIFFUSE, D3DPOOL_DEFAULT, &vb,NULL); // где-то здесь происходит создание геометрии и матриц преобразования void* pointer; vb->Lock(0,sizeof(vertices),(void**)&pointer,0); memcpy(pointer,vertices,sizeof(vertices)); vb->Unlock(); dev->SetStreamSource(0,vb,0,sizeof(vertex)); dev->SetFVF(D3DFVF_XYZ|D3DFVF_DIFFUSE);
- Всё это мы видели уже много раз, но сейчас нам нужно посмотреть на этот код немного с другой стороны. Что здесь происходит:
- Определение структуры vertex. Все вершины трёхмерной модели будут структурными переменными именно этой структуры.
- Объявление указателя на вершинный буфер.
- Создания вершинного буфера. Величина вершинного буфера зависит от размера структуры vertex (первый аргумент). Также обратите внимание на третий аргумент, в котором задаётся гибкий формат вершин.
- Объявление вспомогательного указателя на void, который будет использоваться при получении доступа к участку памяти, где хранится вершинный буфер.
- Заполнение вершинного буфера. Для этого его нужно замкнуть, скопировать в него данные всех вершин и разомкнуть его.
- Далее видеокарте сообщается о источнике данных, которые она должна выводить. В данном случае это вершинный буфер vb.
- И последнее: видеокарте сообщается о гибком формате вершин в текущем источнике данных.
Прошу обратить внимание, что в данном коде информацию о гибком формате вершин нужно указывать дважды: при создании вершинного буфера и при указании видеокарте источника данных для вывода.
Гибкий формат вершин неразрывно связан со структурой vertex. В структуре vertex определяются свойства вершин, из которых вы собираетесь создавать геометрию. Эти вершины затем будет помещены в вершинный буфер. Но для видеокарты вершины - это всего лишь набор битов. Гибкий формат вершин нужен как раз для того, чтобы видеокарта узнала о структуре ваших вершин. Т.е. между структурой vertex и FVF должно быть однозначное соответствие.
В нашем примере у вершин определены трёхмерные координаты и цвет. Поэтому в гибком формате эти вершины будут выглядеть так: D3DFVF_XYZ | D3DFVF_DIFFUSE. Первый флаг говорит, что у вершины три координаты, а второй, что вершина хранит рассеянный (diffuse) цвет.
FVF более подробно
Гибкий формат вершин - это набор флагов, представленный с помощью переменной типа DWORD (мы помним, что это переопределение типа unsigned long). Теперь рассмотрим самые важные форматы:
Флаги вершин в FVF
D3DFVF_DIFFUSE
Вершина хранит рассеянный цвет. Цвет задаётся в формате ARGB (32 бита).
D3DFVF_SPECULAR
Вершина хранит отражаемый (specular) цвет. Задаётся в формате ARGB (32 бита). О использовании specular и diffuse мы будем говорить в одном из ближайших выпусков рассылки.
D3DVFV_NORMAL
Нормаль вершины. Хранится в виде вектора. Представляется тремя переменными типа float. Этот флаг не используется вместе с D3DFVF_XYZRHW.
D3DFVF_XYZ
Позиция вершины в непреобразованных координатах. Три переменные типа float. Не используется вместе с флагом D3DFVF_XYZRHW.
D3DFVF_XYZRHW
Позиция вершины в преобразованных координатах. Если вы используете этот флаг, то не нужно создавать матрицы преобразования. Т.е. это координаты в пространстве окна. Флаг задаётся четырьмя переменными типа float. Первые три переменные: x,y,z. Четвёртая - RHW.
RHW - Reciprocal Homogenous W
Что значит RHW в последнем флаге? Когда мы преобразовывали вершины, то после умножения на проекционную матрицу, мы делили все компоненты вершины на однородную (w). Здесь происходит то же самое, только вместо деления на w, происходит умножение на 1/w. Это одно и то же, но для процессора второй вариант быстрее. Собственно, 1/w называется обратной однородной координатой или Reciprocal homogenous w (reciprocal - обратный, обратная величина, homogenous - однородный). Поэтому четвёртая компонента в флаге D3DFVF_XYZRHW не просто W, а RHW. При создании вершин эта компонента практически всегда задаётся единицей.
Это почти все форматы вершин, которыми мы будем пользоваться в ближайшее время. Посмотреть другие форматы вы можете в справке: Direct3D 9 → Reference → Direct3D Reference → Constants → D3DFVF.
Когда вы создаёте структуру вершин, то важно правильно определить порядок компонентов.
struct vertex { float x,y,z,rhw; unsigned long duffuse; };
Гибкий формат этой вершины будет следующим: D3DFVF_XYZRHW | D3DFVF_DIFFUSE. Важно: в гибком формате вершин порядок флагов не имеет значения, так как происходит установка отдельных битов, но в структуре вершины порядок компонентов важен. Т.е. в предыдущем примере мы можем записать D3DFVF_DIFFUSE | D3DFVF_XYZRHW - порядок не важен. Но мы не можем создать вершины следующей структуры:
struct vertex { unsigned long duffuse; // так нельзя!!! float x,y,z,rhw; };
Координаты всегда должны определяться раньше цвета. Ещё одно правило, которое нам скоро понадобится: нормали задаются после координат вершин. Например:
// формат: D3DFVF_XYZ | D3DFVF_NORMAL struct vertex { float x,y,z; // координаты вершины float nx,ny,nz; // компоненты нормали };
Так как формат вершин указывается в нескольких местах, то иногда создают отдельную переменную, чтобы потом использовать только её, а не полный формат вершин. Пример:
struct vertex1 { float x,y,z; // координаты вершины float nx,ny,nz; // компоненты нормали }; #define FVF_STD_VERTEX (D3DFVF_XYZ | D3DFVF_NORMAL) // или: const unsigned int FVF_STD_VERTEX = D3DFVF_XYZ | D3DFVF_NORMAL; struct vertex2 { float x,y,z,rhw; unsigned long duffuse; float tu, tv; }; #define FVF_INTERFACE (D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1) // или: const unsigned int FVF_INTERFACE = D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1;
Здесь были определены два типа вершин. Первый может быть использован для обычных трёхмерных объектов, а второй для создания интерфейса (если компоненту z для всех вершин задать нулём, то эти вершины будут выведены поверх всех других). Как второй формат может быть использован для вывода интерфейса пока не важно (позже обсудим). Теперь код:
// до основного цикла: IDirect3DVertexBuffer9* vb = NULL; dev->CreateVertexBuffer( 4* sizeof(vertex1), D3DUSAGE_WRITEONLY, FVF_STD_VERTEX, D3DPOOL_DEFAULT, &vb,NULL); IDirect3DVertexBuffer9* interfase = NULL; dev->CreateVertexBuffer( 4* sizeof(vertex2), D3DUSAGE_WRITEONLY, FVF_INTERFACE, D3DPOOL_DEFAULT, &vb,NULL); // создание геометрии и заполнение буферов // основной цикл dev->SetStreamSource(0,vb,0,sizeof(vertex1)); dev->SetFVF(FVF_STD_VERTEX); // вывод объектов dev->SetStreamSource(0,interface,0,sizeof(vertex2)); dev->SetFVF(FVF_INTERFACE); // вывод интерфейса
Смотрите, когда у нас есть несколько типов вершин, которые хранятся в разных буферах, то при выводе нужно менять потоки данных и используемый в данный момент гибкий формат вершин. В начале цикла устанавливается поток вывода - vb и формат FVF_STD_VERTEX. После вывода всех вершин этого буфера, происходит смена потока данных и смена формата вершин.
Получение fvf вершинного буфера
В определённых случаях мы не знаем формат вершин конкретного объекта. Это случается, например, при загрузке моделей из файлов .x. Узнать формат вершин можно с помощью одного из методов вершинного буфера:
ID3DXMesh* mesh = NULL; D3DXLoadMeshFromX(L"object.x",0,videocard,NULL,NULL,NULL,NULL,&mesh); IDirect3DVertexBuffer9* vb = NULL; mesh->GetVertexBuffer(&vb); // получение вершинного буфера D3DVERTEXBUFFER_DESC vb_desc; vb->GetDesc(&vb_desc);
Давайте посмотрим на определение структуры D3DVERTEXBUFFER_DESC:
typedef struct D3DVERTEXBUFFER_DESC { D3DFORMAT Format; D3DRESOURCETYPE Type; // тип ресурса, например, вершинный буфер DWORD Usage; D3DPOOL Pool; UINT Size; // размер буфера в байтах DWORD FVF; } D3DVERTEXBUFFER_DESC, *LPD3DVERTEXBUFFER_DESC;
Как видим, в данной структуре содержится вся информация о буфере данных (не только о вершинных буферах). Теперь можно проверить формат вершин данного буфера. Например, вот проверка на использование нормалей в вершинах буфера:
if (vb_desc.FVF & D3DFVF_NORMAL)
На сегодня всё.