Как наложить текстуру на куб opengl
Перейти к содержимому

Как наложить текстуру на куб opengl

  • автор:

как добавить на куб разные текстуры

Здравствуйте. Я хочу нарисовать куб в openGL, у которого на каждой грани — своя текстура. Вот такой вот рабочий пример куба у которого одна текстура используется для обтягивания всех его граней:

public class Cube extends Abstract3DFigure < private FloatBuffer vertexBuffer; private FloatBuffer textureBuffer; private ByteBuffer indexBuffer; private int[] textures = new int[6]; private float vertices[] = < //Vertices according to faces -1.0f, -1.0f, 1.0f, //Vertex 0 1.0f, -1.0f, 1.0f, //v1 -1.0f, 1.0f, 1.0f, //v2 1.0f, 1.0f, 1.0f, //v3 1.0f, -1.0f, 1.0f, //. 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, >; private float texture[] = < //Mapping coordinates for the vertices 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, >; private byte indices[] = < //Faces definition 0, 1, 3, 0, 3, 2, //Face front 4, 5, 7, 4, 7, 6, //Face rightButtonPositiion 8, 9, 11, 8, 11, 10, //. 12, 13, 15, 12, 15, 14, 16, 17, 19, 16, 19, 18, 20, 21, 23, 20, 23, 22, >; public Cube(Context context) < ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4); byteBuf.order(ByteOrder.nativeOrder()); vertexBuffer = byteBuf.asFloatBuffer(); vertexBuffer.put(vertices); vertexBuffer.position(0); byteBuf = ByteBuffer.allocateDirect(texture.length * 4); byteBuf.order(ByteOrder.nativeOrder()); textureBuffer = byteBuf.asFloatBuffer(); textureBuffer.put(texture); textureBuffer.position(0); indexBuffer = ByteBuffer.allocateDirect(indices.length); indexBuffer.put(indices); indexBuffer.position(0); >public void draw(GL10 gl) < gl.glPushMatrix(); gl.glTranslatef(0, 0, -10); //Move 5 units into the screen gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); //Point to our buffers gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); //Set the face rotation gl.glFrontFace(GL10.GL_CCW); //Enable the vertex and texture state gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer); //Draw the vertices as triangles, based on the Index Buffer information gl.glDrawElements(GL10.GL_TRIANGLES, indices.length, GL10.GL_UNSIGNED_BYTE, indexBuffer); gl.glPopMatrix(); >public void loadGLTexture(GL10 gl, Context context) < Bitmap bitmap = loadResurse(context, R.drawable.border); //Generate one texture pointer. gl.glGenTextures(1, textures, 0); //. and bind it to our array gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); //Create Nearest Filtered Texture gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); //Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT); //Use the Android GLUtils to specify a two-dimensional texture image from our bitmap GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); //Clean upButtonPositiion bitmap.recycle(); >private Bitmap loadResurse(Context context, int resourceId) < InputStream is = context.getResources().openRawResource(resourceId); Bitmap bitmap = null; try < //BitmapFactory is an Android graphics utility for images bitmap = BitmapFactory.decodeStream(is); >finally < //Always clear and close try < is.close(); >catch (IOException e) < >is = null; > return bitmap; > > 

Мне бы хотелось использовать для разных граней — разные текстуры. В данном коде я гружу только одну текстуру. Bitmap bitmap = loadResurse(context, R.drawable.border) ;
А нужно 6 текстур, я могу их получить таким образом, но как потом их натянуть на разные грани мне не понятно.

Bitmap east = loadResurse(context, R.drawable.east); Bitmap north = loadResurse(context, R.drawable.north); Bitmap west = loadResurse(context, R.drawable.west); Bitmap south = loadResurse(context, R.drawable.south); Bitmap up = loadResurse(context, R.drawable.up); Bitmap down = loadResurse(context, R.drawable.down); 

Как я могу это сделать ?

Урок 5: Текстурированный куб

Добро пожаловать на наш пятый урок. В этом уроке вы узнаете:

  • Что такое UV-координаты
  • Как самостоятельно загружать текстуры
  • Как использовать их в OpenGL
  • Что такое фильтрация и мип-маппинг и как их использовать
  • Как загружать текстуры с помощью GLFW
  • Что такое Alpha-канал

UV-координаты

Когда вы текстурируете какой-то объект, то вам необходимо как-то сообщить OpenGL, какая часть изображения прикрепляется к каждому треугольнику. Именно для этого и используются UV-координаты

Каждая вершина помимо позиции имеет несколько дополнительных полей, а также U и V. Эти координаты используются применительно к текстуре, как показано на рисунке:

Обратите внимание, как текстура искажается на треугольнике.

Загрузка Bitmap-изображений

Знание формата файлов BMP не является критичным, так как многие библиотеки могут сделать загрузку за вас. Однако, чтобы лучше понимать то, что происходит в таких библиотеках мы разберем ручную загрузку.

Объявляем функцию для загрузки изображений:

 GLuint loadBMP_custom(const char * imagepath); 

Вызываться она будет так:

 GLuint image = loadBMP_custom("./my_texture.bmp"); 

Теперь перейдем непосредственно к чтению файла.

Для начала, нам необходимы некоторые данные. Эти переменные будут установлены когда мы будем читать файл:

 // Данные, прочитанные из заголовка BMP-файла unsigned char header[54]; // Каждый BMP-файл начинается с заголовка, длиной в 54 байта unsigned int dataPos; // Смещение данных в файле (позиция данных) unsigned int width, height; unsigned int imageSize; // Размер изображения = Ширина * Высота * 3 // RGB-данные, полученные из файла unsigned char * data; 
 FILE * file = fopen(imagepath,"rb"); if (!file)

Первым, в BMP-файлах идет заголовок, размером в 54 байта. Он содержит информацию о том, что файл действительно является файлом BMP, размер изображение, количество бит на пиксель и т. п., поэтому читаем его:

 if ( fread(header, 1, 54, file) != 54 ) < // Если мы прочитали меньше 54 байт, значит возникла проблема printf("Некорректный BMP-файлn"); return false; >

Заголовок всегда начинается с букв BM. Вы можете открыть файл в HEX-редакторе и убедиться в этом самостоятельно, а можете посмотреть на наш скриншот:

Итак, мы проверяем первые два байта и если они не являются буквами “BM”, то файл не является BMP-файлом или испорчен:

 if ( header[0]!='B' || header[1]!='M' )

Теперь мы читаем размер изображения, смещение данных изображения в файле и т. п.:

 // Читаем необходимые данные dataPos = *(int*)&(header[0x0A]); // Смещение данных изображения в файле imageSize = *(int*)&(header[0x22]); // Размер изображения в байтах width = *(int*)&(header[0x12]); // Ширина height = *(int*)&(header[0x16]); // Высота 

Проверим и исправим полученные значения:

 // Некоторые BMP-файлы имеют нулевые поля imageSize и dataPos, поэтому исправим их if (imageSize==0) imageSize=width*height*3; // Ширину * Высоту * 3, где 3 - 3 компоненты цвета (RGB) if (dataPos==0) dataPos=54; // В таком случае, данные будут следовать сразу за заголовком 

Теперь, так как мы знаем размер изображения, то можем выделить область памяти, в которую поместим данные:

 // Создаем буфер data = new unsigned char [imageSize]; // Считываем данные из файла в буфер fread(data,1,imageSize,file); // Закрываем файл, так как больше он нам не нужен fclose(file); 

Следует отметить, что приведенный код может быть использован только для загрузки 24-битных изображений (т. е. где на каждый пиксель изображения отводится 3 байта). С другими форматами BMP-файла вам следует познакомиться самостоятельно.

Мы вплотную подошли к части, касающейся OpenGL. Создание текстур очень похоже на создание вершинных буферов:

  • Создайте текстуру
  • Привяжите ее
  • Заполните
  • Сконфигурируйте

GL_RGB в glTextImage2D указывает на то, что мы работает с 3х компонентным цветом. А GL_BGR указывает на то, как данные представлены в памяти. На самом деле в BMP-файлах цветовые данные хранятся не в RGB, а в BGR (если быть точным, то это связано с тем, как хранятся числа в памяти), поэтому необходимо сообщить об этом OpenGL:

 // Создадим одну текстуру OpenGL GLuint textureID; glGenTextures(1, &textureID); // Сделаем созданную текстуру текущий, таким образом все следующие функции будут работать именно с этой текстурой glBindTexture(GL_TEXTURE_2D, textureID); // Передадим изображение OpenGL glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0, GL_BGR, GL_UNSIGNED_BYTE, data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 

Последние две строки мы поясним позднее, а пока в части C++ мы должны использовать нашу функцию для загрузки текстуры:

 GLuint Texture = loadBMP_custom("uvtemplate.bmp"); 

**Очень важное замечание: **используйте текстуры с шириной и высотой степени двойки! То есть:

  • Хорошие: 128128, 256256, 10241024, 2*2…
  • Плохие: 127128, 35, …
  • Приемлемые: 128*256

Использование текстуры в OpenGL

Что же, давайте посмотрим на наш Фрагментный шейдер:

 #version 330 core // Интерполированные значения из вершинного шейдера in vec2 UV; // Выходные данные out vec3 color; // Значения, которые остаются неизменными для объекта. uniform sampler2D myTextureSampler; void main() < // Выходной цвет = цвету текстуры в указанных UV-координатах color = texture( myTextureSampler, UV ).rgb; >
  • Фрагментному шейдеру требуются UV-координаты. Это понятно.
  • Также, ему необходим “sampler2D”, чтобы знать, с какой текстурой работать (вы можете получить доступ к нескольким текстурам в одном шейдере т. н. мультитекстурирование)
  • И наконец, доступ к текстуре завершается вызовом texture(), который возвращает vec4 (R, G, B, A). A-компоненту мы разберем немного позднее.

Вершинный шейдер также прост. Все, что мы делаем — это передаем полученные UV-координаты в фрагментный шейдер:

 #version 330 core // Входные данные вершин, различные для всех запусков этого шейдера layout(location = 0) in vec3 vertexPosition_modelspace; layout(location = 1) in vec2 vertexUV; // Выходные данные, которые будут интерполированы для каждого фрагмента out vec2 UV; // Значения, которые останутся неизменными для всего объекта uniform mat4 MVP; void main() < // Выходная позиция вершины gl_Position = MVP * vec4(vertexPosition_modelspace,1); // UV-координаты вершины. UV = vertexUV; >

Помните “layout(location = 1) in vec3 vertexColor” из Урока 4? Здесь мы делаем абсолютно тоже самое, только вместо передачи буфера с цветом каждой вершины мы будем передавать буфер с UV-координатами каждой вершины:

 // Две UV-координаты для каждой вершины. Они были созданы с помощью Blender. Мы коротко расскажем о том, как сделать это самостоятельно. static const GLfloat g_uv_buffer_data[] = < 0.000059f, 1.0f-0.000004f, 0.000103f, 1.0f-0.336048f, 0.335973f, 1.0f-0.335903f, 1.000023f, 1.0f-0.000013f, 0.667979f, 1.0f-0.335851f, 0.999958f, 1.0f-0.336064f, 0.667979f, 1.0f-0.335851f, 0.336024f, 1.0f-0.671877f, 0.667969f, 1.0f-0.671889f, 1.000023f, 1.0f-0.000013f, 0.668104f, 1.0f-0.000013f, 0.667979f, 1.0f-0.335851f, 0.000059f, 1.0f-0.000004f, 0.335973f, 1.0f-0.335903f, 0.336098f, 1.0f-0.000071f, 0.667979f, 1.0f-0.335851f, 0.335973f, 1.0f-0.335903f, 0.336024f, 1.0f-0.671877f, 1.000004f, 1.0f-0.671847f, 0.999958f, 1.0f-0.336064f, 0.667979f, 1.0f-0.335851f, 0.668104f, 1.0f-0.000013f, 0.335973f, 1.0f-0.335903f, 0.667979f, 1.0f-0.335851f, 0.335973f, 1.0f-0.335903f, 0.668104f, 1.0f-0.000013f, 0.336098f, 1.0f-0.000071f, 0.000103f, 1.0f-0.336048f, 0.000004f, 1.0f-0.671870f, 0.336024f, 1.0f-0.671877f, 0.000103f, 1.0f-0.336048f, 0.336024f, 1.0f-0.671877f, 0.335973f, 1.0f-0.335903f, 0.667969f, 1.0f-0.671889f, 1.000004f, 1.0f-0.671847f, 0.667979f, 1.0f-0.335851f >; 

Указанные UV-координаты относятся к такой модели:

Остальное очевидно. Мы создаем буфер, привязываем его, заполняем, настраиваем и выводим Буфер Вершин как обычно. Только будьте осторожны, так как в glVertexAttribPointer для буфера текстурных координат второй параметр (размер) будет не 3, а 2.

И вот такой результат мы получим:

в увеличенном варианте:

Фильтрация и мип-маппинг.

Как вы можете видеть на скриншоте выше, качество текстуры не очень хорошее. Это потому, что в нашей процедуре загрузки BMP-изображения (loadBMP_custom) мы указали:

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

Это означает, что в нашем фрагментном шейдере, texture() возвращает строго тексель, который находится по указанным текстурным координатам:

Есть несколько решений, которые позволят улучшить ситуацию.

При помощи линейной фильтрации texture() будет смешивать цвета находящихся рядом текселей в зависимости от дистанции до их центра, что позволит предотвратить резкие границы, которые вы видели выше:

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

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

И линейная, и анизотропная фильтрация имеют недостаток. Если текстура просматривается с большого расстояния, то смешивать 4 текселя будет недостаточно. То есть, если ваша 3D модель находится так далеко, что занимает на экране всего 1 фрагмент, то фильный цвет фрагмента будет являться средним всех текселей текстуры. Естественно, это не реализовано из-за соображений производительности. Для этой цели существует так называемый мип-маппинг:

  • При инициализации вы уменьшаете масштаб текстуры до тех пор, пока не получите изображение 1х1 (которое по сути будет являться средним значением всех текселей текстуры)
  • Когда вы выводите объект, то вы выбираете тот мип-мап, который наиболее приемлем в данной ситуации.
  • Вы применяете к этому мип-мапу фильтрацию
  • А для большего качества вы можете использовать 2 мип-мапа и смешать результат.

К счастью для нас, все это делается очень просто с помощью OpenGL:

 // Когда изображение увеличивается, то мы используем обычную линейную фильтрацию glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Когда изображение уменьшается, то мы используем линейной смешивание 2х мипмапов, к которым также применяется линейная фильтрация glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // И генерируем мипмап glGenerateMipmap(GL_TEXTURE_2D); 

Загрузка текстур с помощью GLFW

Наша процедура loadBMP_custom великолепна, так как мы сделали ее сами, но использование специальных библиотек может быть предпочтительнее (в конечном итоге мы в своей процедуре многое не учли). GLFW может сделать это лучше (но только для TGA-файлов):

 GLuint loadTGA_glfw(const char * imagepath) < // Создаем одну OpenGL текстуру GLuint textureID; glGenTextures(1, &textureID); // "Привязываем" только что созданную текстуру и таким образом все последующие операции будут производиться с ней glBindTexture(GL_TEXTURE_2D, textureID); // Читаем файл и вызываем glTexImage2D с необходимыми параметрами glfwLoadTexture2D(imagepath, 0); // Трилинейная фильтрация. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glGenerateMipmap(GL_TEXTURE_2D); // Возвращаем идентификатор текстуры который мы создали return textureID; >

Сжатые текстуры

На этом шаге вы наверное хотите узнать, как же все-таки загружать JPEG файлы вместо TGA?

Короткий ответ: даже не думайте об этом. Есть идея получше.

##Создание сжатых текстур

  • Скачайте The Compressonator, утилита от ATI
  • Загрузите в нее текстуру, размер которой является степенью двойки
  • Сожмите ее в DXT1, DXT3 или в DXT5 (о разнице между форматами можете почитать на Wikipedia)

  • Создайте мипмапы, чтобы не создавать их во время выполнения программы.
  • Экспортируйте это как .DDS файл

После этих шагов вы имеете сжатое изображение, которое прямо совместимо с GPU. И когда вы вызовите texture() в шейдере, то текстура будет распакована на лету. Это может показаться более медленным, однако это требует гораздо меньше памяти, а значит пересылаемых данных будет меньше. Пересылка данных всегда будет дорогой операцией, в то время как декомпрессия является практически бесплатной. Как правило, использование сжатия текстур повышает быстродействие на 20%.

##Использование сжатой текстуры

Теперь перейдем непосредственно к загрузке нашей сжатой текстуры. Процедура будет очень похожа на загрузку BMP, с тем исключением, что заголовок файла будет организован немного иначе:

 GLuint loadDDS(const char * imagepath) < unsigned char header[124]; FILE *fp; /* пробуем открыть файл */ fp = fopen(imagepath, "rb"); if (fp == NULL) return 0; /* проверим тип файла */ char filecode[4]; fread(filecode, 1, 4, fp); if (strncmp(filecode, "DDS ", 4) != 0) < fclose(fp); return 0; >/* читаем заголовок */ fread(&header, 124, 1, fp); unsigned int height = *(unsigned int*)&(header[8 ]); unsigned int width = *(unsigned int*)&(header[12]); unsigned int linearSize = *(unsigned int*)&(header[16]); unsigned int mipMapCount = *(unsigned int*)&(header[24]); unsigned int fourCC = *(unsigned int*)&(header[80]); 

После заголовку идут данные, в которые входят все уровни мип-мап. К слову, мы можем прочитать их все сразу:

 unsigned char * buffer; unsigned int bufsize; /* вычисляем размер буфера */ bufsize = mipMapCount > 1 ? linearSize * 2 : linearSize; buffer = (unsigned char*)malloc(bufsize * sizeof(unsigned char)); fread(buffer, 1, bufsize, fp); /* закрываем файл */ fclose(fp); 

Сделано. Так как мы можем использовать 3 разных формата (DXT1, DXT3, DXT5), то необходимо в зависимости от флага “fourCC”, сказать OpenGL о формате данных.

 unsigned int components = (fourCC == FOURCC_DXT1) ? 3 : 4; unsigned int format; switch(fourCC)

Создание текстуры выполняется как обычно:

 // Создаем одну OpenGL текстуру GLuint textureID; glGenTextures(1, &textureID); // "Привязываем" текстуру. glBindTexture(GL_TEXTURE_2D, textureID); 

Следующим шагом мы загружаем мип-мапы:

 unsigned int blockSize = (format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 8 : 16; unsigned int offset = 0; /* загрузка мип-мапов */ for (unsigned int level = 0; level < mipMapCount && (width || height); ++level) < unsigned int size = ((width+3)/4)*((height+3)/4)*blockSize; glCompressedTexImage2D(GL_TEXTURE_2D, level, format, width, height, 0, size, buffer + offset); offset += size; width /= 2; height /= 2; >free(buffer); return textureID; 

DXT компрессия пришла к нам из DirectX, где координатная текстура V является инвертированной по сравнению с OpenGL. Поэтому, если вы используете сжатые текстуры, то вам необходимо использовать (coord.u, 1.0 — coord.v), чтобы исправить тексель. Вы можете выполнять это как при экспорте текстуры, так и в загрузчике или в шейдере.

Заключение

В данном уроке вы узнали как создавать, загружать и использовать текстуры в OpenGL.

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

Упражнения

  • В исходный код к урокам включен загрузчик DDS, но без исправления текстурных координат. Модифицируйте код так, чтобы корректно выводить куб.
  • Поэкспериментируйте с разными DDS форматами. Дают ли они разный результат или разную степень сжатия?
  • Попробуйте не создавать мип-мапы в The Compressonator. Каков результат? Создайте 3 пути решения этих проблем.

Полезные ссылки

  • Using texture compression in OpenGL , Sébastien Domine, NVIDIA

Уроки по OpenGL с сайта OGLDev

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

Для наложения текстур необходимо сделать 3 вещи: загрузить текстуру в OpenGL, предоставить координаты текстуры вместе с вершинами (для наложения текстуры согласно с ними), и для получения цвета пикселя совершить некоторые операции. Так как треугольники масштабируют, вращают, перемещают и, наконец, проецируют, то они могут иметь множество различных видов в зависимости от их ориентации относительно камеры. Все что должен сделать GPU, это сопоставить текстуру с вершинами треугольника так, что бы это выглядело реалистично (если текстура «поплывет» по треугольнику, то эффект потеряется). Для этого программист должен поставлять набор координат, известных как «координаты текстуры» для каждой вершины. Когда GPU растеризирует треугольник, то он интерполирует координаты текстуры по поверхности треугольника, и в фрагментном шейдере эти координаты соотносятся с текстурой. Это действие называется «выборкой», и результат выбора — это тексел (пиксель в текстуре). Тексел часто хранит часто хранит цвет, который будет использоваться в отрисовке соответствующего пикселя на экране. В этом уроке мы увидим, что тексел может хранить различные типы данных, которые могут быть использованы для множества эффектов.

OpenGL предоставляет несколько типов текстур, таких как 1D, 2D, 3D и т.д., которые могут быть использованы в различных ситуациях. У 2D текстуры есть ширина и высота, которые указываются любым положительным целым числом. Умножив ширину на высоту получим количество текселей в текстуре. -Так вы указываете координаты текселя для вершины? -Нет, не совсем. Их будет слишком много, и если потребуется изменить текстуру на другую, имеющую отличные размеры, тогда придется перестроить все вершины. Идея в том, что бы была возможность менять текстуру, не изменяя ее координат. Поэтому координаты текстуры указываются в нормированном отрезке [0,1]. Это значит, что координаты текстур обычно дроби, и умножив эту дробь на ширину / высоту мы получим координаты текселя. Например, если координаты [0.5,0.1], и высота текстуры 320, а ширина 240, то координаты текселя (160,20) (0.5 * 320 = 160 и 0.1 * 200 = 20).

Обычно для текстур используют U и V оси, где U соответствует X и V — Y. OpenGL рассматривает значения UV осей слева направо для U оси снизу вверх для V. Посмотрим на следующее изображение:

Это изображение показывает пространство текстуры, и как вы можете заметить, начало координат в левом нижнем углу. U возрастает направо, а V — вверх. Теперь посмотрим на треугольник, чьи координаты указаны на изображении:

Теперь предположим, что мы хотим использовать эту текстуру и эти координаты; в данном случае мы получим рисунок с домиком в положении выше. Пусть треугольник прошел через различные преобразования, и когда пришло время для его растеризации, он выглядит так:

Как вы видите, координаты текстуры «приклеены» к вершине, они центральные атрибуты и не изменяются во время преобразований. Во время интерполяции координаты текстур, в большинстве пикселей, получают такие же координаты текстур как и на оригинальном изображении (потому, что их положение не изменилось относительно вершин), и не смотря на то, что треугольник повернут, координаты его текстуры не изменились. Это значит, что текстура полностью следует за треугольником. Заметим, что существуют техники для контролирования перемещения текстуры по поверхности треугольника в требуемом направлении, но сейчас наши координаты останутся без изменений.

Другой важный момент в наложении текстур — это ‘фильтрация’. Мы уже говорили о том как получается тексел. Позиция текселя всегда задается в указанном промежутке, но что произойдет, если наши координаты текстуры (запомните — они должны быть в отрезке от 0 до 1) приведут нас к текселю в (152.34,745.14)? Обычно отвечают, что они округлятся до (152,745). Да, это будет работать, и даже даст адекватный результат, но в некоторых случаях будет выглядеть не очень хорошо. Лучше взять 2 на 2 текселя ( (152,745), (153,745), (152,744) и (153,744) ) и совершить некую линейную интерполяцию между их цветами. Эта интерполяция должна сохранить относительное расстояние между (152.34,745.14) и каждым текселем. Чем ближе координата к текселю, тем большее влияние он получит, и наоборот, чем он дальше, тем влияние будет меньше. Это гораздо лучше чем оригинальный подход.

Метод, который выбирает итоговый тексел так же известен как ‘фильтрация’. Простейший подход к округлению координат текстуры известен как ‘ближайшая фильтрация’, а более сложный способ — ‘линейная фильтрация’. Другое название для ближайшей фильтрации — ‘точечная фильтрация’. OpenGL предоставляет несколько типов, и вас есть возможность выбирать. Обычно фильтр, который дает лучший результат, требует больших вычислений от GPU и может повлиять на частоту кадров. Выбор фильтра зависит от качества требуемого результата а так же от способностей целевой платформы.

Теперь, когда мы поняли принцип координат текстуры, пришло время рассмотреть как же реализовано наложение текстур в OpenGL. Текстурирование в OpenGL означает взаимодействие с 4-мя сложно запутанными понятиями: объект текстуры, модуль текстур, сэмплер объекта и uniform-сэмплер в шейдере.

Объект текстуры хранит данные о самом изображении текстуры, иначе говоря, тексели. Текстуры могут быть нескольких типов (1D, 2D и т.д.) с различными размерами, а так же формат данных может различаться (RGB, RGBA и т.д). OpenGL предлагает несколько способов для указания точки начала данных в памяти, типов текстур и загрузки данных в GPU. Существует множество параметров для большего контроля такие как вид фильтра и т.д. Очень схож с вершинным буфером объект текстуры, который то же ассоциируется с указателем. После создания указателя и загрузки данных и параметров вы запросто можете назначать другие указатели на лету, просто передав в состояние OpenGL новую текстуру. Вам больше не требуется загружать данные вновь. С этого момента проверка загружены ли данные в GPU до начала рендера — работа для драйвера OpenGL.

Объект текстуры не обязательно перейдет прямо в шейдер (где фактически находится сэмплер). Вместо этого он перейдет в ‘модуль текстур’, индексы которого передаются в шейдер. Таким образом, шейдер получает текстуру через модуль текстур. Обычно доступно сразу несколько модулей текстур, количество которых зависит от вашей видеокарты. Для того, что бы привязать объект текстуры A к модулю 0 необходимо сначала активировать модуль 0, а затем и привязать объект текстуры A. Вы можете активировать модуль текстур 1 и привязать другой (или даже тот же самый) объект текстуры к нему. Модуль 0 останется привязан к текстуре A.

Возникает небольшая сложность с тем фактом, что каждый модуль текстур на самом деле имеет место для нескольких текстур одновременно из-за того, что текстуры бывают нескольких типов. Это называется ‘позицией’ объекта текстур. Когда мы привязываем объект к модулю, то мы указываем его позицию (1D, 2D и т.д.). Поэтому вы можете иметь привязанный объект A к позиции 1D, а объект B к 2D одного итого же модуля.

Операция выбора (обычно) находится внутри фрагментного шейдера, и для этого существует специальная функция. Функция выбора должна знать модуль текстуры для доступа, поскольку вы можете выбирать из нескольких модулей текстур в фрагментном шейдере. Для этого используется специальная uniform-переменная, согласно позиции текстуры: ‘sampler1D’, ‘sampler2D’, ‘sampler3D’, ‘samplerCube’ и другие. Вы можете создать столько uniform-переменных, сколько захотите и назначить значение модуля текстур для каждой напрямую из приложения. Каждый раз, когда вы вызываете функцию выбора на сэмплер uniform-переменной, соответствующий модуль текстур (а вместе с ним и объект текстур) будет использован.

И последнее понятие — это сэмплер объекта. Не путайте его с сэмплером uniform-переменной! Это разные понятия. Идея в том, что объект текстуры хранит и данные текстуры и параметры, которые настраивают операцию выбора. Эти параметры часть состояния сэмплера. Хотя, вы также можете создать сэмплер объекта, настроить его и привязать к модулю текстуры. Когда вы это сделаете, сэмплер объекта перезапишет все другие состояния сэмплера, определенные в объекте текстур. Не волнуйтесь, пока что мы не будем использовать сэмплеры объектов, но все же лучше знать об их существовании.

Следующая диаграмма подводит итог ко всему, что мы изучили относительно связи между понятиями текстур:

Прямиком к коду!

OpenGL знает как получить данные текстуры в различных форматах из памяти, но не предоставляет никаких способов для загрузки текстур в память из файлов изображений таких как PNG или JPG. Нам потребуется дополнительная внешняя библиотека что бы сделать это. Их существует большое множество, но мы будем использовать ImageMagick, свободная библиотека, поддерживающая множество типов изображений, и кроме того, она кроссплатформеная. Если вы используете Ubuntu, вы можете легко установить через ‘apt-get install libmagick++-dev’. Если у вам другой Linux дистрибутив, используйте свой менеджер пакетов или скачайте исходники и соберите библиотеку самостоятельно.

Большинство указателей на текстуры инкапсулированы в следующем классе:

class Texture < public: Texture(GLenum TextureTarget, const std::string& FileName); bool Load(); void Bind(GLenum TextureUnit); >; 

Во время создания объекта текстуры вам необходимо указать позицию (мы используем GL_TEXTURE_2D) и имя файла. После вы можете вызвать функцию Load(). Она может вернуть код ошибки если, например, файл не существует, или если ImageMagick получит другие виды ошибок. encountered any other error. Если вы хотите использовать конкретный экземпляр текстуры, вы должны привязать его к одному из текстурных модулей.

try < m_pImage = new Magick::Image(m_fileName); m_pImage->write(&m_blob, "RGBA"); > catch (Magick::Error& Error)

Вот так мы используем ImageMagick для загрузки из файла и подготовки памяти для загрузки в OpenGL. Мы начинаем с инициализации свойства класса типа Magic::Image используя имя файла текстуры. Этот вызов загружает текстуру в память, которая задана private и не может быть напрямую использована OpenGL. Затем мы записываем изображение в объект Magick::Blob используя формат RGBA (красный, зеленый, синий и альфа канал). BLOB (большой бинарный объект) — это полезный механизм для хранения зашифрованного изображения в память так, что оно может быть использовано сторонними программами. Если будут какие-либо ошибки, то будет брошено исключение, поэтому мы должны быть готовы для него.

glGenTextures(1, &m_textureObj); 

Эта функция OpenGL очень похожа на glGenBuffers(), с которой мы уже хорошо знакомы. Она генерирует указанное число объектов текстур и помещает их в указатель на массив GLuint (второй параметр). В нашем случае нам потребуется только 1 объект.

glBindTexture(m_textureTarget, m_textureObj); 

Мы собираемся сделать несколько вызовов, связанных с текстурой, и в похожей на буфер вершин манере, OpenGL должен знать, с каким объектом текстур работать. Эта цель функции glBindTexture(). Она сообщает OpenGL объект текстуры, который относится ко всем вызовам, связанным с текстурами, до тех пор, пока новый объект текстур не будет передан. В дополнении к указателю (второй параметр) мы также указываем позицию текстуры, которая может принимать значения GL_TEXTURE_1D, GL_TEXTURE_2D и т.д. Вполне можно использовать различные типы объектов текстур для каждой из позиций одновременно. В нашем случае позиция — это часть конструктора (сейчас мы используем GL_TEXTURE_2D).

glTexImage2D(m_textureTarget, 0, GL_RGBA, m_pImage->columns(),m_pImage->rows(), 0, GL_RGBA, GL_UNSIGNED_BYTE, m_blob.data()); 

Гораздо более сложная функция для загрузки главной части объекта текстуры, что по сути, сами данные текстуры. Существует несколько функций glTexImage*, доступных для каждой позиции текстуры. Позиция всегда первый параметр. Второй — это LOD или уровень детализации (Level-Of-Detail). Объект текстуры может хранить одну и ту же текстуру в различном разрешении, понятие, известное как mip-отображение (mip — «много в одном»). Каждое mip-отображение имеет различный коэффициент LOD, 0 для максимального качества, и с увеличением качество падает. Пока что мы имеем только 1 mip-отображение, поэтому мы передаем 0.

Следующий параметр — внутренний формат, в котором OpenGL хранит текстуру. Для примера, вы можете передать текстуру со всеми 4 каналами (красный, зеленый, голубой и альфа), но если вы укажете GL_RED, то вы получите текстуру только с красным каналом, что выглядит довольно … красно (попробуйте это!). Мы используем GL_RGBA для получения всех цветов текстуры. Следующие 2 параметра ширина и высота текстуры в текселях. ImageMagick сохраняет эту информацию для нас когда загружает изображение, и мы получаем эти данные через функции Image::columns()/rows(). Пятый параметр — рамка, которую мы оставим равной 0.

Последние 3 параметра указывают источник входящих данных текстуры. Это формат, тип и адрес в памяти. Формат указывает количество каналов, которые должны соответствовать значению из BLOB. Тип определяет вид данных относительно каждого канала. OpenGL поддерживает множество типов данных, но в ImageMagick BLOB имеет только 1 байт на канал, поэтому мы используем GL_UNSIGNED_BYTE. Наконец, мы указываем адрес данных, которые извлекаются из BLOB’а через функцию Blob::data().

glTexParameterf(m_textureTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(m_textureTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 

Широкая функция glTexParameterf управляет многими аспектами операции выборки текстур. Эти аспекты — часть состояния сэмплера текстуры. Здесь мы указываем фильтры, которые будут использованы для увеличения и минимализации. Каждая текстура имеет заданные ширину и высоту, но очень редко они совпадают с пропорциями треугольника. В большинстве случаев треугольник больше или меньше чем текстура. В этом случае тип фильтра определяет как именно увеличить или уменьшить текстуру для совпадения пропорций. Если треугольник, проходящий растеризацию, больше чем текстура (например очень близок к камере), то у нас некоторые пиксели будут использовать один текстел. А если меньше (очень далеко от камеры) сразу несколько текселей используются для одного пикселя. Мы выбрали фильтр линейной интерполяции для обоих случаев. Как мы уже видели ранее, линейная интерполяция дает хороший результат путем смешивания цвета 2×2 текселя основываясь на текущей позиции текселя (вычисляется путем масштабирования координат текстуры ее размерами).

void Texture::Bind(GLenum TextureUnit)

Так как наше 3D приложение постоянно разрастается, мы возможно захотим использовать множество различных текстур во множестве вызовов отрисовки в функции рендера. Прежде чем делать любой вызов мы должны привязать объект текстур, а так же разрешить использование конкретного модуля текстур, что бы она была доступна в фрагментном шейдере. Эта функция принимает модуль текстуры как параметр типа enum (GL_TEXTURE0, GL_TEXTURE1 и т.д.). Тем самым он станет активным через glActiveTexture() и затем привязываем объект текстур к модулю. Связь будет до тех пор, пока для этого модуля не будет вызвана Texture::Bind() для другой текстуры.

layout (location = 0) in vec3 Position; layout (location = 1) in vec2 TexCoord; uniform mat4 gWVP; out vec2 TexCoord0; void main() < gl_Position = gWVP * vec4(Position, 1.0); TexCoord0 = TexCoord; >; 

Это обновленный вершинный шейдер. Мы добавили еще один входной параметр, названный TexCoord, являющийся двумерным вектором. Вместо вывода цвета шейдер теперь передает координаты текстуры, причем без изменений. Растеризатор интерполирует координаты текстуры по поверхности треугольника и каждый фрагментный шейдер будет вызван со своим значением координат текстуры.

in vec2 TexCoord0; out vec4 FragColor; uniform sampler2D gSampler; void main() < FragColor = texture2D(gSampler, TexCoord0.st); >; 

А это новый фрагментный шейдер. У него входящая переменная, названная TexCoord0, которая содержит интерполированые координаты текстуры, полученные из вершинного шейдера. Так же у нас новая uniform-переменная, названная gSampler, типа sampler2D. Это пример сэмплера uniform-переменной. Приложению требуется задать значение модуля текстуры в эту переменную что бы фрагментный шейдер имел доступ к текстуре. Функция main делает только одну вещь — она вызывает внутреннюю функцию texture2D что бы использовать текстуру. Первый параметр это сэмплер uniform-переменной и второй — координаты текстуры. Возращенное значение — это сэмплер текселя (который, в нашем случае, содержит только цвет), который уже прошел фильтрацию. Это итоговый цвет пикселя в данном уроке. В последующих мы уроках мы увидим, что свет просто влияет на цвет полагаясь на параметры света.

Vertex Vertices[4] = < Vertex(Vector3f(-1.0f, -1.0f, 0.5773f), Vector2f(0.0f, 0.0f)), Vertex(Vector3f(0.0f, -1.0f, -1.15475), Vector2f(0.5f, 0.0f)), Vertex(Vector3f(1.0f, -1.0f, 0.5773f), Vector2f(1.0f, 0.0f)), Vertex(Vector3f(0.0f, 1.0f, 0.0f), Vector2f(0.5f, 1.0f))>; 

До этого момента наш вершинный буфер состоял из последовательного списка экземпляров структуры Vector3f, которая содержала только позицию. Теперь у нас есть структура ‘Vertex’, содержащая так же и координаты текстуры в формате Vector2f.

. glEnableVertexAttribArray(1); . glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), 0); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (const GLvoid*)12); . pTexture->Bind(GL_TEXTURE0); . glDisableVertexAttribArray(1); 

Цикл рендера достаточно изменился. Мы начинаем с разрешения использования атрибутов вершин 1 для координат текстур в дополнении к атрибуту 0, который уже занят для позиции. Это соответствует их расположению в вершинном шейдере. Затем мы вызываем glVertexAttribPointer для указания позиции координат текстуры в вершинном буфере. Они представлены в виде 2 вещественных числах, что и указано во 2 и 3 параметрах. Обратите внимание на 4 параметр. Это размер структуры вершины, и он указывается и для вектора позиции и вектора координат. Этот параметр еще называют как «расстояние между вершинами» (vertex stride), он говорит OpenGL количество байтов между началом атрибутов одной вершины и началом уже следующей. В нашем случае буфер содержит: pos0, texture coords0, pos1, texture coords1 и т.д. В предыдущих уроках у нас была только позиция, так что мы могли установить 0 или sizeof(Vector3f). Сейчас же мы имеем больше чем один атрибут, поэтому размер обязательно должен быть равен размеру структуры. Последний параметр — смещение в байтах от начала структуры до атрибутов текстуры. Мы преобразовываем в GLvoid* потому, что функция ожидает смещение в таком формате.

Прежде чем вызывать отрисовку мы привязываем текстуру, которую мы хотим использовать, к модулю. У нас только одна текстура, поэтому нам подойдет любой модуль. Нам нужно только удостовериться, что тот же самый модуль отправлен в шейдер (об этом ниже). После вызова отрисовки мы отключим этот атрибут.

glFrontFace(GL_CW); glCullFace(GL_BACK); glEnable(GL_CULL_FACE); 

Эти вызовы OpenGL не обязательны для текстурирования, но я их добавил для того, что бы картинка была лучше (попробуйте их отключить…). Они включают отброс задней поверхности для дополнительной оптимизации, и используется что бы отбраковывать треугольники до затратных процессов растеризации. Обосновывается это тем, что 50% поверхностей объектов скрыты от нас (задняя сторона человека, дома, автомобиля и т.д.). Функция glFrontFace() говорит OpenGL, что вершины в треугольнике подаются в сторону движения часовой стрелки. То есть, если вы смотрите прямо на плоскость треугольника, то вы заметите, что вершины указаны в часовом порядке. glCullFace() сообщает GPU, что бы он отбрасывал обратные стороны треугольника. Это значит, что «внутри» объекта ничего рендериться не будет, только внешняя часть. Наконец, включаем отбрасывание задних сторон (по умолчанию выключено).

glUniform1i(gSampler, 0); 

Здесь мы устанавливаем индексы модулей текстуры, который мы собираемся использовать внутри сэмплера uniform-переменной в шейдере. ‘gSampler’ это переменная, значение которой было задано ранее через glGetUniformLocation(). Важно запомнить, что индекс модуля текстуры, который использован здесь, не enum OpenGL’я GL_TEXTURE0 (который имеет другое значение).

pTexture = new Texture(GL_TEXTURE_2D, "test.png"); if (!pTexture->Load())

Здесь мы создаем объект Текстуры и загружаем его. ‘test.png’ добавлен к исходникам этого урока, но ImageMagick должна суметь обработать любой файл, переданный ей.

Домашнее задание: если вы запустите этот урок, то вы увидите, что грани пирамиды не одинаковые (она не правильная). Попробуйте понять почему это происходит, и что надо сделать, что бы это исправить.

Как наложить текстуру на куб opengl

OpenGl наложение текстуры на куб простой и понятный пример

Здесь же наложение текстуры на два полигона можно больше

Простая функция загрузки текстуры из файла (текстура в формате BMP глубина цвета 24)

//—————————————————————————
bool load_texture(char* filename,unsigned int num_tex)
//filename — имя файла, num_tex — номер текстуры
BITMAPFILEHEADER bfh; //Заголовок BMP файла
BITMAPINFOHEADER bih; //Заголовок BMP файла
unsigned char *TxBits,*buf; // массивы для цветов
DWORD nBytesRead; // сколько данных прочтено с файла
HANDLE FileHandle; // хендл, открываемого для чтения, файла
int width,height; // ширина и высота файла

// открываем файл для чтения
FileHandle=CreateFile(filename, // имя файла
GENERIC_READ, // открыт для чтения
0, // совместного использования нет
NULL, // защита по умолчанию
OPEN_EXISTING, // только существующий файл
FILE_ATTRIBUTE_NORMAL, // атрибуты обычного файла
NULL); // шаблона атрибутов нет

if (FileHandle == INVALID_HANDLE_VALUE) // если ошибка при открытии то сообщаем
MessageBox(NULL,»Файл отсутствует!»,»Ошибка»,MB_OK | MB_ICONINFORMATION);
return FALSE;
>
ReadFile(FileHandle,&bfh,sizeof(bfh),&nBytesRead,NULL); //считываем первый заголовок, вообще то он не нужен
ReadFile(FileHandle,&bih,sizeof(bih),&nBytesRead,NULL); //считываем второй заголовок
// записываем размер рисунка
width = bih.biWidth;
height = bih.biHeight;

int size = width*height*3;//
buf = new unsigned char[size]; // выделяем память для исходного рисунка
TxBits = new unsigned char[size]; // выделяем память для получаемого рисунка
ReadFile(FileHandle,buf,(size),&nBytesRead,NULL); //считываем данные о цветах пикселей

// в этом цикле меняем структуру цвета, т.к. BMP файл имеет структуру BGR, а нам нужен RGB
for (int i=0;i <(size);i+=3)
//переписываем массив цветов
TxBits[i]= buf[i+2];
TxBits[i+1]= buf[i+1];
TxBits[i+2]= buf[i];
>

// тут инициализируется текстура
glBindTexture( GL_TEXTURE_2D, num_tex);
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, GL_RGBA,
width, height, // размер текстуры
0, GL_RGB, GL_UNSIGNED_BYTE, TxBits);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

CloseHandle(FileHandle); // закрываем файл
// освобождаем памяти
delete [] TxBits;
delete [] buf;
return TRUE; // возвражаем TRUE если все успешно

glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glEnable(GL_ALPHA_TEST);

glBindTexture(GL_TEXTURE_2D, 1);
glBegin(GL_POLYGON ); // здесь можно GL_QUADS GL_POLYGON

glTexCoord2f(0.0,0.0); glVertex2f(0.1, 0.1);//Низ лево
glTexCoord2f(1.0,0.0); glVertex2f(0.9, 0.1);//Низ право
glTexCoord2f(1.0,1.0); glVertex2f(0.9, 0.9);//Верх право
glTexCoord2f(0.0,1.0); glVertex2f(0.1, 0.9);//Верх лево

glBindTexture(GL_TEXTURE_2D, 2);
glBegin(GL_POLYGON ); // здесь можно GL_QUADS GL_POLYGON
glTexCoord2f(0.0,0.0); glVertex2f(0.3, 0.3);//Низ лево
glTexCoord2f(1.0,0.0); glVertex2f(0.7, 0.3);//Низ право
glTexCoord2f(1.0,1.0); glVertex2f(0.7, 0.7);//Верх право
glTexCoord2f(0.0,1.0); glVertex2f(0.3, 0.7);//Верх лево

glRotatef(xrot,1.0f,0.0f,0.0f); // Вращение по оси X
glRotatef(yrot,0.0f,1.0f,0.0f); // Вращение по оси Y
glRotatef(zrot,0.0f,0.0f,1.0f); // Вращение по оси Z

glBindTexture(GL_TEXTURE_2D, 1);
glBegin(GL_QUADS);
// Передняя грань
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();

glBindTexture(GL_TEXTURE_2D, 2);
glBegin(GL_QUADS);
// Задняя грань
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); // Низ лево
glEnd();

glBindTexture(GL_TEXTURE_2D, 3);
glBegin(GL_QUADS);
// Верхняя грань
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); // Верх право
glEnd();

glBindTexture(GL_TEXTURE_2D, 4);
glBegin(GL_QUADS);
// Нижняя грань
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); // Низ право
glEnd();

glBindTexture(GL_TEXTURE_2D, 5);
glBegin(GL_QUADS);
// Правая грань
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); // Низ лево
glEnd();

glBindTexture(GL_TEXTURE_2D, 6);
glBegin(GL_QUADS);
// Левая грань
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); // Верх лево

xrot+=0.3f; // Ось вращения X
yrot+=0.2f; // Ось вращения Y
zrot+=0.4f; // Ось вращения Z
glFlush();
glutSwapBuffers();
glutPostRedisplay();
>
//—————————————————————————-
#pragma argsused
int main(int argc, char* argv[])
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
glutInitWindowSize(width,height);
glutInitWindowPosition(10, 10);
glutCreateWindow(«Cube Texture»);
//для куба
load_texture(«cubeFive.bmp», 1); // Загрузка текстур
load_texture(«cubeFour.bmp», 2);
load_texture(«cubeOne.bmp», 3); // Загрузка текстур
load_texture(«cubeSix.bmp», 4);
load_texture(«cubeThree.bmp», 5); // Загрузка текстур
load_texture(«cubeTwo.bmp», 6);

glEnable(GL_TEXTURE_2D); // Разрешение наложение текстуры
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0); //
glDepthFunc(GL_LESS); // фрагмент проходит тест если его значение глубины меньше хранимого в буфере
glEnable(GL_DEPTH_TEST); // включить тест гдубины
glShadeModel(GL_SMOOTH); // разрешить плавное цветовое сглаживание
glViewport(0,0,width,height); //
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

/* //для полигонов
load_texture(«cubeFive.bmp», 1);
load_texture(«cubeFour.bmp», 2);
glViewport(0,0,width,height); //
glClearColor(0.5, 0.5, 0.5, 1.0);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1, 0.0, 1, -1.0, 1.0);
glutDisplayFunc(Draw1);
glutMainLoop();

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

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