18c0693f

Наложение микрорельефа


Bump-Mapping, Multi-Texturing & Extensions

Этот урок, написанный Дженсом Шнайдером (Jens Schneider), основан на материале Урока 6, но содержит существенные изменения и дополнения. Здесь вы узнаете:

  • как управлять функциями мультитекстурирования видеокарты;
  • как выполнять "поддельное" наложение микрорельефа методом тиснения;
  • как, используя смешивание, отображать эффектно смотрящиеся логотипы, "летающие" по просчитанной сцене;
  • как просто и быстро выполнять преобразование матриц;
  • познакомитесь с основами техники многопроходной визуализации.
  • По меньшей мере, три из перечисленных пунктов могут быть отнесены к "продвинутым техникам текстурирования", поэтому для работы с ними нужно хорошо понимать основы функционирования конвейера визуализации в OpenGL. Требуется знать большинство команд, изученных в предыдущих уроках и быть достаточно близко знакомым с векторной математикой. Иногда будут попадаться блоки, озаглавленные "начало теории(…)" и оканчивающиеся фразой "конец теории(…)". В таких местах рассматриваются теоретические основы вопросов, указанных в скобках, и если вы их знаете, то можете пропустить. Если возникают проблемы с пониманием кода, лучше вернуться к теоретической части и попробовать разобраться. И еще: в уроке более 1200 строк кода, значительные фрагменты которого очевидны и скучны для тех, кто читал предыдущие главы. Поэтому я не стал комментировать каждую строку, пояснил только главное. Если встретите что-то вроде >…<, это значит, что строки кода были пропущены.

    Итак:

    #include <windows.h>         // Файл заголовков функций Windows

    #include <stdio.h>           // Файл заголовков для библиотеки ввода-вывода

    #include <gl\gl.h>           // Файл заголовков для библиотеки OpenGL32

    #include <gl\glu.h>          // Файл заголовков для библиотеки GLu32

    #include <gl\glaux.h>        // Файл заголовков для библиотеки GLaux

    #include "glext.h"           // Файл заголовков для мультитекстурирования


    #include <string.h>          // Файл заголовков для работы со строками
    #include <math.h>            // Файл заголовков для математической библиотеки
    Параметр GLfloat MAX_EMBOSS задает "интенсивность" эффекта рельефности. Увеличение этого числа значительно усиливает эффект, но приводит к снижению качества и появлению так называемых "артефактов" изображения по краям поверхностей.
    // Коэффициент рельефности. Увеличьте, чтобы усилить эффект
    #define MAX_EMBOSS (GLfloat)0.008f
    Давайте подготовимся к использованию расширения GL_ARB_multitexture. Это просто.
    В настоящее время подавляющая часть акселераторов имеет более одного блока текстурирования на чипе. Чтобы определить, верно ли это для используемой карточки, надо проверить ее на поддержку опции GL_ARB_multitexture, которая позволяет накладывать две или более текстур на примитив за один проход. Звучит не слишком впечатляюще, но на самом деле это мощный инструмент! Практически любая сцена выглядит гораздо красивее, если ее на объекты наложено несколько текстур. Обычно для этого требуется сделать несколько "проходов", состоящих из выбора текстуры и отрисовки геометрии; при увеличении числа таких операций работа серьезно тормозится. Однако не беспокойтесь, позже все прояснится.
    Вернемся к коду: __ARB_ENABLE используется, чтобы при необходимости отключить мультитекстурирование. Если хотите видеть OpenGL-расширения, раскомментируйте строку #define EXT_INFO. Доступность расширений будет проверяться во время выполнения, чтобы сохранить переносимость кода, поэтому нужны будут несколько переменных строкового типа — они заданы двумя следующими строками. Кроме того, желательно различать доступность мультитекстурирования и его использование, то есть нужны еще два флага. Наконец, нужно знать, сколько блоков текстурирования доступно (хотя мы будем использовать всего два). По меньшей мере один такой блок обязательно присутствует на любой OpenGL-совместимой карте, так что переменную maxTexelUnits надо инициализировать единичкой.


    #define __ARB_ENABLE true          // Используется, чтобы полностью отключить расширения
    // #define EXT_INFO        // Раскомментируйте, чтобы увидеть при запуске доступные расширения
    #define MAX_EXTENSION_SPACE 10240  // Символы строк-описателей расширений
    #define MAX_EXTENSION_LENGTH 256   // Максимальное число символов в одной строке-описателе
    bool multitextureSupported=false;  // Флаг, определяющий, поддерживается ли мультитекстурирование
    bool useMultitexture=true;         // Использовать его, если оно доступно?
    GLint maxTexelUnits=1;             // Число текстурных блоков. Как минимум 1 есть всегда
    Следующие строки нужны, чтобы сопоставить расширениям соответствующие вызовы функций C++. Просто считайте, что PFN-и-как-там-дальше — предварительно определенный тип данных, нужный для описания вызовов функций. Мы не уверены, что к этим прототипам будут доступны функции, а потому установим их в NULL. Команды glMultiTexCoordifARB задают привязку к хорошо известным glTexCoordif, описывающим i-мерные текстурные координаты. Заметьте, что они могут полностью заменить команды, связанные с glTexCoordif. Мы пользуемся версиями с GLfloat, и нам нужны прототипы тех команд, которые оканчиваются на "f"; другие команды при этом также остаются доступны ("fv", "i" и т.д.). Два последних прототипа задают функцию выбора активного блока текстурирования (texture-unit), занятого привязкой текстур ( glActiveTextureARB() ), и функцию, определяющую, какой из текстурных блоков связан с командой выбора указателя на массив (glClientActiveTextureARB). К слову: ARB — это сокращение от "Architectural Review Board", "комитет по архитектуре". Расширения, содержащие в имени строку ARB, не требуются для реализации системы, соответствующей спецификации OpenGL, но ожидается, что такие расширения найдут широкую поддержку у производителей. Пока ARB-статус имеют только расширения, связанные с мультитекстурированием. Такая ситуация, скорее всего, указывает на то, что мультитекстурирование наносит страшный удар по производительности, когда дело касается некоторых продвинутых техник визуализации.


    Пропущенные строки относятся к указателям на контекст GDI и прочему.
    PFNGLMULTITEXCOORD1FARBPROC  glMultiTexCoord1fARB  = NULL;
    PFNGLMULTITEXCOORD2FARBPROC  glMultiTexCoord2fARB  = NULL;
    PFNGLMULTITEXCOORD3FARBPROC  glMultiTexCoord3fARB  = NULL;
    PFNGLMULTITEXCOORD4FARBPROC  glMultiTexCoord4fARB  = NULL;
    PFNGLACTIVETEXTUREARBPROC  glActiveTextureARB  = NULL;
    PFNGLCLIENTACTIVETEXTUREARBPROC  glClientActiveTextureARB= NULL;
    Создаем глобальные переменные:
  • filter задает используемый фильтр (см. Урок 06). Обычно берем GL_LINEAR, поэтому инициализируем переменную единичкой.

  • texture хранит текстуру, три копии, по одной на фильтр.

  • bump хранит карты микрорельефа.

  • invbump хранит инвертированную карту микрорельефа. Причина объясняется позже, в теоретическом разделе.

  • Переменные, относящиеся к логотипам, в имени которых есть слово "Logo" - хранят текстуры, добавляемые к сцене на последнем проходе.

  • Переменные, относящиеся к свету, в имени которых есть слово “Light” - хранят параметры источника света.

  • GLuint  filter=1;           // Какой фильтр использовать
    GLuint  texture[3];         // Хранит 3 текстуры
    GLuint  bump[3];            // Рельефы
    GLuint  invbump[3];         // Инвертированные рельефы
    GLuint  glLogo;             // Указатель на OpenGL-логотип
    GLuint  multiLogo;          // Указатель на мультитекстурированный логотип
    GLfloat LightAmbient[]  = { 0.2f, 0.2f, 0.2f};  // Фоновое освещение — 20% белого
    GLfloat LightDiffuse[]  = { 1.0f, 1.0f, 1.0f};  // Рассеянный свет —  чисто белый
    GLfloat LightPosition[]  = { 0.0f, 0.0f, 2.0f}; // Положение источника — перед экраном
    GLfloat Gray[]    = { 0.5f, 0.5f, 0.5f, 1.0f};
    Очередной фрагмент кода содержит числовое описание текстурированного куба, сделанного из GL_QUADS-ов. Каждые пять чисел представляют собой пару из двумерных текстурных и трехмерных вершинных координат. Это удобно для построения куба в цикле for…, учитывая, что нам потребуется сделать это несколько раз. Блок данных заканчивается прототипом функции WndProc(), хорошо известной из предыдущих уроков.


    // Данные содержат грани куба в формате "2 текстурные координаты, 3 вершинные".
    // Обратите внимание, что мозаичность куба минимальна.
    GLfloat data[]= {
      // ЛИЦЕВАЯ ГРАНЬ
      0.0f, 0.0f,    -1.0f, -1.0f, +1.0f,
      1.0f, 0.0f,    +1.0f, -1.0f, +1.0f,
      1.0f, 1.0f,    +1.0f, +1.0f, +1.0f,
      0.0f, 1.0f,    -1.0f, +1.0f, +1.0f,
      // ЗАДНЯЯ ГРАНЬ
      1.0f, 0.0f,    -1.0f, -1.0f, -1.0f,
      1.0f, 1.0f,    -1.0f, +1.0f, -1.0f,
      0.0f, 1.0f,    +1.0f, +1.0f, -1.0f,
      0.0f, 0.0f,    +1.0f, -1.0f, -1.0f,
      // ВЕРХНЯЯ ГРАНЬ
      0.0f, 1.0f,    -1.0f, +1.0f, -1.0f,
      0.0f, 0.0f,    -1.0f, +1.0f, +1.0f,
      1.0f, 0.0f,    +1.0f, +1.0f, +1.0f,
      1.0f, 1.0f,    +1.0f, +1.0f, -1.0f,
      // НИЖНЯЯ ГРАНЬ
      1.0f, 1.0f,    -1.0f, -1.0f, -1.0f,
      0.0f, 1.0f,    +1.0f, -1.0f, -1.0f,
      0.0f, 0.0f,    +1.0f, -1.0f, +1.0f,
      1.0f, 0.0f,    -1.0f, -1.0f, +1.0f,
      // ПРАВАЯ ГРАНЬ
      1.0f, 0.0f,    +1.0f, -1.0f, -1.0f,
      1.0f, 1.0f,    +1.0f, +1.0f, -1.0f,
      0.0f, 1.0f,    +1.0f, +1.0f, +1.0f,
      0.0f, 0.0f,    +1.0f, -1.0f, +1.0f,
      // ЛЕВАЯ ГРАНЬ
      0.0f, 0.0f,    -1.0f, -1.0f, -1.0f,
      1.0f, 0.0f,    -1.0f, -1.0f, +1.0f,
      1.0f, 1.0f,    -1.0f, +1.0f, +1.0f,
      0.0f, 1.0f,    -1.0f, +1.0f, -1.0f
    };
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);        // Объявление WndProc
    В следующем блоке кода реализована проверка поддержки расширений во время выполнения.
    Во-первых, предположим, что у нас есть длинная строка, содержащая список всех поддерживаемых расширений, представленных в виде подстрок, разделенных символом ‘\n’. Таким образом, надо провести поиск этого символа и начать сравнение string с search до достижения либо очередного ‘\n’, либо отличия в сравниваемых строках. В первом случае вернем в "найдено" значение true, во втором — возьмем следующую подстроку, и так до тех пор, пока не кончится string. Со string придется немного повозиться, поскольку она начинается не с символа ‘\n’.
    Кстати: проверку доступности любого данного расширения во время выполнения программы надо выполнять ВСЕГДА!


    bool isInString(char *string, const char *search) {
      int pos=0;
      int maxpos=strlen(search)-1;
      int len=strlen(string);
      char *other;
      for (int i=0; i<len; i++) {
        if ((i==0) || ((i>1) && string[i-1]=='\n')) { // Новые расширения начинаются здесь!
          other=&string[i];
          pos=0; // Начать новый поиск
          while (string[i]!='\n') { // Поиск по всей строке расширения
            if (string[i]==search[pos]) pos++; // Следующий символ
            if ((pos>maxpos) && string[i+1]=='\n') return true; // А вот и она!
            i++;
          }
        }
      }
      return false; // Простите, не нашли!
    }
    Теперь извлечем строку расширений и преобразуем ее в строки, разделенные символом ‘\n’, чтобы провести поиск. Если будет обнаружена строка ”GL_ARB_multitexture”, значит, эта опция поддерживается. Но чтобы ее использовать, нужно, во-первых, чтобы __ARB_ENABLE была установлена в true, а во-вторых, чтобы карточка поддерживала расширение GL_EXT_texture_env_combine, которое указывает, что аппаратура разрешает некоторые новые способы взаимодействия между своими текстурными блоками. Это необходимо, поскольку GL_ARB_multitexture обеспечивает лишь вывод обработанных данных последовательно с текстурного блока с меньшим номером на блок с большим, а поддержка GL_EXT_texture_env_combine означает возможность использования уравнений смешивания повышенной сложности, эффект от которых совсем другой. Если все необходимые расширения поддерживаются и мы не запретили их сами, определим количество доступных текстурных блоков. Это число будет храниться в maxTexelUnits. Затем установим связь между функциями и их именами, для этого воспользуемся вызовом wglGetProcAdress(), передавая ей в качестве параметра строку-имя искомой функции и проводя преобразование типа результата, чтобы гарантировать совпадение ожидаемого и полученного типов.
    bool initMultitexture(void) {
      char *extensions;
      extensions=strdup((char *) glGetString(GL_EXTENSIONS)); // Получим строку расширений


      int len=strlen(extensions);
      for (int i=0; i<len; i++) // Разделим ее символами новой строки вместо пробелов
        if (extensions[i]==' ') extensions[i]='\n';
    #ifdef EXT_INFO
      MessageBox(hWnd,extensions,"поддерживаются расширения GL:",MB_OK | MB_ICONINFORMATION);
    #endif
      if (isInString(extensions,"GL_ARB_multitexture") // Мультитекстурирование поддерживается?
        && __ARB_ENABLE  // Проверим флаг
        // Поддерживается среда комбинирования текстур?
        && isInString(extensions,"GL_EXT_texture_env_combine"))
      {      
        glGetIntegerv(GL_MAX_TEXTURE_UNITS_ARB,&maxTexelUnits);
        glMultiTexCoord1fARB = (PFNGLMULTITEXCOORD1FARBPROC) wglGetProcAddress("glMultiTexCoord1fARB");
        glMultiTexCoord2fARB = (PFNGLMULTITEXCOORD2FARBPROC) wglGetProcAddress("glMultiTexCoord2fARB");
        glMultiTexCoord3fARB = (PFNGLMULTITEXCOORD3FARBPROC) wglGetProcAddress("glMultiTexCoord3fARB");
        glMultiTexCoord4fARB = (PFNGLMULTITEXCOORD4FARBPROC) wglGetProcAddress("glMultiTexCoord4fARB");
        glActiveTextureARB   = (PFNGLACTIVETEXTUREARBPROC) wglGetProcAddress("glActiveTextureARB");
        glClientActiveTextureARB= (PFNGLCLIENTACTIVETEXTUREARBPROC) wglGetProcAddress("glClientActiveTextureARB");
                  
    #ifdef EXT_INFO
        MessageBox(hWnd,"Будет использовано расширение GL_ARB_multitexture.",
         "опция поддерживается!",MB_OK | MB_ICONINFORMATION);
    #endif
        return true;
      }
      useMultitexture=false;// Невозможно использовать то, что не поддерживается аппаратурой
      return false;
    }
    InitLights() инициализирует освещение OpenGL, будучи вызвана позже из InitGL().
    void initLights(void) {
            // Загрузка параметров освещения в GL_LIGHT1
            glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);


            glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
            glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);
            glEnable(GL_LIGHT1);
    }
    Здесь грузится УЙМА текстур. Поскольку у функции auxDIBImageLoad() есть собственный обработчик ошибок, а LoadBMP() труднопредсказуема и требует блока try-catch, я отказался от нее. Но вернемся к процедуре загрузки. Сначала берем базовую картинку и создаем на ее основе три фильтрованных текстуры (в режимах GL_NEAREST, GL_LINEAR и GL_LINEAR_MIPMAP_NEAREST). Обратите внимание, для хранения растра используется лишь один экземпляр структуры данных, поскольку в один момент открытой нужна лишь одна картинка. Здесь применяется новая структура данных, alpha — в ней содержится альфа-слой текстур. Такой подход позволяет хранить RGBA-изображения в виде двух картинок: основного 24-битного RGB растра и 8-битного альфа-канала в шкале серого. Чтобы индикатор состояния работал корректно, нужно удалять Image-блок после каждой загрузки и сбрасывать его в NULL.
    Еще одна особенность: при задании типа текстуры используется GL_RGB8 вместо обычного "3". Это сделано для совместимости с будущими версиями OpenGL-ICD и рекомендуется к использованию вместо любого другого числа. Такие параметры я пометил оранжевым.
    int LoadGLTextures() {           // Загрузка растра и преобразование в текстуры
      bool status=true;              // Индикатор состояния
      AUX_RGBImageRec *Image=NULL;   // Создадим место для хранения текстур
      char *alpha=NULL;
      // Загрузим базовый растр
      if (Image=auxDIBImageLoad("Data/Base.bmp")) {
        glGenTextures(3, texture);    // Создадим три текстуры
        // Создаем текстуру с фильтром по ближайшему
        glBindTexture(GL_TEXTURE_2D, texture[0]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);


        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0,
          GL_RGB, GL_UNSIGNED_BYTE, Image->data);
        // Создаем текстуру с фильтром усреднения
        glBindTexture(GL_TEXTURE_2D, texture[1]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, Image->sizeX, Image->sizeY, 0,
           GL_RGB, GL_UNSIGNED_BYTE, Image->data);
        // Создаем текстуру с мип-наложением
        glBindTexture(GL_TEXTURE_2D, texture[2]);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8, Image->sizeX, Image->sizeY,
          GL_RGB, GL_UNSIGNED_BYTE, Image->data);
      }
      else status=false;
      if (Image) { // Если текстура существует
        if (Image->data) delete Image->data; // Если изображение существует
        delete Image;
        Image=NULL;
      }
    Загрузим рельеф. По причинам, объясняемым ниже, текстура рельефа должна иметь 50% яркость, поэтому ее надо промасштабировать. Сделаем это через команды glPixelTransferf(), которые описывают попиксельное преобразование данных растра в текстуру. Если вы до сих пор не пользовались командами семейства glPixelTransfer(), рекомендую обратить на них пристальное внимание, поскольку они часто бывают очень удобны и полезны.
    Теперь учтем, что нам не нужно, чтобы базовая картинка многократно повторялась в текстуре. Чтобы получить картинку единожды, растянутой в нужное количество раз, ее надо привязать к текстурным координатам с (s,t)=(0.0f, 0.0f) по (s,t)=(1.0f, 1.0f). Все остальные координаты привязываются к чистому черному цвету через вызовы glTexParameteri(), которые даже не требуют пояснений.
      // Загрузим рельефы
      if (Image=auxDIBImageLoad("Data/Bump.bmp")) {
        glPixelTransferf(GL_RED_SCALE,0.5f);   // Промасштабируем яркость до 50%,


        glPixelTransferf(GL_GREEN_SCALE,0.5f); // поскольку нам нужна половинная интенсивность
        glPixelTransferf(GL_BLUE_SCALE,0.5f);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP);  // Не укладывать паркетом
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP);
        glGenTextures(3, bump);  // Создать три текстуры
        // Создать текстуру с фильтром по ближайшему
        >…<
        // Создать текстуру с фильтром усреднения
        >…<
        // Создать текстуру с мип-наложением
        >…<
    С этой фразой вы уже знакомы: по причинам, объясненным ниже, нужно создать инвертированную карту рельефа с той же 50% яркостью. Для этого вычтем полученный ранее растр из чистого белого цвета {255, 255, 255}. Поскольку мы НЕ возвращали RGB-масштабирование на 100% уровень (я три часа разбирался, пока понял, что здесь скрывалась основная ошибка первой версии урока!), инверсный рельеф тоже получится 50% яркости.
        for (int i=0; i<3*Image->sizeX*Image->sizeY; i++) // Проинвертируем растр
          Image->data[i]=255-Image->data[i];
        glGenTextures(3, invbump); // Создадим три текстуры
        // с фильтром по ближайшему
        >…<
        // с фильтром усреднения
        >…<
        // с мип-наложением
        >…<
      }
      else status=false;
      if (Image) { // Если текстура существует
        if (Image->data) delete Image->data; // Если изображение текстуры существует
        delete Image;
        Image=NULL;
      }
    Загрузка изображения логотипа очень проста, кроме, разве что, фрагмента рекомбинации RGB-A. Он, впрочем, тоже достаточно очевиден. Заметьте, что текстура строится на основе alpha-, а не Image-блока! Здесь применена только одна фильтрация.
      // Загрузка картинки логотипа
      if (Image=auxDIBImageLoad("Data/OpenGL_ALPHA.bmp")) {
        alpha=new char[4*Image->sizeX*Image->sizeY];
        // Выделим память для RGBA8-текстуры
        for (int a=0; a<Image->sizeX*Image->sizeY; a++)


          alpha[4*a+3]=Image->data[a*3]; // Берем красную величину как альфа-канал
        if (!(Image=auxDIBImageLoad("Data/OpenGL.bmp"))) status=false;
        for (a=0; a<Image->sizeX*Image->sizeY; a++) {
          alpha[4*a]=Image->data[a*3];        // R
          alpha[4*a+1]=Image->data[a*3+1];    // G
          alpha[4*a+2]=Image->data[a*3+2];    // B
        }
        glGenTextures(1, &glLogo);  // Создать одну текстуру
        // Создать RGBA8-текстуру с фильтром усреднения
        glBindTexture(GL_TEXTURE_2D, glLogo);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Image->sizeX, Image->sizeY, 0,
           GL_RGBA, GL_UNSIGNED_BYTE, alpha);
        delete alpha;
      }
      else status=false;
      if (Image) { // Если текстура существует
        if (Image->data) delete Image->data; // Если изображение текстуры существует
        delete Image;
        Image=NULL;
      }
      // Загрузим логотип "Extension Enabled"
      if (Image=auxDIBImageLoad("Data/multi_on_alpha.bmp")) {
        alpha=new char[4*Image->sizeX*Image->sizeY]; // Выделить память для RGBA8-текстуры
        >…<
        glGenTextures(1, &multiLogo); // Создать одну текстуру
        // Создать RGBA8-текстуру с фильтром усреднения
        >…<
        delete alpha;
      }
      else status=false;
      if (Image) { // Если текстура существует
        if (Image->data) delete Image->data; // Если изображение текстуры существует
        delete Image;
        Image=NULL;
      }
      return status;  // Вернем состояние
    }
    Далее идет практически единственная неизмененная функция ReSizeGLScene(), и ее я пропустил. За ней следует функция doCube(), рисующая куб с единичными нормалями. Она задействует только текстурный блок №0, потому что glTexCoord2f(s,t) делает то же самое, что и glMultiTexCoord2f(GL_TEXTURE0_ARB,s,t). Обратите внимание, что куб нельзя создать, используя чередующиеся массивы, но это тема для отдельного разговора. Кроме того, учтите, что куб НЕВОЗМОЖНО создать, пользуясь списками отображения. Видимо, точность внутреннего представления данных, используемая в этих списках, не соответствует точности, применяемой в GLfloat. Это ведет к неприятным эффектам, которые называются проблемами деколирования (когда источник света не влияет на закрашивание объекта), поэтому от списков я решил отказаться. Вообще, я полагаю, что надо либо делать всю геометрию, пользуясь списками, либо не применять их вообще. Смешивание разных подходов приводит к проблемам, которые где-нибудь да проявятся, даже если на вашей аппаратуре все пройдет успешно.


    GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
    // Изменить размер и инициализировать окно GL
    >…<
    void doCube (void) {
      int i;
      glBegin(GL_QUADS);
        // Передняя грань
        glNormal3f( 0.0f, 0.0f, +1.0f);
        for (i=0; i<4; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
        // Задняя грань
        glNormal3f( 0.0f, 0.0f,-1.0f);
        for (i=4; i<8; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
        // Верхняя грань
        glNormal3f( 0.0f, 1.0f, 0.0f);
        for (i=8; i<12; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
        // Нижняя грань
        glNormal3f( 0.0f,-1.0f, 0.0f);
        for (i=12; i<16; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
        // Правая грань
        glNormal3f( 1.0f, 0.0f, 0.0f);
        for (i=16; i<20; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
        // Левая грань
        glNormal3f(-1.0f, 0.0f, 0.0f);
        for (i=20; i<24; i++) {
          glTexCoord2f(data[5*i],data[5*i+1]);
          glVertex3f(data[5*i+2],data[5*i+3],data[5*i+4]);
        }
      glEnd();
    }
    Время инициализировать OpenGL. Все как в Уроке 06, кроме вызова initLights() вместо прямой инициализации источников света в теле функции. Да, и еще одно: я выполняю здесь настройку мультитекстурирования.
    int InitGL(GLvoid)                      // Все настройки OpenGL проходят здесь
    {
      multitextureSupported=initMultitexture();
      if (!LoadGLTextures()) return false;  // Переход к процедуре загрузки текстур
      glEnable(GL_TEXTURE_2D);              // Включить привязку текстур
      glShadeModel(GL_SMOOTH);              // Включит сглаживание
      glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // Черный фон
      glClearDepth(1.0f);                   // Установка буфера глубины


      glEnable(GL_DEPTH_TEST);              // Включить проверку глубины
      glDepthFunc(GL_LEQUAL);               // Тип проверки глубины
      glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Наилучшая коррекция перспективы
      initLights();                         // Инициализация освещения OpenGL
      return true                           // Инициализация закончилась успешно
    }
    95% всей работы содержится здесь. Все, что упоминалось под грифом "по причинам, объясненным ниже", будет расписано в этом теоретическом блоке.
    Начало теории ( Наложение микрорельефа методом тиснения )
    Если у вас установлен просмотрщик Powerpoint-презентаций, я настоятельно рекомендую скачать следующую презентацию:
    "Emboss Bump Mapping" by Michael I. Gold, nVidia Corp. [.ppt, 309K]
    Для тех, у кого нет просмотрщика, я попытался перевести презентацию в html-формат. Вот она:
    Наложение микрорельефа методом тиснения
    Майкл И. Голд
    Корпорация Nvidia
    Наложение микрорельефа (bump mapping)
    Действительное наложение микрорельефа использует попиксельные вычисления освещенности
  • Вычисление освещенности в каждой точке базируется на возмущенном векторе нормали.

  • Вычисления весьма ресурсоемкие.

  • Более детальное описание читайте здесь: Blinn, J. : Simulation of Wrinkled Surfaces (Моделирование складчатых поверхностей), Computer Graphics. 12,3 (August 1978) 286-292.

  • Информация в сети: на http://www.objectecture.com/ лежит Cass Everitt’s Orthogonal Illumination Thesis (Диссертация по ортогональному освещению Касса Эверитта).

  • Наложение микрорельефа методом тиснения (emboss bump mapping)
    Микрорельеф, наложенный тиснением, похож на резьбу по материалу
  • Учитывается только рассеянный свет, нет зеркальной составляющей

  • Возможны артефакты изображения из-за недостаточного размера текстуры рельефа (в результате, например, движение приводит к сильному размытию — прим. Дженса)

  • Выполнение возможно на пользовательском оборудовании современного уровня (как показано — прим. Дженса)



  • Если рельеф выглядит хорошо, используйте его!

  • Расчет рассеяния света
    C=(L*N) x Dl x Dm
  • L — вектор освещения

  • N — вектор нормали

  • Dl — цвет падающего света

  • Dm — цвет рассеяния материала

  • При наложении микрорельефа попиксельно меняется N

  • При наложении микрорельефа методом тиснения используется аппроксимация (L*N)

  • Аппроксимация коэффициента рассеяния L*N
    В текстурной карте содержится поле высот
  • [0,1] — диапазон значений, принимаемых функцией рельефности

  • Первая производная определяет величину уклона m (материала) в данной точке (Заметьте, что m — чисто одномерная величина. Считайте, что m — это оценка grad(s,t) (градиента) в данной точке — прим. Дженса)

  • m увеличивает/уменьшает базовый коэффициент рассеяния Fd

  • (Fd+m) приближенно определяет (L*N) для каждого пикселя

  • Приближенное вычисление производной
    Используется приближенное вычисление производной
  • Берется высота H0 в точке (s,t)

  • Определяется высота H1 в точке, слегка сдвинутой в направлении источника света, (s+ds,t+dt)

  • Исходная высота H0 вычитается из возмущенной высоты H1

  • Разница является оценкой мгновенного угла наклона m=H1-H0

  • Вычисление рельефа

    1) Исходный рельеф (H0).

    2) На исходный рельеф (H0) накладывается другой, (H1), слегка сдвинутый в направлении источника света.

    3) Из второго вычитается первый (H0-H1). Появляются освещенные (B, bright) и затемненные (D, dark) участки.
    Вычисление освещенности
    Вычисляется цвет фрагмента Cf
  • Cf = (L*N) x Dl x Dm

  • (L*N) ~ (Fd + (H1-H0))

  • Dm x Dl закодировано в текстуре поверхности Ct.