Вершинные программы. Расширение ARB_vertex_program
Ранее рассматриваемые расширения позволяли задавать довольно сложные законы рендеринга примитивов, но
при этом сами эти законы задавались фактически путем комбинации небольшого числа предопределенных
шагов (операций) и выйти за их пределы было очень затруднительно.
Совершенно другой подход предлагают так называе вершинные (vertex) и фрагментные (fragment) программы.
Они представляют собой полноценные программы, написанные на специальном ассемблеро-подобном языке и
выполняются непосредственно на GPU. С помощью таких программ можно задавать очень сложные законы
вычисления, практически невозможные для других способов.
Существует два основных типа подобных программ (шейдеров) - вершинные (vertex program или
vertex shader) и фрагментные (пиксельные)(fragment program или fragment shader).
Отличие между ними заключается в том, к обработке каких элементов они применяются.
Вершинные программы применяются для обработки данных, заданных на уровне отдельных вершин, в то время
как фрагментные программы работают на уровне отдельных пикселов.
В данной статье мы рассмотрим использование вершинных программ через расширение ARB_vertex_program.
В стандартном OpenGL имеется набор заранее определенных опеарций над даннымм, задаваемыми в вершинах,
например, преобразование вершин, вычисление и преобразование текстурных координат, освещение и т.п.
Большинство из этих операций могут конфигурироваться программистом.
Фактически все эти операции представляют собой вершинную программу, однако в ней выполняемые операции
и их порядок заранее задан и зависит от текущего состояния OpenGL, а не явно задается программистом.
Расширение ARB_vertex_program предоставляет пользователю возможность явно задавать закон
преобразования данных в вершинах при помощи последовательности элементарных операций (команд).
Подобные вершинные программы работают с набором регистров (4-мерных вещественных векторов) и каждая
команда осуществляет какую-либо операцию над этими регистрами.
Сама программа представляет собой последовательность действий над 4-х мерными векторами, задающая
закон, который на основе параметров (задаваемых вне блока glBegin/glEnd) и набора атрибутов в
вершине определяет выходные значения для каждой вершины.
Стандратный конвеер (pipeline) OpenGL выглядит следующим образом:
Рис 1. Стандартный конвеер в OpenGL.
Использование вершинных программ позволяет заменить первый блок - преобразование и освещение - на явно
задаваемые пользователем вершинные программы.
Эти программы дают полный контроль над операциями преобразования, освещения, вычисления текстурных
координат, а также дает возможность выполнять другие типы вычислений в вершинах.
При этом все вершинные программы полностью выполняются на GPU, не занимая времени центрального
процессора.
Входными данными для вершинной программы являются атрибуты в вершинах (пользователь может задавать
новые типы атрибутов в вершинах), локальные переменные (регистры) и переменные окружения.
На выходе вершинной программы должны быть вычислены однородные координаты вершины, цвет, степень
затуманивания, текстурнеы координаты и т.п.
При этом вершинная программа не может ни создавать новых вершин, ни уничтожать существющие вершиные.
Также вершинная программа не обладает никакой топологической информацией (ребра, грани и т.п.).
Обычно вершинная программа заменяет собой следующую функциональность OpenGL:
- преобразования вершин при помощи модельной (modelview)матрицы и матрицы проектирования (projection)
- смешение (weighting/blending) вершин
- преобразования нормали
- вершинное освещение
- цветные материалы
- вычисление текстурных координат и их преобразование
- вычисления степени затуманивания вершины
- задаваемые пользователем операции
Следующая функциональность не может быть подменена вершинной программой:
- evaluators
- отсечение по области видимости (view frustrum)
- перспективное деление
- viewport transformation
- преобразование диапазона глубины
- выбор лицевой и нелицевой граней (для двусторонних материалов)
- отсечение первичного и вторичного цветов по отрезку [0,1]
- работу с примитивами, растеризацию, смешение цветов
На следующем рисунке изображена вершинная программа и различные множества регистров, с которыми она
работает.
Рис 2. Вершинная программа и используемые ею регистры.
Создание вершинной программы
Каждая вершинная программа представляется набором байт (ASCII строкой). Работа с вершинной программой
напоминает работу с текстурами - вводится понятие объекта-программы (program object),
идентифицируемого беззнаковым целым числом.
Этот объект - это внутренний объект OpenGL, инкапсулирующий внутри себя как программу, так и параметры
состояния.
Непосредственное создание вершинной программы состоит из следующих шагов - получение идентификатора для
программы при помощи функции glGenProgramsARB, выбор полученного идентификатора как текущего при помощи
функции glBindProgramARB и загрузка текста программы. Все эти шаги продемонстрированы следующим
фрагментом кода.
const char * vpText =
"!!ARBvp1.0\
ATTRIB pos = vertex.position;\
PARAM mat [4] = { state.matrix.mvp };\
# transform by concatenation of modelview and projection matrices\
DP4 result.position.x, mat [0], pos;\
DP4 result.position.y, mat [1], pos;\
DP4 result.position.z, mat [2], pos;\
DP4 result.position.w, mat [3], pos;\
# copy primary color\
MOV result.color, vertex.color;\
END";
GLuint progId;
glGenProgramsARB ( 1, &progId );
glBindProgramARB ( GL_VERTEX_PROGRAM_ARB, progId );
glProgramStringARB ( GL_VERTEX_PROGRAM_ARB, GL_PROGRAM_FORMAT_ASCII_ARB,
strlen ( vpText ), vpText );
if ( glGetLastError () == GL_INVALID_OPERATION )
{
GLint errorPos;
GLubyte * errorStr;
glGetIntegerv ( GL_PROGRAM_ERROR_POSITION_ARB, &errorPos );
errorString = glGetString ( GL_PROGRAM_ERROR_STRING_ARB );
fprintf ( stderr, "Error at position %d.\Line: \"%s\"", errorPos, errorStr );
}
После того, как вершинная программа больше не нужна, она может быть уничтожена при помощи функции
glDeleteProgramsARB.
Рассмотрим подробнее использованные функции.
Для получения идентификаторов вершинных программ служит следующая функция -
void glGenProgramsARB ( GLsizei n, GLuint * ids );
Эта функция резервирует n идентификаторов для использования их в качестве идентификаторов
вершинных программ и помещает зарезервированные идентификаторы в массив ids. При этом
возвращенные идентификаторы помечаются как использованные, однако объекта-программы еще не создается,
он будет создан при первом выборе идентификатора как текущей программы при помощи glBindProgramARB.
Для уничтожения вершинных программ (и освобождения соответствующих идентификаторов) служит следующая
функция -
void glDeleteProgramsARB ( GLsizei n, GLuint * ids );
Для выбора данной вершинной программы как текущей (и создания объекта-программы при первом обращении)
служит функция
void glBindProgramARB ( GLenum target, GLuint id );
При задании вершинной программы параметр target должен принимать значения
GL_VERTEX_PROGRAM_ARB, а параметр id - идентификатор программы, полученный от glGenProgramsARB.
Проверить, является ли заданное беззнаковое целое число идентификатором программы можно при помощи
функции
GLboolean glIsProgramARB ( GLuint progId );
Для задания текста вершинной программы служит функция
void glProgramStringARB ( GLenum target, GLenum format, GLsizei length, const void * string );
Параметр target для задания текста вершинной программы должен принимать значение
GL_VERTEX_PROGRAM_ARB, параметр format может принимать пока только одно значение
GL_PROGRAM_FORMAT_ASCII_STRING_ARB. Параметр length задает длину строки (в байтах)
string, которая задает текст программы. Длина считается без нулевого терминатора и
строка вообще может не иметь нулевой терминатор в конце.
В соответствии со стандартом можно загружать программу, даже если она уже была загружена и выбрана
как текущая.
Одна и та же вершинная программа может разделяться сразу несколькими контекстами точно так же,
как и дисплейные списки и текстуры, т.е. при помощи команды wglShareLists ( hRcTo, hRcFrom ).
Задание параметров
Все параметры, которые могут быть переданы вершинной программе, делятся на три типа:
- вершинные атрибуты (vertex attributes)
- локальные параметры программы (local parameters)
- параметры окружения (environment parameters)
Вершинные атрибуты
Это параметры, задаваемые в каждой вершине и представляющие собой 4-мерные вещественные вектора. Всего
может быть до N таких параметров (N>=16), точно максимальное количество вершинных
атрибутов можно получить при помощи функции glGetProgramivARB по значению константы
GL_MAX_VERTEX_ATTRIBS_ARB:
int maxAttribs;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_VERTEX_ATTRIBS_ARB, &maxAttribs );
Для задания вершитнных атрибутов в виде вещественных (float) 3-х и 4-х мерных векторов служат
следующие функции
void glVertexAttrib4fARB ( GLuint index, float x, float y, float z, float w );
void glVertexAttrib4fvARB ( GLuint index, const float * vector );
void glVertexAttrib3fARB ( GLuint index, float x, float y, float z );
void glVertexAttrib3fvARB ( GLuint index, const float * vector );
Существуют версии этой команды и для меньшего числа аргументов и для других типов передаваемых
аргументов по аналогии с командами glVertex и glColor.
Также существует возможность передачи вершинных атрибутов в виде вершинных массивов при помощи
следующей функции
void glVertexAttribPointerARB ( GLuint index, GLint size, GLenum type,
GLboolean normalized, GLsizei stride,
const void * pointer );
Параметр normalized сообщает нужно ли проводить нормализацию переданного значения.
Можно включать и отключать поддержку отдельных вершинных массивов при помощи функций
void glEnableVertexAttribArrayARB ( GLuint index );
void glDisableVertexAttribArrayARB ( GLuint index );
Запись вершинного атрибута с индексом 0 вызывает выполнение вершинной программы (????)
Из самой программы можно обращаться к вершинным атрибутам как по индексу, так и используя имена для
стандартных атрибутов. Соответствие между индексами и именами приводаится в таблице ниже.
Таблица 1. Соответствие между индексами и именами вершинных атрибутов.
Имя | Индекс |
vertex.position | vertex.attrib [0] |
vertex.weight | verter.attrib [1] |
vertex.weight [0] | verter.attrib [1] |
vertex.normal | verter.attrib [2] |
vertex.color | verter.attrib [3] |
vertex.color.primary | verter.attrib [3] |
vertex.color.secondary | verter.attrib [4] |
vertex.fogcoord | vertex.attrib [5] |
vertex.texcoord | verter.attrib [8] |
vertex.texcoord [0] | verter.attrib [8] |
vertex.texcoord [n] | verter.attrib [8+n] |
Обратите внимание на то, что по стандарту приведенные пары соержат значения, которые не могут
быть использованы однолвременно, например, если в программе используется сразу и обращение к
vertex.position и обращение к vertex.attrib [0], то это приведет к ошибке при
загрузке данной программы.
Задание вершинного атрибута с индексом 0 (включая и задание посредством команды glVertex*) вызывает
выполнение программы для данной вершины.
Локальные параметры
Каждая программа получает в свое распоряжение массив из N 4-мерных вещественных векторов в
качестве своих локальных параметров. Точно узнать максимальное количество локальных параметров
можно при помощи следующего фрагмента кода, но их количество должно быть не менее 96.
При этом локальные параметры сохраняют свои значения не только между вызовами вершинной программы,
но и даже при перезагрузке самой вершинной программы.
int maxLocalParams;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB,
&maxLocalParams );
Для задания локальных параметров служат следующие функции
void glProgramLocalParameter4fARB ( GLenum target, GLuint index, float x, float y,
float z, float w );
void glProgramLocalParameter4fvARB ( GLenum target, GLuint index, const float * vector );
В качестве параметра target во всех этих функциях выступает GL_VERTEX_PROGRAM_ARB.
Из самой вершинной программы локальные программы доступны по имени program.local[index].
Узнать значение локального параметра можно при помощи функции
void glGetProgramLocalParameterfvARB ( GLenum target, GLuint index, float * params );
В качестве параметра target по-прежнему выступает константа GL_VERTEX_PROGRAM_ARB, параметр
params указывает на массив с достаточным местом для размещения 4 вещественных чисел.
Параметры окружения
Кроме вершинных атрибутов, задаваемых в каждой вершине, и локальных параметров, уникальных для каждой
вершинной программы, вершинная программа также получает с свое распоряжение массив из N
4-мерных вещественных векторов - так называемых параметров окружения. Этот набор параметров является
общим для всех вершинных програм.
Максимальное количество параметров окружения можно узнать при помощи следующего фрагмента кода, но
оно должно быть не менее 96.
int maxEnvParams;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB,
&maxEnvParams );
Для задания значений параметров окружения служат следующие функции
void glProgramEnvParameter4fARB ( GLenum target, GLuint index, float x, float y,
float z, float w );
void glProgramEnvParameter4fvARB ( GLenum target, GLuint index, const float * vector );
Из самой вершинной программы локальные программы доступны по имени program.env[index].
Узнать значение параметра окружения с заданным индексом можно при помощи функции
void glGetProgramEnvParameterfvARB ( GLenum target, GLuint index, float * params );
В качестве параметра target по-прежнему выступает константа GL_VERTEX_PROGRAM_ARB, параметр
params указывает на массив с достаточным местом для размещения 4 вещественных чисел.
Параметры состояния
Также из вершинной программы доступны параметры состояния OpenGL. В следующих таблицах приводятся
основные параметры и имена для доступа к ним.
Таблица 2. Свойства материала.
Имя | Компоненты | Комментарий |
state.material.ambient | (r,g,b,a) | Фоновый цвет материала для лицевой грани |
state.material.diffuse | (r,g,b,a) | Диффузный цвет материала для лицевой грани |
state.material.specular | (r,g,b,a) | Бликовый цвет материала для лицевой грани |
state.material.emission | (r,g,b,a) | Цвет свечения материала для лицевой грани |
state.material.shininess | (s,0,0,1) | Степень Фонга материала для лицевой грани |
state.material.front.ambient | (r,g,b,a) | Фоновый цвет материала для лицевой грани |
state.material.front.diffuse | (r,g,b,a) | Диффузный цвет материала для лицевой грани |
state.material.front.specular | (r,g,b,a) | Бликовый цвет материала для лицевой грани |
state.material.front.emission | (r,g,b,a) | Цвет свечения материала для лицевой грани |
state.material.front.shininess | (s,0,0,1) | Степень Фонга материала для лицевой грани |
state.material.back.ambient | (r,g,b,a) | Фоновый цвет материала для нелицевой грани |
state.material.back.diffuse | (r,g,b,a) | Диффузный цвет материала для нелицевой грани |
state.material.back.specular | (r,g,b,a) | Бликовый цвет материала для нелицевой грани |
state.material.back.emission | (r,g,b,a) | Цвет свечения материала для нелицевой грани |
state.material.back.shininess | (s,0,0,1) | Степень Фонга материала для нелицевой грани |
Таблица 3.. Параметры источников света.
Имя | Компоненты | Комментарий |
state.light[n].ambient | (r,g,b,a) | Фоновый цвет источника света n |
state.light[n].diffuse | (r,g,b,a) | Диффузный цвет источника света n |
state.light[n].specular | (r,g,b,a) | Бликовый цвет источника света n |
state.light[n].position | (x,y,z,w) | Координаты источника света n |
state.light[n].attenuation | (a,b,c,e) | Коэффициента затухания в зависимости от расстояния и показатель для конического источника для источника света n |
state.light[n].spot.direction | (x,y,z,c) | Направление для конического источника света и косинус его угла (для источника света n) |
state.light[n].half | (x,y,z,1) | Нормализованный серединный угол между направлением от глаза на источника света и вектором (0,0,1) |
state.lightmodel.ambient | (r,g,b,a) | Фоновый цвет модели освещения |
state.lightmodel.scenecolor | (r,g,b,a) | Цвет сцены в модели освещения для лицевых граней |
state.lightmodel.front.scenecolor | (r,g,b,a) | Цвет сцены в модели освещения для лицевых граней |
state.lightmodel.back.scenecolor/TD> | (r,g,b,a) | Цвет сцены в модели освещения для нелицевых граней |
state.lightprod[n].ambient | (r,g,b,a) | Произведение фонового цвета источника света и материала |
state.lightprod[n].diffuse | (r,g,b,a) | Произведение диффузного цвета источника света и материала |
state.lightprod[n].specular | (r,g,b,a) | Произведение бликового цвета источника света и материала |
state.lightprod[n].front.ambient | (r,g,b,a) | Произведение фонового цвета источника света и материала для лицевой грани |
state.lightprod[n].front.diffuse | (r,g,b,a) | Произведение диффузного цвета источника света и материала для лицевой грани |
state.lightprod[n].front.specular | (r,g,b,a) | Произведение бликового цвета источника света и материала для лицевой грани |
state.lightprod[n].back.ambient | (r,g,b,a) | Произведение фонового цвета источника света и материала для нелицевой грани |
state.lightprod[n].back.diffuse | (r,g,b,a) | Произведение диффузного цвета источника света и материала для нелицевой грани |
state.lightprod[n].back.specular | (r,g,b,a) | Произведение бликового цвета источника света и материала для нелицевой грани |
Таблица 4.. Параметры генерации текстурных координат.
Имя | Компоненты | Комментарий |
state.texgen[n].eye.s | (a,b,c,d) | Коэффициенты для получения s-координаты для текстурного блока n через координаты в системе координат наблюдателя |
state.texgen[n].eye.t | (a,b,c,d) | Коэффициенты для получения t-координаты для текстурного блока n через координаты в системе координат наблюдателя |
state.texgen[n].eye.r | (a,b,c,d) | Коэффициенты для получения r-координаты для текстурного блока n через координаты в системе координат наблюдателя |
state.texgen[n].eye.q | (a,b,c,d) | Коэффициенты для получения q-координаты для текстурного блока n через координаты в системе координат наблюдателя |
state.texgen[n].object.s | (a,b,c,d) | Коэффициенты для получения s-координаты для текстурного блока n через координаты в системе координат объекта |
state.texgen[n].object.t | (a,b,c,d) | Коэффициенты для получения t-координаты для текстурного блока n через координаты в системе координат объекта |
state.texgen[n].object.r | (a,b,c,d) | Коэффициенты для получения r-координаты для текстурного блока n через координаты в системе координат объекта |
state.texgen[n].object.q | (a,b,c,d) | Коэффициенты для получения q-координаты для текстурного блока n через координаты в системе координат объекта |
Таблица 5.. Параметры вычисления затуманивания.
Имя | Компоненты | Комментарий |
state.fog.color | (r,g,b,a) | RGB-цвет тумана |
state.fog.params | (d,s,e,r) | Плотность тумана (d), линейное начало и конец (s,e) и l/(end-start) |
Таблица 6.. Параметры плоскостей отсечения.
Имя | Компоненты | Комментарий |
state.clip[n].plane | (a,b,c,d) | Коэффициенты плоскости отсечения n |
Таблица 7.. Параметры точки.
Имя | Компоненты | Комментарий |
state.point.size | (s,n,x,f) | Параметры точки - размер, минимальный и максммальный размер, граница ослабевания (fade threshold) |
state.point.attenuation | (a,b,c,1) | Коэффициенты ослабевания точки |
Также программе доступен ряд матриц, перечисленных в следующей таблице.
Таблица 8.. Матрицы.
Имя | Комментарий |
state.matrix.modelview[n] | n-я матрица модельного преобразования. Параметр n необязателен. |
state.matrix.projection | Матрица проектирования |
state.matrix.mvp | Произведение матрицы модельного преобразования и проектирования: MVP = P * M[0], |
state.matrix.texture[n] | n-я матрица преобразования текстурных координат. Параметр n необязателен. |
state.matrix.palette[n] | Палитровая матрица n-го модельного преобразования |
state.matrix.program[n] | n-я матрица программы |
При этом к имени матрицы из таблицы 8 может бать добавлено ".inverse", ".transpose" и ".invtrans" для
обозначения того, что следует указанную матрицу обратить/транспонировать/обратить и транспонировать.
Также к имени может быть добавлена ".row [n]" для получения заданного столбца матрицы как 4-х мерного
вектора.
Запись state.matrix.program[n] используется для доступа к домолнительным матрицам. Из основной
программы способ задания этих матриц аналогичен заданию матрицы модельного преобразования,
проектирования и преобразования текстурных координат: вызывается функция glMatrixModeс параметром,
равным GL_MATRIXn_ARB, где в качестве числа n используется номер матрицы.
Для получения максимально возможного числа таких матриц можно использовать следующий фрагмент кода
int maxMatrices;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_MATRICES_ARB,
&maxMatrices );
Результаты выполнения вершинная программа заносит в выходные регистры, приведенные в следующей
таблице.
Таблица 9.. Регистры результата.
Имя | Компоненты | Комментарий |
result.position | (x,y,z,w) | Координаты точки в пространстве отсечения |
result.color | (r,g,b,a) | Главный (primary) цвет лицевой грани |
result.color.primary | (r,g,b,a) | Главный (primary) цвет лицевой грани |
result.color.secondary | (r,g,b,a) | Дополнительный (secondary) цвет лицевой грани |
result.color.front | (r,g,b,a) | Главный (primary) цвет лицевой грани |
result.color.front.primary | (r,g,b,a) | Главный (primary) цвет лицевой грани |
result.color.front.secondary | (r,g,b,a) | Дополнительный (secondary) цвет лицевой грани |
result.color.back | (r,g,b,a) | Главный (primary) цвет нелицевой грани |
result.color.back.primary | (r,g,b,a) | Главный (primary) цвет нелицевой грани |
result.color.back.secondary | (r,g,b,a) | Дополнительный (secondary) цвет нелицевой грани |
result.fogcoord | (f,*,*,*) | Затуманивание (for coordinate) |
result.pointsize | (s,*,*,*) | Размер точки |
result.texcoord | (s,t,r,q) | Текстурная координата для нулевого текстурного блока |
result.texcoord [n] | (s,t,r,q) | Текстурная координата для n-го текстурного блока |
Ниже приводится фрагмент программы, печатающей информацию о константах, ограничивающий количество
регистров, параметрво, временных переменных и т.п.
int maxVertexAttribs;
int maxLocalParams;
int maxEnvParams;
int maxMatrices;
int maxTemporaries;
int maxParams;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_VERTEX_ATTRIBS_ARB, &maxVertexAttribs );
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB, &maxLocalParams );
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_ENV_PARAMETERS_ARB, &maxEnvParams );
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_MATRICES_ARB, &maxMatrices );
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB, &maxTemporaries );
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB, &maxParams );
printf ( "Max vertex attributes : %d\n", maxVertexAttribs );
printf ( "Max local parameters : %d\n", maxLocalParams );
printf ( "Max env. parameters : %d\n", maxEnvParams );
printf ( "Max program matrices : %d\n", maxMatrices );
printf ( "Max program temporaries: %d\n", maxTemporaries );
printf ( "Max parameters : %d\n", maxParams );
Полностью данная программа содержится в исходном коде к статье, доступном
здесь для скачивания.
Структура вершинной программы
Вершинная программа представляет из себя набор строк, каждой строке соответствует какая-то команда (она
также может быть пустой или содержать коментарий). Ниже приводится пример вершинной программы
вычисляющей фоновое, диффузное и бликовое освещение от одного бесконечно-удаленного источника
света.
!!ARBvp1.0
ATTRIB iPos = vertex.position;
ATTRIB iNormal = vertex.normal;
PARAM mvinv[4] = { state.matrix.modelview.invtrans };
PARAM mvp[4] = { state.matrix.mvp };
PARAM lightDir = state.light[0].position;
PARAM halfDir = state.light[0].half;
PARAM specExp = state.material.shininess;
PARAM ambientCol = state.lightprod[0].ambient;
PARAM diffuseCol = state.lightprod[0].diffuse;
PARAM specularCol = state.lightprod[0].specular;
TEMP xfNormal, temp, dots;
OUTPUT oPos = result.position;
OUTPUT oColor = result.color;
# Transform the vertex to clip coordinates.
DP4 oPos.x, mvp[0], iPos;
DP4 oPos.y, mvp[1], iPos;
DP4 oPos.z, mvp[2], iPos;
DP4 oPos.w, mvp[3], iPos;
# Transform the normal to eye coordinates.
DP3 xfNormal.x, mvinv[0], iNormal;
DP3 xfNormal.y, mvinv[1], iNormal;
DP3 xfNormal.z, mvinv[2], iNormal;
# Compute diffuse and specular dot products and use LIT to compute
# lighting coefficients.
DP3 dots.x, xfNormal, lightDir;
DP3 dots.y, xfNormal, halfDir;
MOV dots.w, specExp.x;
LIT dots, dots;
# Accumulate color contributions.
MAD temp, dots.y, diffuseCol, ambientCol;
MAD oColor.xyz, dots.z, specularCol, temp;
MOV oColor.w, diffuseCol.w;
END
Первая строка программы должна быть "!!ARBvp1.0", сообщающая, что далее идет вершинная программа,
соответствующая версии 1.0.
Каждая команда (кроме команды END) должна завершаться точкой с запятой ";".
Завершается вершинная программа командой END.
Весь текст, идущий после символа "#" и до конца строки является комментарием и игнорируется.
Обратите внимание на то, что все имена команд, ключевые слова, используемые при объявлении переменных,
а также слова vertex, state, program и result, являются зарезервированными.
Идентификаторы
В качестве идентификаторов в вершинной программе служат произвольные последовательности латинских букв,
цифр и символа подчеркивания ("_") и знака доллара ("$"), начинающаяся не с цифры (например, Mu$st_Die).
Обратите внимание, что идентификаторы в вершинных программых чувствительны к регистрам букв, т.е.
идентификаторы а1 и А1 различаются.
Каждый идентификатор переменной до его использования должен быть объявлен, само объявление может
находится в любом месте программы (но до первого использоваиня данного идентификатора).
Временные переменные
Для хранения промежуточных значений вершинные программы могут использовать временные переменнные -
4-мерные вещественные вектора, которые перед использованием должны быть объявлены в команде TEMP.
Все временные переменные доступны врешинной программе как для чтения, так и для записи.
Пример.
TEMP flag;
TEMP a,b,c;
Максимально возможное число временных переменных можно узнать при помощи следующего фрагмента кода:
int maxTemporaries;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_TEMPORARIES_ARB,
&maxTemporaries );
Начальные значения временных переменных (как и выходных перемнных) не определены и зависят от
реализации райвера.
Параметры
В качестве параметров могут выступать как 4-мерных вектора, так и массивы таких векторов.
Параметры могут задаваться как явно (через команду PARAM), так и неявно (путем непосредственной
подстановки в текст).
При этом параметры представляеют из себя постоянные (на время выполнения вершинной прораммы) величины,
то есть они для вершинной программы доступны только для чтения.
Пример.
PARAM a = { 1, 2, 3, 4 }; # vector (1, 2, 3, 4)
PARAM b = { 3 }; # vector (3, 0, 0, 1)
PARAM c = { 3, 4 }; # vector (3, 4, 0, 1)
PARAM e = 3; # vector (3, 3, 3, 3)
# array of two vectors
PARAM arr [2] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 } };
ADD a, b, { 1, 2, 3, 4 };
ADD a, c, 3;
Матрица задается как массив из 4 векторов-столбцов.
Если исходная матрица задавалась следующим фрагментом кода.
// When loaded, the first row is "1, 2, 3, 4", because of column-major
// (OpenGL spec) vs. row-major (C) differences.
GLfloat matrix [16] =
{
1, 5, 9, 13,
2, 6, 10, 14,
3, 7, 11, 15,
4, 8, 12, 16
};
glMatrixMode ( GL_MATRIX0_ARB );
glLoadMatrixf ( matrix );
Также пусть в вершинной программе присутствуют следующие объявления.
PARAM mat1[4] = { state.matrix.program[0] };
PARAM mat2[4] = { state.matrix.program[0].transpose };
Тогда mat1[0] принимает значение (1,2,3,4), mat1[3] принимает значение (13,14,15,16),
mat2[0] принимат значение (1,5,9,13), и mat2[3] принимает значеие (4,8,12,16).
Вот еще несколько примеров параметров, основанных на матрицах.
PARAM m0 = state.matrix.modelview[1].row[0];
PARAM m1 = state.matrix.projection.transpose.row[3];
PARAM m2[] = { state.matrix.program[0].row[1..2] };
PARAM m3[] = { state.matrix.program[0].transpose };
Максимально возможное общее количество всех параметров (явных и неявных) можно получить из следующего
фрагмента кода.
int maxParams;
glGetProgramivARB ( GL_VERTEX_PROGRAM_ARB, GL_MAX_PROGRAM_PARAMETERS_ARB,
&maxParams );
Можно связать параметр с каким-либо локальным параметром или параметром окружения:
PARAM c = program.local [0];
PARAM mat [4] = program.ebv [0..3];
PARAM ambient = state.material.ambient;
Адресные переменные
Адресные переменные выступают фактически в качестве индексов при обращении к массивам векторов,
представляя собой однокомпонентный целочисленный вектор
Для объявления адресных перменных служит команда ADDRESS.
Ниже приводится пример использования адресных переменных.
ADDRESS addr;
PARAM list [] = {
{ 1, 0, 0, 1 },
{ 2, 1, 0, 1 },
{ 3, 4, 5, 1 }
};
. . .
ARL addr.x, index.x; # load into the address variable floor (index.x)
. . .
SUB v, a, list [addr.x];
. . .
При использовании адресных переменных можно задавать смещение, лежащее в диапазоне о -64 до +63.
Выходные значения
Выходные параметры доступны только для записи и задаются командой OUTPUT:
OUTPUT oCol = result.color.primary;
Фактически команда OUTPUT просто создает ссылку на соответствующий выходной регистр.
Также можно создать второе имя для уже существующего параметра (alias):
ALIAS b = a;
Система команд
Всего поддерживается 27 различных инструкций, работающих с 4-мерными вещественными векторами. Каждая
такая команда имеет следующий вид:
opCode dest, [-]src0 [,[-]src1 [,[-]src2]]
Здесь opCode - это символьный код инструкции, dest - это регистр, в который будет
помещен результат выполнения команды, а величины src0, src1 и src2 - это регистры
с исходными данными. Квадратными скобками обозначены необязатаельные величины.
Пример.
MOV R1, R2;
MAD R1, R2, R3, -R4;
Необязательный знак минус ("-") позволяет в качестве источника использовать как сам регистр, так и
вектор, получающийся из него умножением на -1.
Для входных регистров (src0, src1 и src2) существует возможность использовать
вместо самого регистра вектор, получающийся из исходного путем перестановки его компонент, например:
MOV R1, R2.yzwx;
MOV R2, -R3.yzwx;
Для выходного регистра можно задать маску, защищающую отдельные компоненты регистра от изменения
MOV R2.xw, -R3;
Рассмотрим систему команд.
Название | Синтаксис | Комментарий |
ABS Вычисление модуля | ABS dest, src; | Данная команда осуществляет покомпонентное вычисление модуля dest=fabs(src) |
ADD Сложение | ADD dest, src0, src1; | Данная команда складывает два параметра и записывает сумму в результат dest=src0+src1 |
ARL | ARL dest.C1, src.C2; | Загружает в адресный регистр значение целой части скалярного операнда |
DP3 Вычисление скалярного произведения по первым трем компонентам | DP3 dest, src0, src1; | Данная команда вычисляет скалярное произведение источников как трехмерных векторов dest=src0.x*src1.x + src0.y*src1.y + src0.z*src1.z |
DP4 Вычисление скалярного произведения | DP4 dest, src0, src1; | Данная команда вычисляет скалярное произведение четырехмерных векторов и записывает результат во все компоненты dest dest=src0.x*src1.x+src0.y*src1.y+src0.z*src1.z+src0.w*src1.w |
DPH Вычисление однородного скалярного произведения | DPH dest, src0, src1; | Данная команда служит для вычисления однородного скалярного произведение dest=src0.x*src1.x + src0.y*src1.y + src0.z*src1.z + src1.w |
DST Вычисление расстояния | DST dest, src0.C1, src1.C2; | Данная команда эффективно вычисляет вектор dest=(1,d,d^2,1/d) по двум скалярным значениям src0.C1=d^2 src1.C2=1/d |
EX2 Приближенное вычисление 2 в степени | EX2 dest, src.C; | Для заданного скалярного операнда src.C вычисляет приближеное значение 2^src.C и записывает во все компоненты регистра dest |
EXP Вычисление 2 в степени | EXP dest, src.C; | Служит для более точного вычисление 2^src.C dest.x=2^floor(src.C) dest.y=src.C - floor ( src.C) dest.z=roughAppr2ToX(floor(scr.C)) dest.w=1 Функция roughAppr2ToX вычисляет приближенное значение с точностью до 2^(-11) |
FLR Вычисление floor | FLR dest, src; | Осуществляет покомпонентное вычисление целой части (floor)(наибольшего целого, не превосходящего аргумента) от компонент источника |
FRC Вычисление дробной части | FRC dest, src; | Осуществляет покомпонентное вычисление дробной части. Дробная часть frac(x)=x-floor(x) |
LG2 Приближенное вычисление логарифма по основанию 2 | LG2 dest, src.C; | Вычисляет приближенное значение логарифма по основанию 2 от скалярного аргумента и записывает во все компоненты источника |
LIT Вычисление коэффициентов освещения | LIT dest, src; | Служит для ускорения вычисления диффузной и бликовой освещенности в вершинах src.x=(n,l) src.y=(n,h> src.w=p - степень, приводится к отрезку [-128,128] На выходе dest.x=1 dest.y=max(0,(n,l)) dest.z=(n.l)>0?roughAppr2ToX(max(0,(n,h))^p):0 dest.w=1 |
LOG Вычисление логарифма по основанию 2 | LOG dest, src.C; | Вычисляет приближенное значение логарифма по основанию 2 от скалярного операнда temp=fabs(src.C) dest.x=floor(log2(temp) dest.y=temp/2^floor(log2(temp)) dest.z=roughAppr2ToX(temp) dest.w=1 |
MAD Умножение и сложение | MAD dest, src0, src1, src2; | Вычисляет dest=src0*src1+src2 |
MAX Вычисление максимума | MAX dest, src0, src1; | Покомпонентное вычисление максимума dest=max(src0,src1) |
MIN Вычисление минимума | MIN dest, src0, src1; | Покомпонентное вычисление минимума dest=min(src0,src1) |
MOV Копирование | MOV dest, src; | dest=src |
MUL Покомпонентное умножение | MUL dest, src0, src1; | Вычисляет покомпонентное произведение dest=src0*src1 |
POW Возведение в степень | POW dest, src0.C1, src1.C2; | Вычисление приближенного значения первого скалярного операнда, возведенного в степень второго скалярного операнда и записывает результат во все компоненты регистра dest apprPow(a,b)=apprExp2(b*apprLog2*a)) |
RCP Вычисление обратного | RCP dest, src.C; | Вычисляет приближенное значение обратного к скалярному операнду и записывает во все компоненты dest dest=1/src.C |
RSQ | RSQ dest, src.C; | dest=1/sqrt(fabs(scr.C)) |
SGE Set on Greater or Equal | SGE dest, src0, src1; | dest.x=src0.x>=src1.x?1:0 dest.y=src0.y>=src1.y?1:0 dest.z=src0.z>=src1.z?1:0 dest.w=src0.w>=src1.w?1:0 |
SLTSet on Less Than | SLT dest, src0, src1; | dest.x=src0.xdest.y=src0.ydest.z=src0.zdest.w=src0.w |
SUB Вычисление разности | SUB dest, src0, src1; | Покомпонентное вычисление разности операндов dest=src0-src1 |
SWZ "перемешивание" компонент | SWZ dest, src, <extSwizzle> где <extSwizzle> ::= <comp>, <comp>, <comp>, <comp> <comp> ::= [-] (0 | 1 | x | y | z | w) | Данная команда позволяет произвольным образом перемешать компоненты (или их часть), в качстве источника для компоненты может выступать любая компонента исходного регистра (со знаком или без) или же значения 0 и 1 |
XPD Вычисление векторного произведения | XPD dest, src0, src1; | Данная команда осуществляет вычисление векторного произвдения первых трех компонент первого операнда на первые три компоненты второго операнда и записывает в первые три компоненты dest |
Примеры
Пример. Нормирование трехмерного вектора.
DP3 result.w, vector, vector; # result.w = nx^2+ny^2+nz^2
RSQ result.w, result.w; # result.w = 1/sqrt(nx^2+ny^2+nz^2)
MUL result.xyz, result.w, vector; # normalize by multiplying by 1/length
Пример. Вычисление определителя матрицы 3х3.
#
# Determinant of | vec0.x vec0.y vec0.z | into result.
# | vec1.x vec1.y vec1.z |
# | vec2.x vec2.y vec2.z |
#
MUL result, vec1.zxyw, vec2.yzxw;
MAD result, vec1.yzxw, vec2.zxyw, -result;
DP3 result, vec0, result;
Пример. Преобразование в пространство отсечения.
!!ARBvp1.0
ATTRIB pos = vertex.position;
PARAM mat [4] = { state.matrix.mvp };
# transform by concatenation of modelview and projection matrices
DP4 result.position.x, mat [0], pos;
DP4 result.position.y, mat [1], pos;
DP4 result.position.z, mat [2], pos;
DP4 result.position.w, mat [3], pos;
# copy primary color
MOV result.color, vertex.color;
END
Пример. Приведение угла к отрезку [0, 2*PI].
# result = 2*PI * fraction(in/(2*PI))
# piVec = (1/(2*PI), 2*PI, 0, 0)
PARAM piVec = { 0.159154943, 6.283185307, 0, 0 };
MUL result, in, piVec.x;
EXP result, result.x;
MUL result, result.y, piVec.y;
Пример. Смешение двух векторов (lerp)
# result = a * vec0 + (1-a) * vec1
# = vec1 + a * (vec0 - vec1)
SUB result, vec0, vec1;
MAD result, a, result, vec1;
Примеры использования вершинных программ
Вычисление необходимых параметров для попиксельного диффузного освещения
Когда мы расматривали попиксельное диффузное освещение и использованием register combiner',
то возникала необходимость вычисления вектора на источника света (l) для каждой вершины выводимого
объекта и перевод его в систем координат касательного пространства.
Это как раз простой пример той работы, которую может легко выполнять вершинная программа - получив
координаты источника света (в одном из локальных параметров) она может по координатам вершины и базису
касательного пространства (векторам t, b и n) вычислить вектор (l) и сразу
перевести его в касательное пространство.
При этом все эти вычисления будут производиться полностью на GPU, единственное, что должен делать центральный
процессор - это поставлять необходимые данные для каждой вершины.
Ниже приводится исходный текст соответствующей вершинной программы.
!!ARBvp1.0
#
# simple vertex shader to setup data for per-pixel diffuse lighting
#
# on entry:
# vertex.position
# vertex.normal - normal vector (n) of TBN basic
# vertex.texcoord [0] - normal texture coordinates
# vertex.texcoord [1] - tangent vector (t)
# vertex.texcoord [2] - binormal vector (b)
#
# program.local [0] - eye position
# program.local [1] - light position
#
# on exit:
# result.texcoord [0] - texture coordinates
# result.texcoord [1] - vector l
#
PARAM eye = program.local [0];
PARAM light = program.local [1];
PARAM mvp [4] = { state.matrix.mvp };
TEMP l, lt;
# compute l (vector to light)
ADD l, -vertex.position, light;
# transform it into tangent space
DP3 lt.x, l, vertex.texcoord [1];
DP3 lt.y, l, vertex.texcoord [2];
DP3 lt.z, l, vertex.normal;
MOV lt.w, l.w;
# store it into texcoord [1]
MOV result.texcoord [1], lt;
# store texcoord [0]
MOV result.texcoord [0], vertex.texcoord [0];
# copy primary and secondary colors
MOV result.color, vertex.color;
MOV result.color.secondary, vertex.color.secondary;
# transform position into clip space
DP4 result.position.x, vertex.position, mvp [0];
DP4 result.position.y, vertex.position, mvp [1];
DP4 result.position.z, vertex.position, mvp [2];
DP4 result.position.w, vertex.position, mvp [3];
# we're done
END
Полный исходный код к этому примеру содержится в ссылке, приведенной в конце статьи.
Вычисление необходимых параметров для попиксельного бликового освещения
Еще одним примером является вычисление необходимых векторов (l и h)для
попиксельного бликового (specular) освещения.
Здесь вычисления несколько сложения - для каждой вершины необходимо найти вектора на источник света и
на наблюдателя, нормировать их и найти их полусумму (вектор h).
Однако и с этой целью легко справляется вершинная программа, приводимая ниже.
!!ARBvp1.0
#
# simple vertex shader to setup data for per-pixel specular lighting
#
# on entry:
# vertex.position
# vertex.normal - normal vector (n) of TBN basic
# vertex.texcoord [0] - normal texture coordinates
# vertex.texcoord [1] - tangent vector (t)
# vertex.texcoord [2] - binormal vector (b)
#
# program.local [0] - eye position
# program.local [1] - light position
#
# on exit:
# result.texcoord [0] - texture coordinates
# result.texcoord [1] - l
# result.texcoord [2] - h
#
PARAM eye = program.local [0];
PARAM light = program.local [1];
PARAM mvp [4] = { state.matrix.mvp };
PARAM half = 0.5;
TEMP l, l2, v, v2, h, h2, temp;
TEMP ht;
# compute l (vector to light)
ADD l, -vertex.position, light;
# normalize it (we need to correctly compute h)
DP3 temp.x, l, l; # now temp.x = (l,l)
RSQ temp.y, temp.x; # compute inverse square root of (l,l)
MUL l, l, temp.y; # normalize
# compute v (vector to viewer)
ADD v, -vertex.position, eye;
# normalize it (we need to correctly compute h)
DP3 temp.x, v, v; # now temp.x = (v,v)
RSQ temp.y, temp.x; # compute inverse square root of (v,v)
MUL v, v, temp.y; # normalize
# compute h = (l+v)/2
ADD h, l, v;
MUL h, h, half;
# transform it into tangent space
DP3 ht.x, h, vertex.texcoord [1];
DP3 ht.y, h, vertex.texcoord [2];
DP3 ht.z, h, vertex.normal;
MOV ht.w, h.w;
# store it into texcoord [1]
MOV result.texcoord [1], ht;
# store texcoord [0]
MOV result.texcoord [0], vertex.texcoord [0];
# copy primary and secondary colors
MOV result.color, vertex.color;
MOV result.color.secondary, vertex.color.secondary;
# transform position into clip space
DP4 result.position.x, vertex.position, mvp [0];
DP4 result.position.y, vertex.position, mvp [1];
DP4 result.position.z, vertex.position, mvp [2];
DP4 result.position.w, vertex.position, mvp [3];
# we're done
END
Заворачиваем вершинную программу в класс
Для удобства использования вершинных программ и доступа к их параметрам можно завернуть вершинную
программу (вместе с методами загрузки, доступа к параметрам, запросам на ограничения) в класс,
предоставляющий весь необходимый доступ, но при этом скрывающей все внутренние детали.
В частности, довольно удобно было бы использовать для обращения к локальным параметрам и параметрам
окружения таким же образом, каким это обращение происходит из самой вершинной программы, т.е.
чтобы следующие примеры работали.
myProgram.local [1] = Vector3D ( x, y, z );
otherProgram.local [0] = v;
Vector4D v2 = VertexProgram :: env [2];
То есть фактически хочется реализовать то, что в Delphi, VB и других языках называется свойствами
(property) - величина, с точки зрения внешнего пользователя выглядящая как обычная
переменная. Но при этом доступ к ней (чтение и запись) происходит через специальные функции.
На самом деле на языке С++ это можно реализовать - в качесве соответствующей переменной должен
выступать экземляр класса, у которого переопределен оператор присваивания и опреатор преобразования
в нужный тип.
При этом сам класс может содержать внутри себя указатели на функции (или методы класса),
обеспечивающие чтение и запись этого значение.
Ниже приводится описание класса, обеспечивающего доступ к векторным свойствам через скрытые внутри
функции доступа.
class ParamProxy
{
public:
typedef void ( __stdcall *PutFunc)( GLenum target, GLuint index, const float * vector );
typedef void ( __stdcall *GetFunc)( GLenum target, GLuint index, float * params );
protected:
GetFunc getter;
PutFunc putter;
int index;
int target;
public:
ParamProxy ( PutFunc put, GetFunc get, GLenum theTarget, int theIndex );
// put-method
void operator = ( const Vector4D& v );
// get method
operator Vector4D () const;
};
При попытке присвоить экземпляру данного класса 4-х мерный вектор, происходит вызов переопределенного
оператора присваивания, который делегирует этот запрос функции.
void ParamProxy :: operator = ( const Vector4D& v )
{
putter ( target, index, v );
}
Аналогично чтение векторного значение приводит к вызову переопределенного оператора приведения типа,
который также делегирует эту операцию заданной функции.
ParamProxy :: operator Vector4D () const
{
Vector4D v;
getter ( target, index, v );
return v;
}
Использование приведенного класса ParamProxy позволяет реализовать доступ к любому параметру
вершинной программы, однако, поскольку мы имеем дело с массивами этих параметров, ввести еще один
класс, у которого перегружен оператор [] таким образом, что он возвращает соответствующий
экземпляр класса ParamProxy.
ParamProxy operator [] ( int index )
{
return ParamProxy ( putter, getter, target, index );
}
Ниже приводится описание класса, реализующего абстракцию вершинной программы и работы с ней.
class VertexProgram
{
protected:
int errorCode;
string errorString;
unsigned id; // program id
public:
ParamArray local; // local parameters
static ParamArray env; // environment params
static int activeProgram;
static int maxVertexAttribs ();
static int maxLocalParams ();
static int maxEnvParams ();
static int maxMatrices ();
static int maxTemporaries ();
static int maxParams ();
VertexProgram ();
VertexProgram ( const char * fileName );
~VertexProgram ();
unsigned getId () const
{
return id;
}
string getErrorString () const
{
return errorString;
}
void bind ();
void unbind ();
void enable ();
void disable ();
bool load ( Data * data );
bool load ( const char * fileName );
};
Весь исходный код к этой статье, включая реализацию класса VertexProgram и все приведенные примеры можно
скачать здесь.
Более полное описание вершинных и фрагментных шейдеров, включая использование шейдеров на OpenGL
Shading Language и многочисленные примеры, можно найти в недавно вышедшей книге
"Расширения OpenGL".
|