OpenGL. Урок 5. Создание и наложение текстур.

Как уже понятно из названия статьи, речь пойдет об использовании текстур в OpenGL. В прошлом уроке мы создали куб и добавили возможность вращать его вокруг осей координат. Сегодня давайте попробуем наложить текстуры на грани куба и получить некое подобие игральной кости =)

Создание и наложение текстур

Итак, возьмем за основу проект из предыдущей статьи, и для начала нужно добавить в него изображения, на базе которых впоследствии мы создадим текстуры. Я не стал особо заморачиваться, просто нарисовал по-быстрому в paint’е 6 картинок, по одной для каждой грани куба 😉 Получилось вот так:

Наложение текстур в OpenGL

Тем, кто как и я, использует Qt Creator теперь необходимо добавить эти файлы в проект. Для этого нужно создать пустой файл ресурсов и прописать в нем пути для всех используемых изображений. Не будем в эту тему углубляться, все-таки статья не об этом 😉

Итак, картинки готовы, задача понятна, давайте приступать к реализации. И первым делом нам необходимо, собственно, создать текстуры из изображений. Добавим в проект функцию generateTextutes():

void MainScene::generateTextures()
{
    glGenTextures(6, textures);
 
    QImage texture1;
    texture1.load(":/cubeOne.jpg");
    texture1 = QGLWidget::convertToGLFormat(texture1);
    glBindTexture(GL_TEXTURE_2D, textures[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, (GLsizei)texture1.width(), (GLsizei)texture1.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, texture1.bits());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

Я решил привести в качестве примера код для создания только одной текстуры, для одной грани. Для остальных граней все делается абсолютно также. В конце статьи я обязательно выложу полный проект с примером, и там можно будет посмотреть весь код 😉

Давайте разбираться, что тут происходит…

Первым делом вызываем функцию glGenTextures() — она генерирует специальные имена для каждой из текстур и помещает их в массив, который мы передаем в качестве одного из аргументов. А первый аргумент — количество текстур, которые нам понадобятся (в данном случае у нас 6 граней, значит передаем в функцию аргумент «6»). Эта функция вызывается только один раз, в самом начале функции generateTextures().

И, кстати, не забудьте объявить массив в нашем классе MainScene:

GLuint textures[6];

С этим все понятно, переходим дальше. Теперь мы создаем объект класса QImage и загружаем в него наше изображение. Функция glBindTexture делает активной текстуру с номером, соответствующим аргументу функции. В данном случае это текстура для первой грани (textures[0]). Вызовом этой функции мы как бы показываем OpenGL, что дальнейшие действия нужно производить именно с той текстурой, которую мы выбрали. Поэтому вызов glTexImage2D() произведет генерацию текстуры, соответствующей номеру textures[0] (напоминаю, в этом массиве у нас хранятся уникальные номера-идентификаторы для каждой из текстур проекта).

Функция glTexImage2D() принимает на вход кучу аргументов, так что давайте рассмотрим каждый из них в отдельности:

  • первый параметр говорит о том, что мы будем работать с 2D текстурой
  • второй параметр — уровень детализации, у нас 0
  • третий — 3 — число цветовых компонент — RGB
  • четвертый, пятый — ширина, высота текстуры, у нас они определяются автоматически
  • пятый — относится к границе текстуры, его просто оставляем нулевым
  • шестой, седьмой — дают OpenGL информацию о цветах и типе данных исходного изображения
  • и, наконец, седьмой параметр передает OpenGL сами данные изображения, из которых нужно формировать текстуру

На этом создание текстуры заканчивается, но нужно еще настроить некоторые параметры отображения текстур:

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

Эти две функции определяют, как будет выглядеть текстура, если размер оригинальной текстуры больше/меньше (GL_TEXTURE_MAG_FILTER или GL_TEXTURE_MIN_FILTER), чем изображение на экране. При выборе GL_LINEAR текстура будет всегда выглядеть сглаженной.

С созданием теперь мы разобрались окончательно, добавим пару строк в функцию initializeGL():

glEnable(GL_TEXTURE_2D);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);

Как вы уже догадались, работать с текстурами мы будем также через массивы. Каждой вершине грани (квадрата) нужно поставить в соответствие 2D координату текстуры, причем нужно обязательно учесть, что текстуры в OpenGL имеют нормированные координаты, то есть от 0 до 1. В нашем примере текстура квадратная, значит она будет иметь такие координаты:

Массив вершин и текстур в OpenGL

Рассмотрим код для наложения текстуры на первую (переднюю) грань куба. Для остальных граней все делается аналогично.

glBindTexture(GL_TEXTURE_2D, textures[0]);
 
cubeVertexArray[0][0] = 0.0;
cubeVertexArray[0][1] = 0.0;
cubeVertexArray[0][2] = 1.0;
 
cubeVertexArray[1][0] = 0.0;
cubeVertexArray[1][1] = 1.0;
cubeVertexArray[1][2] = 1.0;
 
cubeVertexArray[2][0] = 1.0;
cubeVertexArray[2][1] = 1.0;
cubeVertexArray[2][2] = 1.0;
 
cubeVertexArray[3][0] = 1.0;
cubeVertexArray[3][1] = 0.0;
cubeVertexArray[3][2] = 1.0;
 
cubeVertexArray[4][0] = 0.0;
cubeVertexArray[4][1] = 0.0;
cubeVertexArray[4][2] = 0.0;
 
cubeVertexArray[5][0] = 0.0;
cubeVertexArray[5][1] = 1.0;
cubeVertexArray[5][2] = 0.0;
 
cubeVertexArray[6][0] = 1.0;
cubeVertexArray[6][1] = 1.0;
cubeVertexArray[6][2] = 0.0;
 
cubeVertexArray[7][0] = 1.0;
cubeVertexArray[7][1] = 0.0;
cubeVertexArray[7][2] = 0.0;
 
cubeTextureArray[0][0] = 0.0;
cubeTextureArray[0][1] = 0.0;
 
cubeTextureArray[1][0] = 1.0;
cubeTextureArray[1][1] = 0.0;
 
cubeTextureArray[2][0] = 1.0;
cubeTextureArray[2][1] = 1.0;
 
cubeTextureArray[3][0] = 0.0;
cubeTextureArray[3][1] = 1.0;
 
cubeIndexArray[0][0] = 0;
cubeIndexArray[0][1] = 3;
 
cubeIndexArray[0][2] = 2;
cubeIndexArray[0][3] = 1;
 
glVertexPointer(3, GL_FLOAT, 0, cubeVertexArray);
glTexCoordPointer(2, GL_FLOAT, 0, cubeTextureArray);
glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, cubeIndexArray);

Как видите, все очень похоже на работу с массивами вершин и цветов, только вместо цветов мы задаем координаты текстуры для каждой из вершин квадрата. Повторив эти действия для остальных граней, в итоге мы получим полноценный «текстурный» кубик 😉

Наложение текстур в OpenGL

Наша задача на сегодня реализована, поэтому на этом я заканчиваю статью, обязательно еще вернемся к теме OpenGL на нашем сайте, так что не проходите мимо 😉

Вот полный проект с примером программы — OpenGLTest_texture

Понравилась статья? Поделись с друзьями!

OpenGL. Урок 5. Создание и наложение текстур.: 8 комментариев
  1. А можно ли каким-нибудь образом динамически выводить на текстуру текст, задаваемый пользователем?

    • Можно попробовать как-нибудь так — генерировать текстуру с текстом и накладывать на исходную текстуру. Хотя возможно есть даже какие-нибудь стандартные средства OpenGL для этого.

  2. Где то в коде вероятно ошибка. Покрутил кубик секунд 20, так он съел ОЗУ почти 1,5 ГБ и вылетел. Есть скрин.

    • Там просто генерацию текстур надо вынести в функцию initializeGL(), потому что это надо делать только один раз, я сейчас перезалью проект.

  3. Все еще разбираюсь с кодом. Перетащил все, что касается куба, в отдельный класс. Еще вытащил из функции отрисовки инициализацию вершин. Она нужна только один раз. В mainScene в функции paintGL вызываю только cubic->paint(). Все работает. Теперь хочется сделать отрисовку всего куба только ОДНИМ вызовом DrawElements(). Вы 6 раз инициализируете нулевой элемент массива cubeIndexArray[0][х]. Хотя у него размерность 6. В предыдущем уроке этот же массив был полностью определен. Не могу понять, что делать с cubeTextureArray и что делает функция glBindTexture(GL_TEXTURE_2D, textures[0]);

    • glBindTexture(GL_TEXTURE_2D, textures[0]) как бы выбирает нужную текстуру, делает ее активной для работы. Поэтому тут так просто не получится сделать отрисовку одним вызовом DrawElements. Зато можно сделать текстуру одной картинкой и тогда это сработает. В массив cubeTextureArray тогда для индексов, соответствующих разным граням нужно будет записывать нужные координаты одной текстуры.

  4. Полагаю, именно из-за того, что отдельно рисуются 6 граней, возникают артефакты в виде белых щелей на ребрах куба (видно даже на картинках).

  5. аа, нет) это обрезка в Paint’e такая) это не артефакты! Но все равно, хочется сделать отрисовку одним вызовом.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *