Урок 23. Квадратирование со сферическим наложением в OpenGL

  Сферическое наложение текстур окружения дает возможность быстро создавать отражения в металлических или зеркальных поверхностях в кадре. Этот метод не столь точен, как кубическая карта окружения, и уж тем более отличается от реальной жизни, зато он гораздо быстрее! За основу возьмем код Урока 18. Кроме того, мы не будем пользоваться текстурами оттуда, взамен них создадим две новые: сферическую карту и фоновое изображение.

  Прежде чем мы начнем… "Красная книга" определяет сферическую карту как изображение сцены на металлическом шаре из бесконечно удаленной точки с бесконечным фокусным расстоянием. Конечно, в реальности это недостижимо. Лучший способ создать сферическую карту, не пользуясь линзой "рыбий глаз", какой я обнаружил, это поработать в Фотошопе.

  Создание сферической карты в Фотошопе:

  Прежде всего, нужно изображение, которое вы намерены использовать для сферического наложения. Откройте изображение в Фотошопе и выделите его целиком. Скопируйте, создайте новый файл (при создании его размеры будут предложены Фотошопом, они будут совпадать с размерами скопированного изображения) и вставьте туда содержимое буфера. Смысл операции состоит в том, чтобы получить возможность использовать все фильтры Фотошопа. Другой способ добиться этого - изменить текущий режим изображения на RGB через соответствущий пункт меню.

  Затем изображение нужно промасштабировать так, чтобы его размеры были степенью двойки. Как вы помните, чтобы изображение можно было использовать в качестве текстуры, оно должно иметь размер 128х128, 256х256 точек и так далее. В меню "Изображение" выберите "Размер изображения", снимите галочку напротив опции сохранения пропорций и измените размеры так, чтобы подогнать их к размерам текстуры. Если исходное изображение, скажем, 100х90 точек, предпочтительно сделать его 128х128, а не 64х64 точки, чтобы максимально сохранить детали.

  Наконец, из меню "Фильтры" надо выбрать "Искажения" и в них "Сферизацию". После применения этого фильтра центр изображения станет выпуклым, как шарик. В нормальной сферической карте изображение по мере приближения к краю должно темнеть и уходить в черноту, но сейчас это неважно. Сохраните полученный результат в формате BMP, и можно приступать к кодированию!

  На этот раз мы не станем вводить никаких глобальных переменных, только модифицируем массив текстур так, чтобы он мог хранить их 6 штук.

 

GLuint  texture[6];                // Хранилище для 6 текстур ( ИЗМЕНЕНО )

  Следующее, что я сделал - модифицировал функцию LoadGLTextures() так, чтобы она могла загружать 2 картинки и создавать 3 фильтра (похоже на то, как это было в уроках по обычному текстурированию). Там выполнялись два цикла, и в каждом создавалось три текстуры с использованием разных режимов фильтрации. Почти весь этот код переписан или изменен.

 

int LoadGLTextures()                // Загрузить картинки и создать текстуры

{

  int Status=FALSE;                 // Индикатор статуса

 

  AUX_RGBImageRec *TextureImage[2]; // Выделим место для хранения текстур

 

  memset(TextureImage,0,sizeof(void *)*2);         // Сбросим эти указатели

 

  // Загрузим картинку, проверим на ошибки, если картинка не найдена - выйдем

  if ((TextureImage[0]=LoadBMP("Data/BG.bmp")) &&  // Фоновая текстура

    (TextureImage[1]=LoadBMP("Data/Reflect.bmp"))) // Текстура отражения

                                                   // (сферическая карта)

  {

    Status=TRUE;                    // Установить индикатор в TRUE

 

    glGenTextures(6, &texture[0]);  // Создадим три текстуры

 

    for (int loop=0; loop<=1; loop++)

    {

      // Создадим текстуры без фильтрации

      glBindTexture(GL_TEXTURE_2D, texture[loop]); // Текстуры 0 и 1

      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, 3, TextureImage[loop]->sizeX,

                   TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,

                   TextureImage[loop]->data);

 

      // Создадим линейно фильтрованные текстуры

      glBindTexture(GL_TEXTURE_2D, texture[loop+2]);    // Текстуры 2, 3 и 4

      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, 3, TextureImage[loop]->sizeX,

                   TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE,

                   TextureImage[loop]->data);

 

      // мип-мап текстуры

      glBindTexture(GL_TEXTURE_2D, texture[loop+4]);    // Текстуры 4 и 5

      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, 3, TextureImage[loop]->sizeX,

                        TextureImage[loop]->sizeY, GL_RGB, GL_UNSIGNED_BYTE,

                        TextureImage[loop]->data);

    }

    for (loop=0; loop<=1; loop++)

    {

          if (TextureImage[loop])                  // Если текстура существует

        {

              if (TextureImage[loop]->data)        // Если существует изображение текстуры

            {

                  free(TextureImage[loop]->data);  // Освободим память изображения текстуры

          }

          free(TextureImage[loop]);                // Освободим память

                                                   // структуры изображения

      }

    }

  }

 

  return Status; // Вернем статус

}

  Теперь слегка изменим код рисования куба. Вместо 1.0 и -1.0 в качестве значений нормали используем 0.5 и -0.5. Так у нас появится возможность увеличивать и уменьшать карту отражений. Если значение нормали велико, то отраженное изображение станет больше и, возможно, на нем будут заметны квадратики. А если нормаль уменьшить до 0.5, изображение тоже уменьшится и качество картинки повысится. Если еще сильнее уменьшить значение нормали, то мы получим нежелательные результаты.

 

GLvoid glDrawCube()

{

    glBegin(GL_QUADS);

    // Передняя грань

    glNormal3f( 0.0f, 0.0f, 0.5f);          ( Изменено )

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    // Задняя грань

    glNormal3f( 0.0f, 0.0f,-0.5f);          ( Изменено )

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    // Верхняя грань

    glNormal3f( 0.0f, 0.5f, 0.0f);          ( Изменено )

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    // Нижняя грань

    glNormal3f( 0.0f,-0.5f, 0.0f);          ( Изменено )

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    // Правая грань

    glNormal3f( 0.5f, 0.0f, 0.0f);          ( Изменено )

    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    // Левая грань

    glNormal3f(-0.5f, 0.0f, 0.0f);          ( Изменено )

    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);

    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);

    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

  glEnd();

}

  Теперь в InitGL будут добавлены два новых вызова, устанавливающих режим генерации текстур для S и T для использования при сферическом текстурировании. Текстурные координаты S, T, R и Q определенным образом соответствуют координатам объекта x, y, z и w. Если вы применяете одномерную текстуру (1D), то будете использовать координату S. Если текстура двумерная, то кроме S применяется и координата T.

  Следующий фрагмент кода заставляет OpenGL генерировать координаты S и T, основываясь на формуле сферического наложения. Координаты R и Q обычно игнорируются. Координата Q может быть использована в расширениях продвинутых техник текстурирования, а координата R, возможно, станет полезной, когда в библиотеку OpenGL будет добавлено 3D текстурирование. Сейчас же мы проигнорируем и R, и Q. Координата S идет горизонтально через плоскость нашего полигона, а координата T - вертикально.

 

  // Изменить для S режим генерации текстур на "сферическое наложение" ( Новое )

  glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

  // Изменить для T режим генерации текстур на "сферическое наложение" ( Новое )

  glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

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

 

int DrawGLScene(GLvoid)          // Здесь происходит все рисование

{

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Очистим экран и буфер глубины

  glLoadIdentity();              // Сбросим вид

 

 

  glTranslatef(0.0f,0.0f,z);

 

  glEnable(GL_TEXTURE_GEN_S);    // Включим генерацию координат текстуры для S ( НОВОЕ )

  glEnable(GL_TEXTURE_GEN_T);    // Включим генерацию координат текстуры для T ( НОВОЕ )

 

  // Выберем сферическое текстурирование ( ИЗМЕНЕНО )

  glBindTexture(GL_TEXTURE_2D, texture[filter+(filter+1)]);

  glPushMatrix();

  glRotatef(xrot,1.0f,0.0f,0.0f);

  glRotatef(yrot,0.0f,1.0f,0.0f);

  switch(object)

  {

  case 0:

    glDrawCube();

    break;

  case 1:

    glTranslatef(0.0f,0.0f,-1.5f);               // Отцентруем цилиндр

    gluCylinder(quadratic,1.0f,1.0f,3.0f,32,32); // Цилиндр радиусом 0.5 и высотой 2

    break;

  case 2:

    // Сфера радиусом 1, состоящая из 16 сегментов по долготе/широте

    gluSphere(quadratic,1.3f,32,32);

    break;

  case 3:

    glTranslatef(0.0f,0.0f,-1.5f);                // Отцентруем конус

    // Конус с радиусом основания 0.5 и высотой 2

    gluCylinder(quadratic,1.0f,0.0f,3.0f,32,32);

    break;

  };

 

  glPopMatrix();

  glDisable(GL_TEXTURE_GEN_S);        // Отключим генерацию текстурных координат ( НОВОЕ )

  glDisable(GL_TEXTURE_GEN_T);        // Отключим генерацию текстурных координат ( НОВОЕ )

 

  glBindTexture(GL_TEXTURE_2D, texture[filter*2]); // Выберем фоновую текстуру ( НОВОЕ )

  glPushMatrix();

    glTranslatef(0.0f, 0.0f, -24.0f);

    glBegin(GL_QUADS);

      glNormal3f( 0.0f, 0.0f, 1.0f);

      glTexCoord2f(0.0f, 0.0f); glVertex3f(-13.3f, -10.0f,  10.0f);

      glTexCoord2f(1.0f, 0.0f); glVertex3f( 13.3f, -10.0f,  10.0f);

      glTexCoord2f(1.0f, 1.0f); glVertex3f( 13.3f,  10.0f,  10.0f);

      glTexCoord2f(0.0f, 1.0f); glVertex3f(-13.3f,  10.0f,  10.0f);

    glEnd();

 

  glPopMatrix();

 

  xrot+=xspeed;

  yrot+=yspeed;

  return TRUE; // Продолжим

}

  Последнее, что надо сделать - обновить процедуру обработки нажатия пробела, чтобы отразить изменения, внесенные нами в изображаемые квадратичные объекты (были удалены диски).

 

        if (keys[' '] && !sp)

        {

          sp=TRUE;

          object++;

          if(object>3)

            object=0;

        }

  Мы закончили! Теперь, пользуясь наложением текстур окружения, вы можете делать разные впечатляющие вещи, например, почти точное отражение содержимого комнаты. Я планировал показать, как делать кубическое наложение, но моя видеокарточка не поддерживает этот режим. Может быть, через месяц или около того я куплю GeForce2 :). Кроме того, описанное текстурирование я изучал самостоятельно (в основном из-за того, что по этому вопросу практически нет информации), так что если в этом уроке есть какие-то неточности, сообщите о них либо мне по почте, либо сразу NeHe.

  Спасибо. Удачи!

 

© GB Schmick (TipTup)
http://www.tiptup.com/

 15 января 2002 (c)  Vasily Chernikov