Chapter 5
Работа с картинками

5.4  Создаем текстуру в памяти

     Одного вывода изображений недостаточно для создания полноценных трехмерных сцен. Часто возникает потребность накладывать изображение на трехмерные объекты и поворачивать/сдвигать их. Для этих целей существую текстуры. Также текстуры помогут вам покрыть весь объект в виде мозаики. Скажем, когда у вас имеется кирпичная стена, то вам не надо загружать изображение с кучей кирпичей. Достаточно загрузить один кирпич и указать, что эту текстуру нужно размножить по всей плоскости.
     Сначала мы разберем создание и наложение текстур на плоскость. Затем рассмотрим наложение текстур на объекты, описанные в секции 4.1. И наконец, на все прочие, созданные из многоугольников; в частности, тор и чайник.

Для того, чтобы наложить текстуру на объект, вы должны:

  1. Загрузить графический файл в память
  2. Создать имя-идентификатор текстуры
  3. Сделать его активным
  4. Создать саму текстуру в памяти
  5. Установить параметры текстуры
  6. Установить параметры взаимодействия текстуры с объектом
  7. Связать координаты текстуры с объектом

Первое вы уже научились делать в предыдущей секции. Создайте проект с именем Texture. Объявите следующие глобальные переменные:

unsigned int photo_tex;
AUX_RGBImageRec* photo_image;


unsigned int space_tex;
AUX_RGBImageRec* space_image;

     Переменные photo_tex и space_tex будут служить идентификаторами текстур. А в photo_image и space_image мы загрузим bmp-файлы. Тут нужно отметить, что текстуры в OpenGL должны иметь размер 2n x 2m, где n и m целые числа. Это сделано для ускорения работы, т.к. сжимать или растягивать такие текстуры быстрее и удобней. Вы, конечно, можете загрузить изображение любого другого размера, но его придется масштабировать. На мой взгляд, это неправильно. Результат масшабирования вас может и не устроить. Так что, я использую графические файлы с размером, кратным степени двойки. Мне удобнее отредактировать изображение в каком-нибудь графическом пакете, урезать его или, наоборот, дополнить, чем потом выяснять, почему оно искажается. Впрочем, тут многое зависит от конкретного случая. Художнику, который делает текстуры, все равно, какого размера ее делать, поэтому легче попросить его сделать изображение с подходящими размерами. Вставьте следующий код в функцию main.

photo_image = auxDIBImageLoad("photo.bmp");
space_image = auxDIBImageLoad("space.bmp");

     Картинки возьмите из моей программы - Texture. Фотографию можете взять свою.;-) Только размер ее желательно оставить 512x512.
     Теперь вы должны создать имя-идентификатор текстуры. Его нужно создавать, когда у вас в приложении используется более одной текстуры, чтобы была возможность как-то их различать. В противном случае, когда текстура только одна, идентификатор ей не нужен. В следующем примере, при наложение текстуры на сферу, у нас будет ровно одна текстура, и я покажу, вызовы каких функций необязательны. А пока, я предполагаю, что вам нужно использовать несколько текстур. Кстати, я просматривал много примеров при написании книги. Среди них были примеры из широко известной Red Book, примеры из MSDN, из Интернет и других источников, но все, что касалось текстур, работало только с одной текстурой. Для элементарной программы-примера, конечно, подойдет и одна текстура, а вот для серьезных приложений, вряд ли. Мы хотим написать серьезные приложения, поэтому нам придется использовать нескольких текстур. Функция glGenTextures принимает на вход два параметра. Первый указывает количество имен-идентификаторов текстур, которые нужно создать. Второй параметр - указатель на массив элементов типа unsigned int. Количество элементов в массиве должно совпадать с числом, указанным в качестве первого параметра. Например, следующий код создает десять имен текстур.

unsigned int names[10];
glGenTetures(10, names);

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

glGenTextures(1, &photo_tex);
glGenTextures(1, &space_tex);

     Теперь мы привязываемся к текстуре фотографии, т.е. делаем ее активной. Для этого служит функция glBindTexture. Первый параметр должен быть GL_TEXTURE_2D или GL_TEXTURE_1D. Он показывает, с одномерным или двумерным изображением будем работать. Все примеры здесь касаются двумерных текстур. Для одномерной текстуры я просто не нашел красивого примера. Впрочем, в Red Book есть пример с одномерной текстурой. Там чайник разукрашивают красной лентой. Где взять эти примеры и многое другое смотрите в приложении 'A'. Второй параметр glBindTexture - идентификатор, который мы создали выше при помощи glGenTextures. Теперь добавьте вызов этой функции в main.

glBindTexture(GL_TEXTURE_2D, photo_tex);

     Теперь мы должны создать саму текстуру в памяти. Массив байт в структуре AUX_RGBImageRec не является еще текстурой, потому что у текстуры много различных параметров. Создав текстуру, мы наделим ее определенными свойствами. Среди параметров текстуры вы указываете уровень детализации, способ масшабирования и связывания текстуры с объектом. Уровень детализации нужен для наложения текстуры на меньшие объекты, т.е. когда площадь на экране меньше размеров изображения. Нулевой уровень детализации соответствует исходному изображению размером 2nx2m, первый уровень - 2n-1x2m-1, k-ый уровень - 2n-kx2m-k. Число уровней соответствует min(n,m). Для создания текстуры имеется две функции glTexImage[1/2]D и gluBuild[1/2]DMipmaps.

glTexImage2D(                 gluBuild2DMipmaps(
  GLenum target,                 GLenum target,
  GLint lavel,                   GLint components,
  GLint components,              GLsizei width,
  GLsizei width,                 GLsizei height,
  GLsizei height,                GLenum format,
  GLint border,                  GLenum type,
  GLenum format,                 const GLvoid* pixels)
  GLenum type,
  const GLvoid* pixels)

     Основное различие в том, что первая функция создает текстуру одного определенного уровня детализации и воспринимает только изображения , размер которых кратен степени двойки. Вторая функция более гибкая. Она генерирует текстуры всех уровней детализации. Также эта функция не требует, чтобы размер изображения был кратен степени двойки. Она сама сожмет/растянет изображение подходящим образом, хотя возможно окажется, что и не вполне подходящим. Я воспользуюсь функцией glTexImage2D. Первый параметр этой функции должен быть GL_TEXTURE_2D. Второй - уровень детализации. Нам нужно исходное изображение, поэтому уровень детализации - ноль. Третий параметр указывает количество компонентов цвета. У нас изображение хранится в формате RGB. Поэтому значение этого параметра равно трем. Четвертый и пятый параметры - ширина и высота изображения. Шестой - ширина границы; у нас гарницы не будет, поэтому значение этого параметра - ноль. Далее, седьмой параметр - формат хранения пикселей в массиве - GL_RGB и тип - GL_UNSIGNED_BYTE. И наконец, восьмой параметр - указатель на массив данных. Еще вы должны вызвать функцию glPixelStorei и задать, что выравнивание в массиве данных идет по байту. Добавьте следующий код в функцию main.

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, 3,
             photo_image->sizeX,
             photo_image->sizeY,
             0, GL_RGB, GL_UNSIGNED_BYTE,
             photo_image->data);

     Аналогичный результат можно получить, вставив вызов gluBuild2DMipmaps с параметрами, указанными ниже.

gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
                  photo_image->sizeX,
                  photo_image->sizeY,
                  GL_RGB, GL_UNSIGNED_BYTE,
                  photo_image->data);

     Теперь нужно установить параметры текстуры. Для этого служит функция

glTexParameter[if](GLenum target, GLenum pname, GLenum param)

     Первый параметр принимает значение GL_TEXTURE_1D или GL_TEXTURE_2D. Второй - pname - определяет параметр текстуры, который вы будете изменять. И третий параметр - это устанавливаемое значение. Если вы воспользовались gluBuild2DMipmaps вместо glTexImage2D, то вам не надо устанавливать следующие параметры, т.к. уже сформированы текстуры всех уровней детализации, и OpenGL сможет подобрать текстуру нужного уровня, если площадь объекта не совпадает с площадью текстуры. В противном случае, вы должны добавить следующие строки:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

     Вы указали, что для уменьшения и увеличения текстуры используется алгоритм GL_NEAREST. Это означает, что цветом пикселя объекта, на который накладывается текстура, становится цвет ближайшего пикселя элемента текстуры. Вместо GL_NEAREST можно указать GL_LINEAR, т.е. цвет элемента объекта будет вычисляться как среднее арифметическое четырех элементов текстуры. Имеются еще четыре алгоритма вычисления цвета элемента объекта. Их можно устанавливать, когда вы создали текстуру со всеми уровнями детализации, т.к. применяют алгоритмы GL_NEAREST и GL_LINEAR к одному или двум ближайшим уровням детализации.
     Еще вы можете установить взаимодействие текстуры с объектом. Тут имеются два режима при использовании трех компонентов цвета. Первый режим, установленный по умолчанию, когда у вас учитывается цвет объекта и цвет текстуры. Результирующий цвет получается перемножением компонентов цвета текстуры на компоненты цвета объекта. Скажем, если цвет текстуры - (r,g,b), а цвет объекта, на который она накладывается, - (r0,g0,b0), то результирующим цветом будет - (r*r0,g*g0,b*b0). В случае, если цвет объекта черный - (0,0,0), то вы не увидите на нем текстуру, так как она вся будет черной. Второй режим взаимодействия, когда цвет объекта не учитывается. Результирующим цветом будет цвет текстуры. Эти параметры можно установить следующим образом.

glTexEnv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
glTexEnv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)

     По умолчанию, как я уже сказал, является режим GL_MODULATE. Теперь сделайте активной текстуру space_tex. И повторите для нее то же самое. На этом заканчивается создание текстуры. Осталось связать координаты текстуры с координатами объекта. Отредактируйте функцию display так:

 glEnable(GL_TEXTURE_2D);
 glColor3d(1,1,1);

 glBindTexture(GL_TEXTURE_2D, space_tex );
  glBegin(GL_QUADS);
     glTexCoord2d(0,0); glVertex3d(-5,-5, -0.1);
     glTexCoord2d(0,1); glVertex3d(-5, 5, -0.1);
     glTexCoord2d(1,1); glVertex3d( 5, 5, -0.1);
     glTexCoord2d(1,0); glVertex3d( 5,-5, -0.1);
  glEnd();

 glBindTexture(GL_TEXTURE_2D, photo_tex);
   glBegin(GL_QUADS);
     glTexCoord2d(0,0); glVertex2d(-4,-4);
     glTexCoord2d(0,1); glVertex2d(-4, 4);
     glTexCoord2d(1,1); glVertex2d( 4, 4);
     glTexCoord2d(1,0); glVertex2d( 4,-4);
 glEnd();

 glDisable(GL_TEXTURE_2D);

     Как вы, наверное, догадались, glTexCoord2d сопоставляет координаты текстуры вершинам четырехугольника. Скажу только, что нижний левый угол текстуры имеет координаты (0,0), а верхний правый - (1,1).


Исходный файл смотрите здесь. Исполняемый файл здесь.
Моя фотография здесь. Звездное небо здесь.