Уроки по OpenGL с сайта OGLDev


Урок 10 - Индексированная отрисовка

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

Вот пример порядковой отрисовки:

Если нам нужны треугольники, то GPU генерирует следующий набор: V0/1/2, V3/4/5, V6/7/8 и т.д.

А вот пример индексированной отрисовки:

В этом случае GPU создаст следующие треугольники: V4/0/1, V5/2/1, V6/1/7 и т.д.

Использование такой отрисовки в OpenGL требует создания и заполнения буфера индексов. Этот буфер должен быть указан вместе с вершинным до вызова функции отрисовки, а так же необходимо использовать другое API.

Прямиком к коду!

GLuint IBO;

Мы добавляем еще один указатель на буферный объект для буфера индексов.

Vertices[0] = Vector3f(-1.0f, -1.0f, 0.0f);
Vertices[1] = Vector3f(0.0f, -1.0f, 1.0f);
Vertices[2] = Vector3f(1.0f, -1.0f, 0.0f);
Vertices[3] = Vector3f(0.0f, 1.0f, 0.0f);

Для демонстрации обмена нам потребуется более сложная фигура. Во многих уроках для этого используется куб. Для него потребуются 8 вершин и 12 треугольников. Но так как я ленивый, у нас будет вращающаяся пирамида. Ей потребуется всего 4 вершины и 4 треугольника, это гораздо проще для написания вручную…

Если смотреть на эти вершины сверху (вдоль оси Y), мы увидим такое расположение:

unsigned int Indices[] = { 0, 3, 1,
                           1, 3, 2,
                           2, 3, 0,
                           0, 2, 1 };

Буфер индексов заполняется с помощью массива индексов. Индексы указывают на расположение вершин в вершинном буфере. Если смотреть на массив и чертеж выше, то вы заметите, что последний треугольник - основание пирамиды, остальные же - ее грани. Пирамида не симметрична, но это легко исправляется.

glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(Indices), Indices, GL_STATIC_DRAW);

Мы создаем, а затем заполняем буфер индексов используя массив индексов. Вся разница в создании вершинного и индексного буферов в том, что для первого мы указываем GL_ARRAY_BUFFER, а для второго - GL_ELEMENT_ARRAY_BUFFER.

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);

В дополнении к привязыванию вершинного буфера мы делаем то же самое с индексным. И, мы снова используем GL_ELEMENT_ARRAY_BUFFER в качестве типа буфера.

glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);

Мы используем glDrawElements вместо glDrawArrays. Первый параметр - это тип примитивов (так же, как и у glDrawArrays). Второй параметр указывает какое количество индексов в индексном буфере будет использоваться. Следующий параметр - тип переменных в буфере индексов. GPU обязан знать размер каждого индекса, иначе он не сможет получить индексы из буфера. Возможные варианты: GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT, GL_UNSIGNED_INT. Если индексы малы, то вы захотите выбрать наименьший тип для экономии памяти и наоборот, в случае больших индексов требуются большие типы. Последний параметр передает GPU смещение в байтах от начала буфера индексов до до позиции первого индекса для обработки. Это полезно, когда один буфер индексов содержит индексы многих объектов. Указывая смещение и количество индексов возможно показать GPU какой объект рендерить. Мы хотим начать с начала, поэтому указываем 0. Заметим, что тип последнего параметра GLvoid*, поэтому если мы указываем что-либо отличное от 0, то необходимо преобразовать в этот тип.

powered byDisqus