Top.Mail.Ru

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

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

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

Пример наложения текстур в OpenGL.

Тем, кто как и я, использует 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. В нашем примере текстура квадратная, значит она будет иметь такие координаты:

Массив вершин и текстур в 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.

Подписаться
Уведомить о
guest

9 комментариев
Старые
Новые
Межтекстовые Отзывы
Посмотреть все комментарии
sva
sva
9 лет назад

А можно ли каким-нибудь образом динамически выводить на текстуру текст, задаваемый пользователем?

sva
sva
9 лет назад

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

sva
sva
9 лет назад

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

sva
sva
9 лет назад

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

sva
sva
9 лет назад

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

jimka
jimka
5 лет назад

Жаль, что больше нет статей по OpenGL. =(

9
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x