Наше комьюнити:   OpenGL Shader Lab  •  Half-Life FX  •  Форум  
OpenGL Shader Lab

Просмотр статьи

Нормализация вектора в шейдере 03-06-2009 17:17

    В этой статье я хотел бы собрать все известные мне способы нормализации векторов в шейдерах различных версий. Материал не претендует на оригинальность, но, возможно, окажется полезным.
    Для начала – несколько слов о том, что такое нормализация и для чего она может понадобиться в шейдере.
    Нормализация вектора – это приведение вектора к единичной длине. При этом сохраняются значения направляющих косинусов (т.е. направление вектора), а длина становится равной единице. Во многих попиксельных расчетах фигурируют скалярные произведения, выполняющиеся с векторами единичной длины. Однако значения самих векторов часто указываются повершинно. Для получения значения в каждом пикселе используется встроенный механизм линейной интерполяции OpenGL. При этом вектор может стать не единичным (даже если исходные вектора были единичными), и, следовательно, шейдер, оперирующий векторами единичной длины, выдаст неверный результат. Для борьбы с этим вектор требуется нормализовать попиксельно.
    Исходя из определения, для получения вектора единичной длины из данного вектора требуется покомпонентно разделить его на значение длины, которая получается извлечением квадратного корня из скалярного произведения вектора самого на себя.


Code:
vec3 normalizedVec = vec / sqrt(dot(vec, vec));
//или
vec3 normalizedVec = vec / sqrt(vec.x*vec.x + vec.y*vec.y + vec.z*vec.z);


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

GLSL

В языках высокого уровня предусмотрены встроенные функции для часто выполняющихся операций. Не является исключением и операция нормализации вектора. Используйте функцию normalize:


Code:
vec3 viewVec = normalize(tsViewVec);


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

ARB_fragment_program

Версия шейдеров 2.0, представляемая стандартным расширением ARB_fragment_program, обладает достаточными возможностями для точной нормализации вектора. Это можно выполнить в три ассемблерные инструкции.


Code:
DP3 result.w, vector, vector;
RSQ result.w, result.w;
MUL result.xyz, result.w, vector;


Если вы используете язык Cg для написания шейдеров с последующей компиляцией в профайл arbfp1, то можете использовать функцию normalize по аналогии с GLSL. Результирующий ассемблерный код будет соответствовать приведенному выше.


Cg Pixel Shader:
float3 viewVec = normalize(tsViewVec);


Однако если вы захотите использовать версию шейдеров 3.0 от NVidia, путем компиляции Cg-кода в профайл fp40, то получите всего одну инструкцию, NRM. Ассемблерная инструкция специально для нормализации вектора появилась в шейдерной модели 3.0. и отсутствует в обычном наборе команд ARB_fragment_program. Для ее использования требуется указывать опцию OPTION NV_fragment_program2.

ATI_fragment_shader

Перейдем к версии шейдеров 1.4, представленной на видеокартах ATI Radeon расширением ATI_fragment_shader. Возможности этой шейдерной модели весьма ограничены. В частности, нельзя осуществлять деление и извлечение квадратного корня. Поэтому точную нормализацию вектора мы провести не можем. На помощь приходят приближенные методы.
Наиболее известным из них является использование специальной текстуры, нормирующей кубической карты (normalization cubemap). Мы используем исходный вектор в качестве текстурной координаты для обращения к ней. Все, что нужно сделать – построить эту текстуру таким образом, чтобы на пересечении с вектором находится пиксель, содержащий значение нормализованного вектора. Так как диапазон значений текстуры лежит в интервале от 0 до 1, а значения вектора от -1 до 1, нормальный вектор надо предварительно упаковать, а после чтения кубической карты – распаковать. И шейдерная модель 1.4 поддерживает операцию распаковки.
Вот так выглядит нормирующая кубическая карта в «развернутом» виде.
 


Изображение взято из презентации «Implementing Bump-Mapping using Register Combiners», автор Chris Wynn, NVIDIA Corporation.


У описанного метода приближенной нормализации есть серьезный недостаток. Если вы играли в Doom3 на видеокарте с поддержкой только шейдеров 1.х, то, возможно, замечали, что блик освещения на гладких поверхностях не гладкий, а состоит из концентрических «разводов». Это и есть ошибка приближенного метода – 8-битной точности не хватает для упаковки большого диапазона значений. Из-за этого, кстати, и не имеет смысла делать текстуру большой, обычно вполне хватает текстур 32х32.
Второй способ приближенной нормализации, о котором я хочу рассказать, основан на формуле:


Code:
Vn = V * ((3 - len(V)^2)/2)


Или


Code:
Vn = V*((3-dot(V,V))/2)


Как видите, здесь нет деления (деление на 2 равнозначно умножению на 0.5) и извлечения квадратного корня, поэтому мы можем попробовать переложить это вычисление на шейдер. Для начала немного преобразуем формулу:


Code:
Vn = V*((3-dot(V,V))/2) = V - V*(dot(V,V)*0.5 - 0.5)))


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


Code:
DP3_HALF_SAT reg1, 2*reg0-0.5, 2*reg0-0.5;
NOP
MAD  reg0, -2*reg0-0.5, reg1-0.5, 2*reg0-0.5;
NOP


В результате мы получим более точное значение нормализованного вектора, и соответствующее пятно блика будет без «разводов».

NV_register_combiners

Шейдерная модель 1.0 от NVidia обладает меньшими возможностями, чем AFS, однако нам хватит их вполне для реализации вышеописанных приближенных способов. Нормализация с помощью нормирующей кубической карты делается точно так же, как для ATI_fragment_shader, поэтому повторно говорить об этом не будем. А вот второй способ, с нормализацией по приближенной формуле, рассмотрим еще раз, в рамках возможностей Register Combiners.
Опять же, мы можем воспользоваться возможностями Input Mapping для регистров. Код нормализации потребует точно так же две RGB-порции General Combiners. Вы можете заметить, что в плане совершаемых операций он иденичен вышеприведенному AFS-коду. Так выглядит код нормализации для nvparse (предполагается, что вектор V был упакован в диапазон [0,1] и передан в регистр tex0):


Register Combiners:
//General combiner 0:
//RGB: V.V * 0.5
{
    rgb {
        spare0 = expand(tex0) . expand(tex0);
        scale_by_one_half();
    }
}
//General combiner 1:
//RGB: V - V*(V.V*0.5 - 0.5) (normalized V)
{
    rgb {
        discard = expand(tex0) * unsigned_invert(zero);
        discard = -expand(tex0) * half_bias(spare0);
        spare0 = sum();
    }
}


Результат помещается во временный регистр spare0.

Заключение

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


Автор: XaeroX

Новости форума
Новые статьи
Вход в систему
Имя:
Пароль:
Помнить меня

[ вход ]
[ регистрация ]
[ забыли пароль? ]

Реклама
Текущие активные пользователи: 44
В данный момент на сайте 0 участников и 44 гостей.


Наш форум: В данный момент на форуме 118 посетителей.
Временная зона GMT. Текущее время 20:50.

На основе AcademCMS v1.3
Авторское право © 2001 - 2009, Chain Studios
Техническая площадка: HLFX.Ru