Дополнительные возможности OpenGL 3/4

Flatshading

Для выходных атрибутов вершинного (геометрического) шейдера кроме стандартного варианта билинейной интерполяции переменной вдоль примитива при его растеризации была добавлена новая возможность. При помощи спецификатора flat можно указать, что значение данной переменной не нужно интерполировать, а следует взять значение от одной из вершин примитива.

flat out vec4 color4;

В качестве вершины, задающей значение для соответствующей flat-переменной, может выступать либо первая, либо последняя вершина примитива. Какую именно из них следует использовать задается при помощи команды glProvokingVertex.

void glProvokingVertex ( GLenum provokeMode );

Параметр provokeMode может принимать всего два значения - GL_FIRST_VERTEXT_CONVENTION (использовать для задания значений flat-переменных первую вершину примитива) и GL_LAST_VERTEX_CONVENTION (использовать последнюю вершину примитива). ИЗначально установлено значение GL_LAST_VERTEX_CONVENTION.

Расширение ARB_draw_elements_base_vertex

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

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

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

Рис 1. Использование смещения для выбора модели из вершинного буфера

Для вывода геометрии с заданием смещения были введены следующие новые команды.

void glDrawEelementsBaseVertex         ( GLenum mode, GLsizei count, GLenum type, void * indices, int baseVertex );
void glDrawRangeElementsBaseVertex     ( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, void * indices, int baseVertex );
void glDrawElementsInstancedBaseVertex ( GLenum mode, GLsizei count, GLenum type, const void * indices, GLsizei primCount, int baseVertex );
void glMultiDrawElementsBaseVertex     ( GLenum mode, GLsizei * count, GLenum type, void ** indices, GLsizei primCount, int baseVertex );

Данные команды эквивалентны соответствующим командам glDraw* и glMultiDraw*, отличие заключается в прибавлении смещения baseVertex при индексировании в массивы вершин.

Primitive restart

Как правило, сложная геометрия выводится не в виде отдельных треугольников (GL_TRIANGLES), а в более компактном виде используя режимы GL_TRIANGLE_STRIP и GL_TRIANGLE_FAN. Однако далеко не все объекты можно свести к одной triangle strip (или triangle fan), гораздо чаще объект представляется в виду нескольких strip'ов.

Однако при использовании стандартного способа вывода геометрии использование нескольких strip'ов приводит к необходимости использования нескольких команд glDraw* - по одной на каждый strip, что не очень эффективно.

Поэтому начиная с версии 3.1 OpenGL взял из расширения NV_primitive_restart возможность "склейки" нескольких однотипных примитивов в один. Для этого используется специальный номер вершины (primitive restart index), который обозначает конец одного примитива и начала следующего. Сама вершина с этим индексом просто отбрасывается: единственное ее назначение - разделять однотипные примитивы. Появление этой вершины эквивалентно началу новой команды glDraw*.

Таким образом пи помощи такого индекса можно легко склеить набор triangle strip'ов (или triangle fan'ов) в один большой блок и вывести его всего одной командой glDraw*. Номер вершины-разделителя не является фиксированным, а задается командой glPrimitiveRestartIndex.

void glPrimitiveRestartIndex ( GLuint index );

Для того чтобы использовать эту возможность, необходимо ее включить при помощи команды glEnable с параметром GL_PRIMITIVE_RESTART. Обратите внимание, что если вывод геометрии осуществляется при помощи команды gl*BaseVertex, то сравнение номера вершины с primitive restart index осуществляется до прибавления к индексу вершины смещения.

Рис 2. Объединение массивов индексов для отдельных моделей в один.

Расширение ARB_draw_indirect

Традиционно все аргументы для команд glDraw* передаются вызывающим кодом, т.е. являются передаваемыми CPU параметрами. Данное расширение, вошедшее в состав OpenGL 4.0, вводит новый тип вершинного буфера GL_DRAW_INDIRECT_BUFFER, задачей которого как раз является предоставление аргументов для команд glDraw*. Для этого вводятся две новые команды.

void glDrawArraysIndirect   ( GLenum mode, const void * indirect );
void glDrawElementsIndirect ( GLenum mode, GLenum type, const void * indirect );

Для задания данных для этих команд служат буфера типа GL_DRAW_INDIRECT_BUFFER, если такого буфера нет, то параметр indirect трактуется как указатель на соответствующую структуру в памяти CPU.

Если есть буфер, прикрепленный (bound) к цели GL_DRAW_INDIRECT_BUFFER, то параметр indirect рассматривается как смещение внутри соответствующего буфера и структура с данными берется уже из буфера.

Для команды glDrawArraysIndirect структура с данными имеет следующий вид:

struct
{
    GLuint count;
    GLuint primCount;
    GLuint firstIndex;
    GLuint reservedMustBeZero;
};

Для команды glDrawElementsIndirect структура, задающая данные для вывода имеет следующий вид:

struct
{
    GLuint count;
    GLuint primCount;
    GLuint firstIndex;
    GLuint baseVertex
    GLuint reservedMustBeZero;
};

Поля этих структур полностью соответствуют соответствующим параметрам команда glDraw*.

Данное расширение позволяет весь запрос на рендеринг геометрии полностью формировать на GPU, избегая тем самым лишних пересылок данных между CPU и GPU.

Условный рендеринг

Хотя в OpenGL и есть асинхронные запросы видимости, позволяющие проверить видимость относительно буфера глубины заданного набора геометрии, однако их широкое использование затрудняет высокая латентность данной операции - результат проверки сперва формируется на GPU и затем пересылается обратно на CPU для использования.

Поэтому в OpenGL 3.0 из расширения NVX_conditional_render была перенесена возможность условного рендеринга - с набором запросов на рендеринг связывается асинхронный запрос на видимость и если к началу рендеринга результат запроса будет готов, то в случае невидимости вся посланная геометрия будет сразу же отброшена прямо на GPU.

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

Для обозначения группы команд как условных служит команды glBeginConditionalRendering и glEndConditionalRendering. Все запросы на рендеринг, находящиеся между вызовами этих команд считаются условными и привязываются с запросу на видимость, идентификатор которого передается в команде glBeginConditionalRendering.

void glBeginConditionalRendering ( GLuint id, GLenum mode );
void glEndConditionalRendering   ();

Параметр id задает идентификатор асинхронного запроса на видимость, который связывается с данным блоком, а параметр mode задает поведение в случае когда к моменту готовности к рендерингу результаты запроса видимости еще не готовы. Этот параметр может принимать одно из следующих значений:

Расширение ARB_depth_clamp

В состав OpenGL 3.2 вошло расширение ARB_depth_clamp, позволяющее фактически убрать ближнюю плоскость отсечение.

Обычно вся геометрия отсекается по набору плоскостей, включающим в себя ближнюю и дальнюю плоскости отсечения (см. рис. ).

Рис. 3. Отсечение по ближней и дальней плоскостям отсечения.

После отсечения по этим плоскостям гарантируется, что у нас все значения глубины после масштабирования перейдут в отрезок [0,1].

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

В этом случае получаемое при интерполяции значение глубины (которое может теперь выйти за отрезок [0,1]) будет перед тестом глубины приведено к этом отрезку и обработано как имеющее уже приведенное значение глубины (а не отброшено как при использовании ближней и дальней плоскостей отсечения).

Для включения и выключения этой возможности следует использовать команды glEnable и glDisable с параметром GL_DEPTH_CLAMP.

glEnable ( GL_DEPTH_CLAMP );
    // perform some rendering with near and far planes clipping off
. . .
    // restore default behavoiur
glDisable ( GL_DEPTH_CLAMP );

Обратите внимание, что данное расширение относится не только к полигонам, но и ко всем выводимым примитивам.