4.Основы рендеринга 4.1.Представление и хранение поверхности объекта Каким же образом осуществляется рендеринг объектов сцены? Прежде всего, следует остановиться на том, что есть объект. Существует множество способов представить трехмерный объект и потом его визуализировать. Библиотека Direct3D использует один из них: представление поверхности трехмерного объекта треугольной сеткой (см. рис. 3).
Рис. 3. Слева – чайник, заданный треугольной сеткой. Справа – треугольная сетка этого объекта. Существует множество способов задавать треугольную сетку. Наиболее общим из которых (и рекомендуемым при реализации лабораторных работ), является индексированный список треугольников. Это представление предполагает существование двух массивов – массива вершин объекта и массива индексов, длина которого равна 3N, где N – количество треугольников (см. пример на рис. 4).
Рис. 4. Представление четырехугольника в виде индексированного списка треугольников. Порядок перечисления вершин в треугольнике важен, ибо он определяет какая сторона треугольника является передней (внешней), а какая задней (внутренней)4. Следует различать два понятия: вершина (vertex) и позиция вершины. Если второе это обычно три float – координаты вершины в 3х мерном пространстве, то первое это позиция вершины и данные (например, цвет), соответствующие ей. Так как данные в вершине могут быть различны для различных целей, то в Direc3D существует понятие вершинного формата – описание полей структуры вершины. Для этого существует два способа задания этой структуры: FVF флаги (flexible vertex format flags) и описание вершины (vertex declaration). Первый способ существенно проще и его достаточно для создания лабораторных работ. Второй способ значительно более гибок и предназначен для написания сложного конвейера рендеринга.
Использование FVF флагов предполагает, что поля вершины располагаются в определенном порядке и могут присутствовать или отсутствовать, что как раз и определяется флагами. В лабораторных работах нас будут интересовать поля, перечисленные в таблице 1. Таблица 1. Поля вершины, применяемые для создания лабораторных работ в рамках курса.
Поле
| Тип и размер
| FVF флаг
| Позиция вершины в 3D
| float x 3
| D3DFVF_XYZ
| Нормаль к поверхности к вершине
| float x 3
| D3DFVF_NORMAL
| Цвет в вершине
| uint (32-bit)
| D3DFVF_DIFFUSE
| Текстурные координаты для i-ого канала5
| float х K
| D3DFVF_TEXCOORDSIZEK(i)
| Здесь K – размерность i-ого текстурного канала, которая может быть от 1 до 4. Обычно используются двумерные текстурные координаты. Количество текстурных каналов определяется флагом D3DFVF_TEXM, где M от 0 до 8.
Цвет в вершине кодируется одним двойным словом по одному байту (8-бит) на канал как это показано в таблице 2.
Таблица 2. Кодирование цвета 32-битным словом.
Канал
| альфа
| красный
| зеленый
| синий
| Смещение
| 24
| 16
| 8
| 0
| Маска
| 0xFF000000
| 0x00FF0000
| 0x0000FF00
| 0x000000FF
|
Например, вершина, содержащая позицию и цвет (см. структуру ниже), описывается комбинацией D3DFVF_XYZ | D3DFVF_DIFFUSE.
struct Vertex
{
float x, y, z;
DWORD color;
};
Сообщить API об используемом формате вершины можно с помощью метода SetFVF(), например
m_pDev->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
Перейдем к рассмотрению способа хранения вершин и индексов. На настоящий момент самым оптимальным с точки зрения скорости рендеринга, является хранение данных в памяти видеоплаты. Чтобы сделать код хранения и использования не зависящим от типа памяти и организовать доступ к ней в Direct3D API используется концепция вершинных и индексных буферов (vertex and index buffers). При создании буфера указывается память, в которой будет храниться его содержимое. Доступ до данных, лежащих в буфере осуществляется методами Lock() и Unlock() соответствующих классов.
Рассмотрим как создается и используется вершинный буфер. Создание осуществляется с помощью метода CreateVertexBuffer().
IDirect3DVertexBuffer9* createAndFillVertexBuffer()
{
IDirect3DVertexBuffer9 *pVB; m_pDev->CreateVertexBuffer(
4*sizeof(Vertex), // Размер буфера в байтах
D3DUSAGE_WRITEONLY, // Usage. Флаги, описывающие способы использования
// создаваемого буфера. В данном случае
// предполагается, что чтение из буфера
// производиться не будет.
0, // FVF флаги данного буфера. Их установка для наших
// целей не обязательна.
D3DPOOL_DEFAULT, // Pool. Этим параметром выбирается память, в
// которой будет храниться буфер. DEFAULT
// предполагает, что драйвер видеоплаты выберет
// оптимальный вариант.
&pVB, // Адрес указателя на вершинный буфер
NULL); // Должно быть NULL Vertex *pVtx;
HRESULT hRes =
pVB->Lock(0, 0, // Этими двумя параметрами задается часть вершинного
// буфера, до которой будет осуществляться доступ.
// Задание двух нулей подразумевает, что будет
// доступ будет получен до всего буфера
(void*)&pVtx, // Указатель на void*, по котором будет записан
// адрес в системной памяти, куда можно записывать
// содержимое буфера. Однако, так как мы знаем
// формат данных, то мы можем использовать
// типизированный указатель.
0); // Флаги, определяющие тип доступа к данным. Для
// наших целей достаточно 0.
if (hRes == D3D_OK)
{
// Если lock прошел успешно, то производим записть данных.
for (int i = 0; i < 4; i++)
pVtx[i] = …; pVB->Unlock();
} return pVB;
}
Создание и использование индексного буфера осуществляется почти аналогично: вместо FVF флагов метод CreateIndexBuffer() принимает перечисляемый тип D3DFORMAT, который может принимать значения D3DFMT_INDEX16 или D3DFMT_INDEX32. В принципе, так как графические примитивы редко содержат больше 0xFFFF вершин, то рекомендуется использовать 16-ти битный формат.
|