Помощь в учёбе, очень быстро...
Работаем вместе до победы

Работа с текстурами

РефератПомощь в написанииУзнать стоимостьмоей работы

Для того чтобы мы могли накладывать текстуру на многоугольник, необходимо каким-то образом «прикрепить» пашу текстуру к многоугольнику. В OpenGL для этого в каждой вершине многоугольника задаются текстурные координаты. В качестве текстурных координат можно использовать 1-, 2-, 3- и 4-мерные векторы, которые внутри OpenGL все равно приводятся к четырехмерному вектору в однородных координатах. При… Читать ещё >

Работа с текстурами (реферат, курсовая, диплом, контрольная)

Библиотека OpenGL содержит встроенную поддержку текстур: на каждый выводимый многоугольник можно наложить заранее подготовленное изображение (текстуру). При выводе этого многоугольника заданное изображение будет корректным образом накладываться на него.

Первым шагом для работы с текстурами является создание в OpenGL соответствующего объекта-текстуры и загрузка в этот объект соответствующего изображения. Обратите внимание, что в OpenGL нет встроенной поддержки для загрузки текстур из файлов в различных графических форматах (например, .jpg или .gif). Для этого следует использовать другие библиотеки, например DevIL или SOIL, поддерживающие загрузку изображений из большого числа различных форматов.

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

void glGenTextures (GLsizei n, GLuint * ids);

Первый параметр задает, сколько именно идентификаторов нужно выделить (и заодно задает размер массива, задаваемого вторым параметром). Второй параметр содержит указатель на массив, в котором будут возвращены выделенные идентификаторы.

Для уничтожения уже существующих текстур используется команда gIDeleteTextures:

void glDeleteTextures (GLsizei n, const GLuint * ids);

Следующим шагом будет сделать эту текстуру текущей («привязать» ее) для заданного типа текстуры. Для этого используют команду glBindTexture:

void glBindTexture (GLenum target, GLuint textureld);

Параметр target задает тип используемой текстуры и принимает одно из следующих значений: GL_TEXTURE1 D, GL_TEXTURE_2D, GL_TEXTURE_3D и GL_TEXTURE_CUBE_MAP. Если это первая «привязка» данной текстуры, то внутри OpenGL будет создан соответствующий текстурный объект и типом данного объекта станет target. После того как текстура «привязана», можно задать ее свойства, задать соответствующее ей изображение и использовать при рендеринге.

Если при вызове функции glBindTexture вместо идентификатора текстуры передать нуль, то никакая текстура не становится текущей для заданного типа и текстурирования происходить не будет.

Для задания параметров текстуры служит функция gITexParameter:

void glTexParameter{i f}(GLenum target, GLenum property,.

GLenum value);

Параметр target идентичен этому же параметру у функции glBindTexture. Параметр property задает значение, какого именно свойства текстуры мы будем задавать. Последний параметр содержит значение для выбранного свойства.

Ниже рассмотрим только основные свойства текстур. Одними из самых важных свойств являются GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S и GL_TEXTURE_WRAP_R. Существуют и другие свойства, но их рассматривать не будем.

Первое из этих свойств (GL_TEXTURE_MAG_FILTER) задает режим фильтрации при увеличении (растяжении) текстуры. Соответствующие этому свойству значения равны GL_NEAREST и GL_LINEAR.

Второе свойство (GL_TEXTURE_MIN_FILTER) задает режим фильтрации при сжатии текстуры. Соответствующими значениями являются GL_NEAREST, GLJJNEAR, GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST и GL_LINEAR_MIPMAP_LINEAR.

To, как работают данные фильтры, рассмотрим далее.

Свойства GL_TEXTURE_WRAP_S и GL_TEXTURE_WRAP_T задают закон отсечения соответствующих компонент текстурных координат (в OpenGL текстурные координаты являются четырехмерным вектором и для их компонент используются следующие имена — s, t, г, q). Чаще всего используются режимы GL_CLAMP и GL_REPEAT.

Для того чтобы мы могли накладывать текстуру на многоугольник, необходимо каким-то образом «прикрепить» пашу текстуру к многоугольнику. В OpenGL для этого в каждой вершине многоугольника задаются текстурные координаты. В качестве текстурных координат можно использовать 1-, 2-, 3- и 4-мерные векторы, которые внутри OpenGL все равно приводятся к четырехмерному вектору в однородных координатах. При этом, если вторая или третья компонента не заданы, то они считаются равными нулю. Если не задана четвертая компонента, то она считается равной единице. Для задания текстурных координат в вершине служит команда gITexCoord:

void glTexCoord{1 2 3 4}{s i f d}(coords);

void glTexCoord{1 2 3 4}{s i f d}v (pointer);

Текстурные координаты являются атрибутом вершины, как и цвет и нормаль. Внутри OpenGL они умножаются на матрицу преобразования текстурных координат (GL_TEXTURE).

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

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

С точки зрения текстурных координат вся текстура — это просто единичный квадрат ([О, I]2). Таким образом, текстурные координаты вообще не зависят от реального размера текстуры в текселах (подобно тому, как изображение на экране состоит из отдельных пикселов, текстура считается состоящей из отдельных текселов, организованных в 1-, 2-, 3-мерный массив). Это позволяет отделить текстуру от используемых текстурных координат и облегчить переход от одной текстуры к другой. Из этого следует, что если фрагменту соответствуют текстурные координаты (s, t), то перед обращением к этой текстуре необходимо привести обе компоненты текстурных координат к отрезку [0, 1].

Двумя самыми простыми способами приведения числа к отрезку [О, 1] являются обрезание (GL_CLAMP) и взятие дробной части (GL_REPEAT). При первом из них, если соответствующее значение меньше нуля, оно заменяется нулем, а если больше единицы, то оно заменяется на единицу. Для второго способа просто берется дробная часть от соответствующего значения, которая гарантированно лежит на отрезке [0, 1].

Наиболее часто используемым режимом является GL_REPEAT: так, если для квадрата заданы текстурные координаты, как показано на рис. 10.11, то в результате на этот квадрат будет наложено четыре изображения текстуры — по одному на каждую четверть квадрата (для режима GL_REPEAT).

Квадрат с заданными текстурными координатами.

Рис. 10.11. Квадрат с заданными текстурными координатами.

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

Когда мы рассматривали обработку изображений, то считали, что изображение — это функция, заданная на сетке (точнее, в узлах этой сетки). То же самое применимо и для текстуры. Соответственно, скорее всего, окажется, что точка, задаваемая текстурными координатами, не попадет точно в узел этой сетки, а будет находиться между четырьмя соседними узлами (рис. 10.12).

Если наша точка, задаваемая текстурными координатами (s, t), попадает мимо узлов сетки, то самым простым способом получения соответствующего этой точке значения из текстуры будет взять значения из ближайшего к ней узла сетки. Именно этот способ фильтрации и соответствует режиму GL_NEAREST.

Положение точки с заданными текстурными координатами относительно узлов сетки.

Рис. 10.12. Положение точки с заданными текстурными координатами относительно узлов сетки.

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

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

Представьте себе очень большой квадрат, параллельный экрану, на который накладывается текстура в виде шахматных клеток. Если этот квадрат близок к экрану, то каждый пиксел экрана будет точно соответствовать определенной клетке текстуры (т.е. каждая клетка текстуры будет накрывать сразу группу пикселов на экране). Однако по мере удаления квадрата от экрана количество пикселов, соответствующих одной клетке текстуры, будет уменьшаться и, наконец, станет меньше одного пиксела. Это означает, что данному пикселу можно назначить как черный, так и белый цвет, поскольку клетки обоих этих цветов будут проектироваться на данный пиксел. На практике это приведет к тому, что пикселам экрана будут назначаться фактически случайные цвета. В результате мы получим на экране просто «мусор» из белых и черных точек. Это происходит из-за того, что в действительности пиксел на экране — это не бесконечно малая точка, а некоторая достаточно малая область на экране. Если построить пирамиду по области, соответствующей одному пикселу с вершиной в центре проектирования, то она высечет на нашем квадрате прямоугольную область, включающую в себя текселы как черного, так и белого цветов.

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

В связи с этим на практике обычно используют довольно простое и красивое решение, называемое пирамидальным фильтрованием (mipmapping). Его идея заключается в том, чтобы заранее построить набор вспомогательных текстур путем усреднения текселов исходной текстуры.

Разобьем исходную текстуру на блоки 2×2 тексела. Для каждого такого блока найдем среднее значение цвета по составляющим его текселам и построим новую текстуру вдвое меньшего размера (по каждому измерению), заменим каждый такой блок текселов из исходной текстуры на один тексел со средним значением цвета (рис. 10.13).

Фильтрация текстуры для построения пирамиды уровней.

Рис. 10.13. Фильтрация текстуры для построения пирамиды уровней.

Далее повторяем эту процедуру и по построенной таким образом текстуре строим новую текстуру еще меньшего размера и т. д. Повторяем этот процесс до тех пор, пока не придем к текстуре, состоящей из всего одного тексела. Значением этого тексела будет средний цвет по всей исходной текстуре.

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

Теперь для каждого фрагмента нетрудно найти соответствующий ему коэффициент сжатия текстуры. Если, например, сжатие происходит ровно в 8 раз, то мы можем вместо исходной текстуры взять слой из этой пирамиды, соответствующий сжатию текстуры ровно в 8 раз. При этом мы можем для выборки из этого слоя пирамиды использовать как линейную фильтрацию, так и выбор ближайшего тексела.

Однако в общем случае степень сжатия текстуры для фрагмента, скорее всего, окажется не равной какой-либо степени двух. Тогда мы можем легко найти два соседних слоя из пирамиды, так что наш коэффициент сжатия находится между степенями сжатия для этих слоев. Например, если нам нужна степень сжатия, равная 5, то такими слоями будут слои, соответствующие сжатию в 4 и 8 раз.

Далее мы можем либо выбрать один из этих двух слоев (например, 4) и произвести выборку из него, либо произвести выборку из каждого из этих слоев, а потом получить итоговое значение путем интерполяции полученных значений. OpenGL позволяет связать с каждой текстурой такую пирамиду из сжатых текстур и при обращении к текстуре использовать значение из слоев этой пирамиды. Тогда для выбора значения из такой текстуры у нас получается четыре варианта: нужно определить, как именно мы будем брать значение из конкретного слоя, и определить, будем ли мы брать значение из ближайшего слоя или сразу из двух соседних слоев и интерполировать между ними. Этим вариантам соответствуют константы GL_NEAREST_MIPMAP_NEAREST, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST и GL_LINEAR_MIPMAP_LINEAR.

Для каждой текстуры необходимо задать соответствующее ей изображение (или всю пирамиду изображений). Для этого служат команды gITexImagelD, glTexlmage2D и glTexlmage3D:

void glTexlmage1D (GLenum target, GLint level,.

GLint internalFormat, GLsizei width,.

GLint border, GLenum format, GLenum type, const GLvoid * texels);

void glTexlmage2D (GLenum target, GLint level,.

GLint internalFormat, GLsizei width,.

GLsizei height, GLint border, GLenum format,.

GLenum type, const GLvoid * texels);

void glTexlmage3D (GLenum target, GLint level,.

GLint internalFormat, GLsizei width,.

GLsizei height, GLsizei depth, GLint border,.

GLenum format, GLenum type, const GLvoid * texels);

Первый параметр (target) имеет тот же смысл, что и для команды glBindTexture. Параметр level служит для задания уровня слоя внутри пирамиды, при этом основанию пирамиды соответствует значение 0. Каждый тексел текстуры внутри может содержать от одной до четырех компонент. Число компонент определяется форматом текстуры.

Параметры format и intemalFormat определяют формат, в котором задаются данные для текстуры (например, GL_RGB или GL_RGBA), и внутренний формат, в котором GPU будет хранить текстуру в своей памяти (например, GL_RGB8 или GL_RGBA8; допустимо вместо этого параметра просто указать число компонент).

Параметры width, height и depth задают размер текстуры в текселах по каждому измерению. Параметр border сейчас не используется и должен быть равен нулю.

Параметр type определяет, какой тип данных используется для задания значений каждой компоненты — обычно это байты, им соответствует тип GL_UNSIGNED_BYTE. Параметр texels является указателем на область памяти, где находятся значения для каждой компоненты каждого тексела.

Часто вместо этих вызовов используются следующие функции из библиотеки GLU:

void gluBuild1DMipmaps (GLenum target, GLenum internal format,.

GLsizei width, GLenum format,.

GLenum type, const GLvoid * texels);

void gluBuild2DMipmaps (GLenum target, GLenum internal format,.

GLsizei width, GLenum height,.

GLenum format, GLenum type, const GLvoid * texels);

void gluBuild3DMipmaps (GLenum target, GLenum internal format,.

GLsizei width, GLenum height,.

GLsizei depth, GLenum format,.

GLenum type, const GLvoid * texels);

Основное отличие этих функций от gITexImage в том, что они по заданному изображению строят и загружают все уровни пирамиды. Также они перемасштабируют текстуры в текстуры размера, равного степени двух.

Ниже приведен пример функции, строящей текстуру в чернобелую клетку:

#define WIDTH 64.

#define HEIGHT 64.

GLuint buileCheckerTexture ().

{.

GLubyte image [WIDTH][HEIGHT][4];

GLuint id;

Работа с текстурами.

На рис. 10.14 приведен вид квадрата с «натянутой» на него текстурой без использования пирамидальной фильтрации. Обратите внимание на многочисленные артефакты в дальней части квадрата, где происходит наибольшее сжатие текстуры.

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

Рис. 10.14. Квадрат без использования пирамидальной фильтрации.

Рис. 10.14. Квадрат без использования пирамидальной фильтрации.

На рис. 10.15 показано, как изменится изображение в случае использования трилинейной фильтрации. Обратите внимание, что все артефакты, связанные со сжатием текстуры, полностью пропали.

Квадрат с использованием пирамидального фильтрования.

Рис. 10.15. Квадрат с использованием пирамидального фильтрования.

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

glEnable (GL_TEXTURE_2D);

На рис. 10.16 приводится уже знакомое изображение чайника, однако на этот раз на него наложена текстура.

Рассмотрим, какие операции нужно произвести, чтобы вывести такое изображение чайника. Как уже отмечалось, необходимо для каждой вершины задать текстурные координаты. Для этого используют функцию glutSolidTeapot, ее можно применить и для получения искомого изображения (см. рис. 10.16). Необходимо только наличие загруженной текстуры, выбранной для тина GL_TEXTURE_2D (если текстура всего одна, то мы ее выбираем единожды для этого типа при загрузке и больше не трогаем), и разрешение наложения двухмерной текстуры (для этого в начале программы (но после создания окна) необходимо вызвать glEnable с аргументом GL_TEXTURE_2D).

Несколько изменим ситуацию: пусть будет две текстуры, задаваемые идентификаторами tex1 и tex2. Необходимо вывести два чайника: первый должен быть с первой текстурой, а второй — со второй текстурой.

Как и ранее, нужно в начале программы разрешить двухмерное текстурирование и создать необходимые текстуры. Но теперь у нас уже нет универсальной текстуры. Нужно перед выводом очередного объекта выбирать соответствующую ему текстуру и только после этого выводить объект:

// сохраняем матрицу gIPushMatrix ();

// задаем преобразование для первого объекта gITranslatef (1,0, 0);

// задаем текстуру для первого объекта glBindTexture (GL_TEXTURE_2D, texl);

// выводим объект glutSoldTeapot (1);

// восстанавливаем матрицу gIPopMatrix ();

// опять сохраняем матрицу gIPushMatrix ();

// задаем преобразование для второго объекта gITranslatef (-1, 0, 0);

// задаем текстуру для второго объекта glBindTexture (GL_TEXTURE_2D, tex2);

// выводим второй объект glutSoldTeapot (1);

// восстанавливаем матрицу gIPopMatrix ();

Можно совместить освещение с наложением текстуры, для чего разрешить одновременно и освещение, и текстурирование. На рис. 10.17 приведено получающееся в таком случае изображение для чайника.

Текстурированный чайник.

Рис. 10.16. Текстурированный чайник.

Освещенный и текстурированный чайник.

Рис. 10.17. Освещенный и текстурированный чайник.

Особым типом текстуры являются кубические текстурные карты (cubic texture тар). Если одномерная текстура — это фактически функция, заданная на единичном отрезке [0, 1], двухмерная текстура — функция, заданная на единичном квадрате, и кубическая текстура — функция, заданная в единичном кубе, то кубическая текстурная карта — это функция от направления в трехмерном пространстве.

С математической точки зрения функция от направления в пространстве — это функция, заданная на единичной сфере: каждому направлению соответствует однозначно определенная точка на ее поверхности. Для задания направления мы выпускаем луч из начала координат (центра сферы) в заданном направлении. Этот луч всегда пересекает сферу в единственной точке, определяющей возвращаемое значение.

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

Чтобы получить значение, соответствующее заданному направлению, мы, как и ранее, выпускаем луч из начала координат в этом направлении и ищем его пересечение со сторонами куба. Такой луч всегда пересечет одну из сторон куба, а точка пересечения и будет определять возвращаемое значение (рис. 10.18).

Кубическая текстурная карта.

Рис. 10.18. Кубическая текстурная карта.

Тем самым, такую функцию можно задать с помощью шести изображений, «наклеиваемых» на стороны этого куба. Тогда для задания каждого из этих шести изображений служит уже знакомая нам функция glTex! mage2D, которая в качестве параметра target получает одну из следующих констант: GL_TEXTURE_CUBE_IVIAP_ POSITIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_ MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_ CUBE_MAP_NEGATIVE_Y или GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:

unsigned textureld;

gIGenTextures (1, &textureld);

gIBindTexture (GL_TEXTURE_CUBE_MAP, textureld);

for (int i = 0; i < 6; i++).

glTexlmage2D (GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + i, 0, GL_RGB8, width, width, 0, GL_RGB, GL_UNSIGNED_BYTE, maps [i])) glEnable (GL_TEXTURE_CUВE_MAP);

Одним из назначений кубических текстурных карт является имитация отражения гладким объектом своего отражения {environment mapping). Для этого строится кубическая текстурная карта, содержащая изображения окружающих объектов со всех шести сторон (часто для этого используется готовая текстура, но ее можно построить и на ходу — прямо в приложении).

Далее у нас уже есть соответствующая кубическая текстурная карта, и мы можем для имитации отражения воспользоваться одной из функций OpenGL — автоматической генерацией текстурных координат для моделирования отражения. Эта возможность довольно специфична, но для наших целей она великолепно подходит. Обратите внимание: для ее работы требуется, чтобы в вершинах были заданы нормали.

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

gITexGeni (GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION);

gITexGeni (GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION);

gITexGeni (GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION);

glEnable (GL_TEXTURE_GEN_S);

glEnable (GL_TEXTURE_GEN_T);

glEnable (GL_TEXTURE_GEN_R);

glutSolidTeapot (1.0);

gIDisable (GL_TEXTURE_GEN_S);

gIDisable (GL_TEXTURE_GEN_T);

gIDisable (GL_TEXTURE_GEN_R);

На рис. 10.19 приведено изображение чайника с наложенной на него кубической текстурой, имитирующей отражение.

Изображение чайника е наложенной на него кубической текстурой.

Рис. 10.19. Изображение чайника е наложенной на него кубической текстурой.

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

OpenGL может работать со многими текстурами, они все равно загружаются в процессе работы всего один раз — обычно при инициализации программы (но после создания контекста OpenGL). Далее, перед выводом объекта, использующего какуюлибо текстуру, мы просто делаем ее текущей посредством вызова gIBindTexture.

Показать весь текст
Заполнить форму текущей работой