OpenGL: Использование Register Combiners.
Автор: CyberDemon
С появлением чипа от NVIDIA с «притягивающим» названием GeForce, стал возможным не только более быстрый рендеринг наших любимых полигонов, но и использование новых спецэффектов и технологий, реализация которых на более древних видеокартах была либо попросту невозможна, либо очень затруднительна.
Один из таких "наворотов" от nVidia - это Register Combiners (не путать с бравыми ребятами, которые в поте лица работают в поле, чтобы мы с вами могли покушать немного хлебушка).
Таким образом, появилось новое расширение для OpenGL, именуемое как GL_NV_register_combiners. Поддержка данного расширения появилась с чипа GeForce (NV10).
Собственно говоря, Register Combiners представляют собой набор из 2-ух или более (до 8-ми) General Combiners, одного Final Combiner и набора всевозможных операций над данными.
Вообще, можно использовать произвольное количество и комбинаций General Combiners, но из соображения понятности кода делают так, что Combiners образуют цепочку так, что выходные данные с самого первого Combiner-а является входными для второго и так далее. Так же желательно ограничивать количество используемых Combiners.
В заключительной части обработки стоит Final Combiner.
Теория.
Разрешение и запрещение использования Register Combiners производится с помощью вызова функций glEnable и glDisable соответственно. При этом используется константа GL_REGISTER_COMBINERS_NV.
Чтобы указать количество используемых General Combiners, производим вызов функции
glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, num);
где num - есть кол-во General Combiners.
General Combiner состоит из нескольких блоков, через которые последовательно проходят данные:
1. Блок входных RGB и Alpha регистров.
2. Блок предварительной обработки входных данных
3. Блок комбинации входных данных
4. Блок обработки скомбинированных данных
Блоки 1 и 2 имеют 4 потока или переменных (A, B, C, D), которые могут принимать различные значения, в зависимости от того, как вы его (Combiner) запрограммируете.
Программирование General Registers осуществляется с помощью 2-ух функций:
glCombinerInputNV и glCombinerOutputNV.
glCombinerInputNV( GLenum stage, GLenum portion,
GLenum var, GLenum input,
GLenum mapping, GLenum usage)
stage - указывает номер General Combiner, который будет программироваться с помощью этого вызова. Возможные значения - от GL_COMBINER0_NV до GL_COMBINER7_NV;
portion - определяет, какая часть входных данных будет использоваться - цветовые данные или альфа канал (GL_RGB или GL_ALPHA);
var - определяет используемую переменную. Возможные значения - от GL_VARIABLE_A_NV до GL_VARIABLE_D_NV;
input - параметр, определяющий, откуда берутся данные для переменной.
Возможны 10 значений:
GL_ZERO - понятное дело, 0, то есть ничего;
GL_CONSTANT_COLOR0_NV, GL_CONSTANT_COLOR1_NV - данные берутся из регистров постоянного цвета, которые программируются отдельно (см. ниже);
GL_FOG - данные о тумане;
GL_PRIMARY_COLOR_NV - данные берутся из регистра текущего цвета (цвет может быть установлен с помощью glColor, например, или же в процессе программирования General Registers);
GL_SECONDARY_COLOR_NV - еще один регистр цвета (вторичный);
GL_SPARE0, GL_SPARE1 - временные регистры. Устанавливаются в процессе работы General Combiners.
GL_TEXTURE0_ARB, GL_TEXTURE1_ARB - используется текстура с определенного текстурного блока.
mapping - задает работу блока предварительной обработки данных (блок 2).
Возможные значения:
GL_SIGNED_IDENTITY_NV - данные передаются без изменений : f(x)=x;
GL_SIGNED_NEGATE_NV - инвертирование знаковых данных: f(x)=-x;
GL_UNSIGNED_IDENTITY_NV - беззнаковые данные без изменений: f(x)=max(0,x);
GL_UNSIGNED_INVERT_NV - инвертирование беззнаковых данных: f(x)=1-min(max(0,x),1);
GL_EXPAND_NORMAL_NV - "расширение" области значений с [0,1] до [-1,1]: f(x)=2*max(0,x)-1;
GL_EXPAND_NEGATE_NV - то же, что и предыдущее значение, но с отрицанием: f(x)=-2*max(0,x)+1;
GL_HALF_BIAS_NORMAL_NV - то же, что и EXPAND_NORMAL, но "ополовиненное": f(x)=max(0,x)-0.5;
GL_HALG_BIAS_NEGATE_NV - то же, что и EXPAND_NEGATE, но опять же, только половинка: f(x)=-max(0,x)+0.5;
usage - то, что подается на вход блока 3, варианты те же, что и для portion;
Пример:
glCombinerInputNV( GL_COMBINER0_NV, GL_RGB,
GL_VARIABLE_A_NV, GL_TEXTURE0_ARB,
GL_UNSIGNED_IDENTITY, GL_RGB);
На вход Combiner-у 0 подается текстура, из которой берется RGB составляющая и без изменений попадает в переменную A.
Итак, входящие данные мы запрограммировали, теперь надо запрограммировать операции над этими данными и регистры, в которые будет попадать результат этих операций. В блоке комбинации входных данных происходят 3 простейшие операции: переменные A и B перемножаются между собой (аналогично C и D), а так же происходит суммирование этих двух произведений, таким образом, на выходе имеем 3 значения.
glCombinerOutputNV(GLenum stage, GLenum portion,
GLenum abOutput, GLenum cdOutput, GLenum sumOutput,
GLenum scale,GLenum bias,
GLboolean abDotProduct, GLboolean cdDotProduct, GLboolean muxSum);
stage, portion - то же, что и у glCombineInputNV;
abOutput, cdOuput, sumOutput - выходные значения с блока комбинации данных. Здесь определяется, в какой регистр они попадут дальше.
Возможные варианты:
GL_DISCARD_NV - игнорируем значение;
GL_PRIMARY_COLOR_NV, GL_SECONDARY_COLOR_NV - регистры цветов;
GL_SPARE0_NV, GL_SPARE1_NV - временные регистры;
GL_TEXTURE0_ARB, GL_TEXTURE1_ARB - текстурные регистры;
scale - масштаб результата, может принимать значения GL_NONE, GL_SCALE_BY_TWO_NV, GL_SCALE_BY_FOUR_NV, GL_SCALE_BY_ONE_HALF_NV. Умножение производится на 0, 2, 4, 0.5 соответственно.
bias - сдвиг результата: GL_NONE или GL_BY_NEGATIVE_ONE_HALF_NV. Во втором случае, из результата вычитается 0.5
abDotProduct, cdDotProduct - параметры, определяющие, каким образом будет происходить перемножение переменных. Если какой-то из них = GL_TRUE, то в соответствующем регистре появится результат операции Dot3:
res(x,y,z) = (a.x*b.x+a.y*b.y+a.z*b.z, a.x*b.x+a.y*b.y+a.z*b.z, a.x*b.x+a.y*b.y+a.z*b.z) - аналогично для C*D
иначе, в случае GL_FALSE будет умножение:
res(x,y,z) = (a.x*b.x, a.y*b.y, a.z*.b.z).
muxSum - в случае GL_FALSE получаем A*B + C*D, а в случае GL_TRUE - A*B or C*D.
Пример:
glCombinerOutputNV( GL_COMBINER0_NV, GL_RGB,
GL_SPARE0, GL_SPARE1, GL_DISCARD, GL_RGB);
На вход Combiner-у 0 подается текстура, из которой берется RGB составляющая и без изменений попадает в переменную A.
Вот почти и все - осталось только настроить Final Combiner и дело в шляпе :)
glFinalCombinerInputNV(GLenum var, GLenum input,
GLenum mapping, GLenum usage);
input - значение, подаваемое на вход переменной var. Принимает те же самые значения, что и для General Combiner, но с добавлением следующего: GL_E_TIMES_F_NV (умножение переменной E на F - см. ниже) и GL_SPARE0_PLUS_SECONDARY_COLOR_NV - думаю, ясно из названия
var - то же самое, что и для General Combiner, но переменных немного больше - от A до G. Причем, после поступления на вход Final Combiner этих данных, результат рассчитывается по следующей формуле:
Res = A*B + (1-A) * C + D
mapping поддерживает только 2 параметра (см. вверху) - GL_UNSIGNED_IDENTITY_NV и GL_UNSIGNED_INVERT_NV;
Пример:
glFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_GL_RGB);
На вход A Final Combiner-а подается значение временного регистра SPARE0, из которого берется RGB составляющая.
Пример реализации Bump-mapping, используя Register Combiners.
Приведенный здесь пример реализует простейший вариант Bump-mapping. Он состоит в следующем: имеется основная текстура (Base) и bump-карта (ее можно сформировать с помощью plug-in для Photoshop, который можно достать опять же на nVidia.
Определяется вектор от источника света и производится его Dot3 умножение на bump-карту, в результате чего мы получаем карту освещенности поверхности, а уже после этого накладываем основную карту умножением.
Разрешаем использование Register Combiners:
glEnable(GL_REGISTER_COMBINERS_NV);
Устанавливаем количество используемых General Combiners - 1:
glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);
Устанавливаем цвет вершин (это будет PRIMARY_COLOR), который будет являться вектором (нормализованным) от источника света:
glColor3f(light.x, light.y, light.z);
Программируем первый General Combiner - нам надо умножить вектор света на первую текстуру путем Dot3, значит, в переменную A пойдет Bump-текстура, причем, мы расширяем диапазон ее значений до [-1,1]:
glCombinerInputNV(
GL_COMBINER0_NV, // num combiner
GL_RGB, // portion
GL_VARIABLE_A_NV, // variable
GL_TEXTURE0_ARB, // input
GL_EXPAND_NORMAL_NV,// mapping
GL_RGB // component usage
);
Переменную B настроим на вектор света:
glCombinerInputNV(
GL_COMBINER0_NV,
GL_RGB,
GL_VARIABLE_B_NV,
GL_PRIMARY_COLOR_NV,
GL_EXPAND_NORMAL_NV,
GL_RGB
);
Производим настройку операции Dot3 - результат попадает в SPARE0
glCombinerOutputNV(
GL_COMBINER0_NV,
GL_RGB,
GL_SPARE0_NV, // AB output
GL_DISCARD_NV, // CD output
GL_DISCARD_NV, // sum output
GL_NONE, // scale
GL_NONE, // bias
GL_TRUE, // ab dot product
GL_FALSE, // cd dot product
GL_FALSE // muxsum
);
Настраиваем Final Combiner - надо умножить A на B, а потом умножить на Base текстуру. A*B у нас лежит в SPARE0 значит, для Final Combiner настраиваем переменную A на SPARE0, а переменную B на Base текстуру, в результате получаем (см. формулу выше) res = A*B + (1-A)*C + D = A*B;
glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB); glFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB); glFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); glFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); glFinalCombinerInputNV( GL_VARIABLE_E_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB); glFinalCombinerInputNV( GL_VARIABLE_F_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
Потом идет рендер полигона и все !!! :) Только не забудьте выключить Register Combiners, если они Вам больше не нужны:
glDisable(GL_REGISTER_COMBINERS_NV);
Базовая текстура | Bump текстура | Результат |
15 февраля 2002 (Обновление: 17 июня 2009)
Комментарии [2]