Как уже понятно из названия статьи, речь пойдет об использовании текстур в OpenGL. В прошлом уроке мы создали куб и добавили возможность вращать его вокруг осей координат. Сегодня давайте попробуем наложить текстуры на грани куба и получить некое подобие игральной кости.
Итак, возьмем за основу проект из предыдущей статьи, и для начала нужно добавить в него изображения, на базе которых впоследствии мы создадим текстуры. Я не стал особо заморачиваться, просто нарисовал по-быстрому в paint'е 6 картинок, по одной для каждой грани куба. Получилось вот так:
Тем, кто как и я, использует Qt Creator теперь необходимо добавить эти файлы в проект. Для этого нужно создать пустой файл ресурсов и прописать в нем пути для всех используемых изображений. Итак, картинки готовы, задача понятна, давайте приступать к реализации. И первым делом нам необходимо, собственно, создать текстуры из изображений. Добавим в проект функцию generateTextutes():
void MainScene::generateTextures() { glGenTextures(6, textures); QImage currentTexture; currentTexture.load(":/cubeOne.jpg"); currentTexture = QGLWidget::convertToGLFormat(currentTexture); glBindTexture(GL_TEXTURE_2D, textures[0]); glTexImage2D(GL_TEXTURE_2D, 0, 3, (GLsizei)currentTexture.width(), (GLsizei)currentTexture.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, currentTexture.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); // Далее генерация остальных текстур...
Я решил привести в качестве примера код для создания только одной OpenGL текстуры для одной грани. Для остальных граней все делается абсолютно также. В конце статьи я обязательно выложу полный проект с примером, и там можно будет посмотреть весь код.
Давайте разбираться, что тут происходит...
Первым делом вызываем функцию 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. В нашем примере текстура квадратная, значит она будет иметь такие координаты:
Рассмотрим код для наложения текстуры на первую (переднюю) грань куба. Для остальных граней все делается аналогично:
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 на нашем сайте, так что не проходите мимо!
Вот полный проект с примером программы - OpenGLTest_texture.
А можно ли каким-нибудь образом динамически выводить на текстуру текст, задаваемый пользователем?
Можно попробовать как-нибудь так - генерировать текстуру с текстом и накладывать на исходную текстуру. Хотя возможно есть даже какие-нибудь стандартные средства OpenGL для этого.
Где то в коде вероятно ошибка. Покрутил кубик секунд 20, так он съел ОЗУ почти 1,5 ГБ и вылетел. Есть скрин.
Там просто генерацию текстур надо вынести в функцию initializeGL(), потому что это надо делать только один раз, я сейчас перезалью проект.
Все еще разбираюсь с кодом. Перетащил все, что касается куба, в отдельный класс. Еще вытащил из функции отрисовки инициализацию вершин. Она нужна только один раз. В mainScene в функции paintGL вызываю только cubic->paint(). Все работает. Теперь хочется сделать отрисовку всего куба только ОДНИМ вызовом DrawElements(). Вы 6 раз инициализируете нулевой элемент массива cubeIndexArray[0][х]. Хотя у него размерность 6. В предыдущем уроке этот же массив был полностью определен. Не могу понять, что делать с cubeTextureArray и что делает функция glBindTexture(GL_TEXTURE_2D, textures[0]);
glBindTexture(GL_TEXTURE_2D, textures[0]) как бы выбирает нужную текстуру, делает ее активной для работы. Поэтому тут так просто не получится сделать отрисовку одним вызовом DrawElements. Зато можно сделать текстуру одной картинкой и тогда это сработает. В массив cubeTextureArray тогда для индексов, соответствующих разным граням нужно будет записывать нужные координаты одной текстуры.
Полагаю, именно из-за того, что отдельно рисуются 6 граней, возникают артефакты в виде белых щелей на ребрах куба (видно даже на картинках).
аа, нет) это обрезка в Paint'e такая) это не артефакты! Но все равно, хочется сделать отрисовку одним вызовом.
Жаль, что больше нет статей по OpenGL. =(