{{notification.text}}

MirGames

Первый вопрос, который должен возникнуть у непросвещенного читателя, это – «А что это такое?». И я приступаю к объяснению сего вопроса без лишних прелюдий. Расширение GL_ARB_Vertex_Buffer_Object предназначено для передачи вертексных массивов данных из памяти CPU к памяти GPU и обратно.

Для начала проведем небольшой экскурс в основы OpenGL. Чтобы нарисовать полигон на экране необходимо указать координаты вершин этого полигона. Это можно сделать различными способами.

Первый и самый «тормозной» способ это использования логических скобок glBegin()…glEnd с указанием между ними координат вершин полигона. К сожалению, этот способ не пригоден для отрисовки многополигональных мешей, максимум, где он может пригодиться это GUI, SkyBox’ы и т.д.

Поэтому перейдем ко второму способу, использованию вертексных массивов. Этот способ дает огромный прирост в FPS по сравнению с glBegin…glEnd. Главным плюсом является то, что все данные передаются видеокарте сразу, а не маленькими порциями. Вертексные массивы, конечно, ускоряют рендериннг но не на столько, на сколько хотелось бы.

Почти на любой видеокарте установлена видеопамять (Intel Extreme Graphics в расчет не берем), почему бы не хранить вершинные данные в ней, ведь в этом случае мы:

  1. Более экономно используем время процессора (ну что ему заняться больше нечем, чем модель перед каждым рендером в видеопамять отправлять?)
  2. Освобождаем место в оперативной памяти.
  3. И главное — меш хранится в видеопамяти, которая в разы быстрее оперативной.

Следует упомянуть также, что VBO поддерживается на всех современных видеокарточках (опять таки не берем в расчет Intel Extreme Graphics) начиная с GF2 MX. Думаю, что этих доводов достаточно, чтобы убедить читателя в полезности сего расширения, так что перейду непосредственно к рассказу о его практическом использовании и оптимизации.

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

Данные в видеопамять передаются посредством вершинных буферов(vertex buffers), по сути вершинный буфер – это просто массив байт, работа с VBuffer’ами реализована почти также как с текстурами. Сначала необходимо создать вертексный буфер, в котором будут храниться данные. Для этого предназначена процедура:

procedure glGenBuffersARB(n: GLsizei; buffers: PGLuint);

Создает n вертексных буферов, в параметре buffers возвращается указатель на массив индексов этих буферов, в случае не успешности операции в buffers возвращается nil. Полученный в результате этой нехитрой операции индекс нужно использовать в процессе загрузки данных в буфер и во время рендера, для того чтобы указать текущий буфер из которого будут браться данные.

Чтобы производить какие-либо махинации с созданным буфером необходимо сделать его текущим, для этого предназначена процедура:

procedure glBindBufferARB(target: GLenum; buffer: GLenum);

Делает текущим буфер с индексом buffer, параметр target принимает одну из двух констант:

  1. GL_ELEMENTS_ARRAY_BUFFER_ARB – созданный буфер будет хранить порядок точек в буфере (попросту грани).
  2. GL_ARRAY_BUFFER_ARB – созданный буфер будет хранить вершинные данные (точки, нормали, текстурные координаты и т.д.).

Теперь необходимо полученный буфер наполнить данными, сделать это можно двумя способами. Разберем оба, а там уж сами решите какой из них удобней.

Первый способ загрузки данных

Сущность первого способа почти ничем не отличается от загрузки данных текстуры, следует отметить, что он является более популярным и часто используемым, но и в том и в другом случае необходимо вызвать функцию:

procedure glBufferDataARB (target: GLenum; size: GLsizei; const data: PGLuint; usage: GLenum);

Наполняет текущий буфер данными, передаваемыми в массиве из size байт в указателе data, параметр target имеет тот же смысл что и в glBindBufferARB, usage – одна из констант:
GL_STREAM_DRAW_ARB, GL_DYNAMIC_DRAW_ARB, GL_STATIC_DRAW_ARB, GL_STREAM_READ_ARB, GL_DYNAMIC_READ_ARB, GL_STATIC_READ_ARB, GL_STREAM_COPY_ARB, GL_DYNAMIC_COPY_ARB и GL_STATIC_COPY_ARB.

Эти константы указывают на то, как предполагается использовать буфер:

  1. GL_ STATIC… — следует использовать когда вы не собираетесь обновлять данные находящиеся в массивах.
  2. GL_ STREAM…- в точности наоборот, т.е. используется только тогда, когда данные находящиеся в массивах каждый кадр обновляются. Может использоваться например, для реализации анимации, хотя в этом случае мы не получим никакого прироста в производительности от VBO, ведь данные будут каждый раз обновляться и подгружаться из памяти, для такого рода задач лучше использовать шейдер.
  3. GL_DYNAMIC… — в данном случае предполагается что данные будут часто обновляться.

…READ…, …COPY…, …DRAW… — указывают на то, что буфер будет использоваться для соответственно, чтения данных из него, как для чтения, так и для вывода и только для вывода.

Еще одна полезная функция:

procedure glBufferSubDataARB(target: GLenum; offset: GLsizei; size: GLsizei; const data: pointer);

Изменяет данные в уже заполненном буфере, параметр target имеет то же смысл что и в других подобных функциях, offset – задает смещение в байтах относительно начала буфера, size — размер изменяемого блока данных в байтах, data – указатель на массив данных.

Второй способ загрузки данных

Суть второго способа именуемого mapping’ом, заключается в отображении буфера в оперативную память и получении указателя на этот блок памяти с которым программист может делать тоже что и с обычным массивом.

Сначала необходимо включить отображение буфера в память:

function glMapBufferARB (target: GLenum; access: GLenum): Pointer;

Отображает текущий буфер в системную память и возвращает адрес на образ буфера в системной памяти. Параметр target имеет тот же смысл что и в других подобных функциях, access – указывает на тип доступа к буферу и может принимать одну из следующих констант: GL_READ_ONLY_ARB, GL_WRITE_ONLY_ARB или GL_READ_WRITE_ARB.

Как ясно из названий констант они указывают, что буфер будет использован соответственно только для чтения из него, только для записи или как для чтения, так и для записи. Если операция неуспешна, то в качестве указателя будет возвращен nil.

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

Узнать размер текущего буфера можно при помощи процедуры:

procedure glGetBufferParameterivARB (target: GLenum; pname: GLenum; params: Pointer);

Возвращает в параметре params указатель на информацию, о текущем буфере задаваемую одной из констант:

  1. GL_BUFFER_SIZE_ARB – возвращает размер текущего буфера в байтах.
  2. GL_BUFFER_USAGE_ARB – возвращает частоту обновления буфера и его использование (GL_STREAM_DRAW_ARB, GL_DYNAMIC_COPY_ARB, GL_STATIC_READ_ARB и т.д.)
  3. GL_BUFFER_ACCESS_ARB– возвращает тип доступа к буферу (GL_READ_ONLY_ARB, GL_WRITE_ONLY_ARB или GL_READ_WRITE_ARB).
  4. GL_BUFFER_MAPPED_ARB – возвращает текущее состояния буфера при использовании mapping’a (GL_TRUE -открыт, GL_FALSE – закрыт).

Когда данные будут перемещены в образ буфера в оперативной памяти, его следует закрыть:

function glUnmapBufferARB (target: GLenum): GLboolean;

Закрывает отображение текущего буфера в системную память, параметр target имеет тот же смысл что и в других подобных функциях. Если операция успешна будет возвращен true, если в процессе операций над образом его повредили, то будет возвращен false. После закрытия отображения буфера полученный ранее указатель перестает быть правильным и для того чтобы изменить данные в буфере необходимо вызвать glMapBufferARB.

Рендер

С инициализацией вроде разобрались, приступим к рендеру.

Рендер VBuffer’ов осуществляется с помощью стандартных функций glVertexPointer, glNormalPointer, glDrawElements и т.д. Далее я приведу откомментированный код иллюстрирующий рендер модели в которой F_Count полигонов, VBO_VERTEX – идентификатор буфера с вершинами модели, VBO_FACES — идентификатор буфера с гранями модели.

//включаем массивы
  glEnableClientState(GL_VERTEX_ARRAY);
//биндим буфер с геометрией
  glBindBufferARB(GL_ARRAY_BUFFER_ARB,VBO_VERTEX);
//в качестве указателя на массив с вершинами выставляем nil
//если поставить целое число, то оно будет задавать смещение в байтах относительно начала буфера 
  glVertexPointer(3,GL_FLOAT,0, nil);
//биндим буфер с гранями
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB ,VBO_FACES);
//непосредственно рендер
  glDrawElements(GL_TRIANGLES,3*F_Count,GL_UNSIGNED_SHORT,nil);
//биндим нулевой буфер 
  glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);
  glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB,0);
//выключаем массивы
  glDisableClientState(GL_VERTEX_ARRAY);

Все довольно таки просто, следует, однако отметить, что после вызова glBindBufferARB(…,0); мы можем использовать обыкновенный рендеринг с использованием вертексных массивов.

Когда вершинный буфер вам будет не нужен, необходимо освободить занимаемую им память:

Procedure glDeleteBuffersARB (n: GLsizei; buffers: PGLuint);

Уничтожает n вершинных буферов идентификационные номера которых передаются в указателе buffers.

Проверить существует ли буфер с заданным идентификационным номер можно при помощи команды:

function glIsBufferARB(buffer: GLuint): GLboolean;

Проверяет, существует ли буфер с порядковым номером buffer, в зависимости от результата проверки возвращает true или false.

Пара советов по оптимизации

В заключении приведу пару советов по оптимизации VBO.

  1. Храните все вершинные данные (точки, нормали, текстурные координаты, цвет и т.д.) в одном вертексном буфере, а во время рендера задавайте смещение относительно начала буфера, эта нехитрая операция позволяет сэкономить время, тратящееся на переключение между буферами.
  2. Для вывода статической геометрии используйте только GL_STATIC_DRAW_ARB в параметре usage функции glBufferDataARB, это существенно ускоряет рендер.
Ссылки по теме

http://www.gamedev.ru/articles/?id=20124
http://steps3d.narod.ru/tutorials/tutorial-VBO.html


Пример VBO
08.12.06 22:46