Попиксельное бликовое (specular) освещение с использованием register combiner'ов
Используя механизм register combiner'ов можно достаточно легко реализовать попиксельное
бликовое (specular) освещение.
Бликовое освещение мы будем рассматривать в следующем виде:
I = Iamb + max (0, (h,n))^p,
Здесь коэффициент Iamb задает фоновую освещенность, а h - бисектор единичного вектора
на источник света l и единичного вектора на наблюдателя v:
h = (l+v)/|l+v|,
Рис 1. Вычисление вектора h.
Степень p, в которую возводится скалярное произведение, чем она больше, тем более резким
получается блик.
Как и в случае диффузного освещения в нулевом текстурном блоке разместим карту нормалей, а в первом
блоке - нормирующую кубическую текстуру.
Только вместо задания для кубической текстуры в качестве координат значений вектора на источник
света l, будем в каждой вершине задавать вектор h.
Тогда после интерполяции текстурных координат и применения их к нормирующей кубическоц текстуре мы
получим для каждого пиксела выводимой грани нормированное значение вектора h.
К сожалению, используя механизм register combiner'ов достаточно сложно реализовать возведение
в произвольную степень p.
Достаточно легко реализовать возведение в восьмую степень. Для этого понядобятся два
general combiner'а. Первый из них конфигурируется аналогично случаю диффузного освещения
и на выходе он записывает в регистр spare0.rgb скалярное произведение (n,h).
Второй general combiner в переменные А и В записывает max(0,(n,h)) и на выходе
записывает покоординатное произведение этих переменных опять в spare0.rgb, т.е. там
мы получаем уже max(0,(n,h))^2.
// A = max ( (n,h), 0 )
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV,
GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B = max ( (n,h), 0 )
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV,
GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// setup output of (h,n)^2
glCombinerOutputNV ( GL_COMBINER1_NV, GL_RGB,
GL_SPARE0_NV, // AB output
GL_DISCARD_NV, // CD output
GL_DISCARD_NV, // sum output
GL_NONE, // no scale
GL_NONE, // no bias
GL_FALSE, // we do not need here dot product
GL_FALSE, GL_FALSE );
Чуть более сложной является настройка final combiner'а - в нем в качестве переменных А и В
выступает псевдорегистр EF, C = 0, D = constant-color0, а в переменные E и F записывается
значения из spare0.rgb.
// A.rgb = EF
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_E_TIMES_F_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B.rgb = EF
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_E_TIMES_F_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = constant_color1.rgb
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_CONSTANT_COLOR1_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// E = spare0.rgb
glFinalCombinerInputNV ( GL_VARIABLE_E_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// F = spare0.rgb
glFinalCombinerInputNV ( GL_VARIABLE_F_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// G.alpa = 1
glFinalCombinerInputNV ( GL_VARIABLE_G_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_ALPHA );
В результате на выходе получается:
I = Iamb + max (0, (h,n))^8,
На приведенном ниже рисунке вы видите результат работы данного подхода.
Рис 2. Результат работы попиксельного бликового освещения с показателем степени 8
К сожалению значение степени, равное восьми, не всегда подходит, поэтому в ряде случаев можно
использовать различные приближения.
Достаточно хорошие результаты дает следующая приближенная формула:
max (0, 4*(max(0,(h,n))-0.75))^2),
При использовании данной схемы в нулевом general combiner'е как и ранее вычисляется значение
(n,h) и записывает его в регистр spare0.rgb.
Следующий general combiner конфигурируется следующим образом:
A = max ( 0, (n,h) )
B = 1
C = -constant-color0
D = 1
Использование в качестве выхода AB+CD с масштабирующим коэффициентом GL_SCALE_BY_FOUR_NV дает на
выходе 4*(max(0,(n,h))-0.75).
// A = max ( (n,h), 0 )
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV,
GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B = 1
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV,
GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB );
// C = -constant color0.rgb
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_C_NV, GL_CONSTANT_COLOR0_NV,
GL_SIGNED_NEGATE_NV, GL_RGB );
// D = 1
glCombinerInputNV ( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_RGB );
// setup output of (h,n) - 0.75
glCombinerOutputNV ( GL_COMBINER1_NV, GL_RGB,
GL_DISCARD_NV, // AB output
GL_DISCARD_NV, // CD output
GL_SPARE0_NV, // sum output
GL_SCALE_BY_FOUR_NV,
GL_NONE, // no bias
GL_FALSE, // we do not need here dot product
GL_FALSE,
GL_FALSE ); // neither we need mux here
Все, что остается сделать final combiner'у - это отсечь отрицательные значения и возвести
результат в квадрат.
// A.rgb = max ( 0, spare0.rgb )
glFinalCombinerInputNV ( GL_VARIABLE_A_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// B.rgb = max ( 0, spare0.rgb )
glFinalCombinerInputNV ( GL_VARIABLE_B_NV, GL_SPARE0_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// C = 0
glFinalCombinerInputNV ( GL_VARIABLE_C_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// D = constant_color1.rgb
glFinalCombinerInputNV ( GL_VARIABLE_D_NV, GL_CONSTANT_COLOR1_NV,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// E = 0
glFinalCombinerInputNV ( GL_VARIABLE_E_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// F = 0
glFinalCombinerInputNV ( GL_VARIABLE_F_NV, GL_ZERO,
GL_UNSIGNED_IDENTITY_NV, GL_RGB );
// G.alpa = 1
glFinalCombinerInputNV ( GL_VARIABLE_G_NV, GL_ZERO,
GL_UNSIGNED_INVERT_NV, GL_ALPHA );
Ниже приводится изображение, полученное с использованием данного подхода.
Рис 3. Результат работы попиксельного бликового освещения с приближенным вычислением возведения
в степень.
Аналогично случаю диффузного освещения строится изображение сложных тел, например тора.
Ниже приводится описания соответствующих классов и структур.
struct Vertex
{
Vector3D pos; // position of vertex
Vector2D tex; // texture coordinates
Vector3D n; // unit normal
Vector3D t, b; // tangent and binormal
Vector3D l; // light vector in the tangent space
Vector3D h;
// map vector to tangent (TBN) space
Vector3D mapToTangentSpace ( const Vector3D& v ) const
{
return Vector3D ( v & t, v & b, v & n );
}
};
struct Face // triangular face
{
int index [3]; // indices to Vertex array
};
class Torus
{
int numRings;
int numSides;
int numVertices;
int numFaces;
Vertex * vertices;
Face * faces;
public:
Torus ( float r1, float r2, int rings, int sides );
~Torus ()
{
delete [] vertices;
delete [] faces;
}
void calcLightVectors ( const Vector3D& light, const Vector3D& eye );
void draw ();
};
Обратите внимание, что в структуре Vertex теперь хранится не только вектор l,
но и вектор h.
Также метод calcLightVectors берет на вход два параметра - положение источника света
light и положение наблюдателя eye.
void Torus :: calcLightVectors ( const Vector3D& light, const Vector3D& eye )
{
// compute texture coordinates for
// normalization cube map
for ( int i = 0; i < numVertices; i++ )
{
Vector3D l = (light - vertices [i].pos).normalize ();
Vector3D v = (eye - vertices [i].pos).normalize ();
vertices [i].l = vertices [i].mapToTangentSpace ( l );
vertices [i].h = vertices [i].mapToTangentSpace ( l + v );
}
}
Ниже приводится изображение тора, полученное с использованием бликового освещения.
Рис 4. Тор с бликовым освещением.
Весь исходный код примеров к этой статье можно скачать
здесь. Для сборки всех примеров следует использовать
либо файл Makefile.nmake в Windows, либо Makefile в Linux'е.
При подготовке статьи использовалась статья Марка Килгарда "A Practical and Robust Bump-mapping
Technique for Today's GPUs" доступаная на сайте компании
NVIDIA.
|