Расширение ARB_vertex_array_object

Использование различных вершинных массивов (или VBO) позволяет легко перенести большой объем различных атрибутов вершин (таких как координаты, нормаль/бинормаль, цвет, текстурные координаты и т.п.) непосредственно на GPU и быстро осуществлять рендеринг данных.

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

void	Torus :: draw ()
{
    int vertexStride = sizeof ( Vertex );
    int texOffs      = ((int)&vertices [0].tex) - ((int)&vertices [0]);

                            // сохранить состояние и разрешить использование массивов
    glPushClientAttrib  ( GL_CLIENT_VERTEX_ARRAY_BIT );
    glEnableClientState ( GL_VERTEX_ARRAY );
    glEnableClientState ( GL_TEXTURE_COORD_ARRAY );

                            // задать источники данных для каждого из атрибутов вершины
    glBindBufferARB   ( GL_ARRAY_BUFFER_ARB, vertexId );
    glVertexPointer   ( 3, GL_FLOAT, vertexStride, (void *)0 );
    glTexCoordPointer ( 2, GL_FLOAT, vertexStride, (void *)texOffs );
    glBindBufferARB   ( GL_ELEMENT_ARRAY_BUFFER_ARB, indexId );
    glDrawElements    ( GL_TRIANGLES, 3*numFaces, GL_UNSIGNED_INT, 0 );
    glBindBufferARB   ( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
    glPopClientAttrib ();
}

Обратите внимание, что объем кода для настройки и восстановления всего двух атрибутов (координат вершины и ее текстурных координат) во много раз превосходит код для непосредственного рендеринга данных (одна команда glDrawElements).

Расширение ARB_vertex_array_object вводит новый тип объекта OpenGL - Vertex Array Object, сохраняющего в себе настройки (состояние OpenGL, относящееся к вершинным массивам) для используемых вершинных массивов. Досточно один раз задать все эти настройки, сохранив их в соответствующем VAO. После этого для рендеринга достаточно лишь "активировать" (bind) соответсвтующий VAO, чтобы все эти настройки вступили в силу. Также легко восстановить исходное состояние - достаточно выбрать другой VAO.

Каждый VAO, как и многие другие объекты OpenGL, идентифицируется при помощи беззнаковых целых чисел (GLuint). Идентификатор 0 зарезервирован. Для создания и уничтожения VAO служат следующие функции:

void glGenVertexArrays    ( GLsizei n, GLuint * arrays );
void glDeleteVertexArrays ( GLsizei n, GLuint * arrays );

Первая из них создает сразу n VAO и помещает их идентификаторы в массив arrays, вторая функция - уничтожает n VAO из массива arrays.

Для проверки того, является ли заданное число идентификатором какого-либо VAO можно при помощи следующей функции:

GLboolean glIsVertexArray ( GLuint array );

Функция glBindVertexArray "выбирает" (делает текущим) VAO с заданным идентификатором. После этого все команды, задающие параметры вершинныых буферов, изменяют состояние хранимое в данном VAO. Кроме того именно данный VAO и определяет текущие настройки вершинных буферов.

void glBindVertexArray ( GLuint array )

Для того, чтобы получить идентификтор активного в данный момент VAO служит следующий фрагмент кода:

GLuint curArray;

glGetIntegerv ( GL_VERTEX_ARRAY_BINDING, (int *)&curArray );

Обратите внимание, что есть определенные ограничения - нельзя "резделять" VAO между несколькими контекстами и единственным способом изменения VBO, ссылка на который хранится в VAO, является команда glBufferData.

Для удобства работы завернем VAO в класс на языке С++:

#ifndef __VERTEX_ARRAY_OBJECT__
#define __VERTEX_ARRAY_OBJECT__

#include    "libExt.h"

class   VertexArray
{
    GLuint  id;
    
public:
    VertexArray ()
    {
        glGenVertexArrays ( 1, &id );
    }
    
    ~VertexArray ()
    {
        glDeleteVertexArrays ( 1, &id );
    }
    
    bool    isOk () const
    {
        return glIsVertexArray ( id );
    }
    
    void    bind ()
    {
        glBindVertexArray ( id );
    }
    
    void    unbind ()
    {
        glBindVertexArray ( 0 );
    }
};

#endif

С использованием данного класса можно изменить фрагмент кода, приведенного в начале статьи. Один раз, сразу после создания вершинных буферов, необходимо создать VAO, и записать в него всю требуемую настройку:

int	vertexStride = sizeof ( Vertex );
int	texOffs      = ((int)&vertices [0].tex) - ((int)&vertices [0]);

vao = new VertexArray ();

vao -> bind ();

glPushClientAttrib  ( GL_CLIENT_VERTEX_ARRAY_BIT );
glEnableClientState ( GL_VERTEX_ARRAY );
glEnableClientState ( GL_TEXTURE_COORD_ARRAY );

glBindBufferARB   ( GL_ARRAY_BUFFER_ARB, vertexId );
glVertexPointer   ( 3, GL_FLOAT, vertexStride, (void *)0 );
glTexCoordPointer ( 2, GL_FLOAT, vertexStride, (void *)texOffs );

glBindBufferARB   ( GL_ELEMENT_ARRAY_BUFFER_ARB, indexId );

vao -> unbind ();

После этого код для рендеринга станет выглядеть следующим образом:

vao -> bind ();

glDrawElements    ( GL_TRIANGLES, 3*numFaces, GL_UNSIGNED_INT, 0 );

vao -> unbind ();

Ряд примеров на использование VAO можно найти на nopper.tv/opengl.html.

По этой ссылке можно скачать весь исходный код к этой статье.

Также доступны для скачивания откомпилированные версии для M$ Windows и Linux.

Обратите внимание, что для использование кода из этой статьи потребуется порследняя версия библиотеки libExt.