Главная -
Статьи -
Проекты -
Ссылки -
Скачать -
Из гельминтов -
Юмор, приколы -
Почитать -
Обо мне -
Мысли -
Гостевая -

Попиксельное бликовое (specular) освещение с использованием register combiner'ов

Используя механизм register combiner'ов можно достаточно легко реализовать попиксельное бликовое (specular) освещение.

Бликовое освещение мы будем рассматривать в следующем виде:

           I = Iamb + max (0, (h,n))^p,

Здесь коэффициент Iamb задает фоновую освещенность, а h - бисектор единичного вектора на источник света l и единичного вектора на наблюдателя v:

           h = (l+v)/|l+v|,

Вычисление вектора h в моделе Фонга

Рис 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,

На приведенном ниже рисунке вы видите результат работы данного подхода.

Попиксельное бликовое (specular) освещение грани с наложенной картой нормалей (bumpmap)

Рис 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 );

Ниже приводится изображение, полученное с использованием данного подхода.

Попиксельное бликовое (specular) освещение грани с наложенной картой нормалей (bumpmap) с использованием приближенного возведения в степень

Рис 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 );
    }
}

Ниже приводится изображение тора, полученное с использованием бликового освещения.

Попиксельное бликовое (specular) освещение тора с наложенной картой высот (heightmap)

Рис 4. Тор с бликовым освещением.

Весь исходный код примеров к этой статье можно скачать здесь. Для сборки всех примеров следует использовать либо файл Makefile.nmake в Windows, либо Makefile в Linux'е.

При подготовке статьи использовалась статья Марка Килгарда "A Practical and Robust Bump-mapping Technique for Today's GPUs" доступаная на сайте компании NVIDIA.


Copyright © 2003-2004 Алексей В. Боресков

Используются технологии uCoz