OpenGL ES 1. Двумерные текстуры
Для нанесения изображения на поверхность полигона мы должны установить соответствие между вершинами полигона и координатами текстур, т.е. выполнить проекцию текстуры на полигон. Рассмотрим очень простой пример.
Теперь попробуем изменить правило присвоения вершинам координат текстуры. Правой нижней точке нашего квадрата присвоим координаты текстуры S=0.75, T=0.75, а левой верхней точке S=0.25, T=0.25:
Получим такое изображение, растянутое по диагонали:
Вернёмся от теоретических рассуждений к практике. Начнем с вопроса: "Как загрузить графический файл в OpenGL ES? Сначала мы должны указать программе как упакованы пиксели в файле. Обычно принимают за правило, что одному цвету (красному, зелёному и синему) соответствует один байт в файле. Устанавливается это так:
gl.glPixelStorei(GL10.GL_UNPACK_ALIGNMENT,1); Если забудете выполнить данную команду можете получить изображение разрезанное на куски по диагонали. Далее нужно попросить OpenGL выделить нам свободные имена текстур. Имя текстуры - это уникальный номер текстуры, по которой происходит обращение к ней. Что представляет из себя имя текстуры? Обычное целое число - 1, 2, 3 и.т.д. Суть в том, что для привязки новой текстуры к имени оно должно быть свободно, т.е. не занято другой текстурой. Для генерации свободных имен существует команда glGenTextures, формат которой выглядит так: gl.glGenTextures(количество создаваемых имен, массив в который нужно записать имена, 0); Например, нам нужно получить одно свободное имя для загрузки текстуры. Мы должны предварительно создать пустой массив целых чисел, куда будут записаны имена. Поскольку необходимо одно имя - размер этого массива равен единице: int[] names=new int[1]; Теперь получим в этот массив одно свободное имя текстуры: gl.glGenTextures(1, names, 0); Мы можем обращаться к полученному имени как к names[0]. Далее, мы должны сделать наше имя текстуры текущим поскольку все последующие команды будут действовать на это имя. Установка текущей текстуры производится командой: gl.glBindTexture(GL10.GL_TEXTURE_2D, names[0]); Первый аргумент указывает, что текущая текстура будет двумерной. В прочем, бывают ещё одномерные, трёхмерные и кубические текстуры. В этой статье мы их рассматривать не будем. Второй аргумент - это имя нашей текстуры. Теперь можно настраивать индивидуальные параметры текущей текстуры. Создадим мипмапы для нашей текстуры. Что такое мипмап? Это множество уменьшенных копий нашего изображения. Они нужны для того, чтобы текстуры удалённых от глаза наблюдателя объектов выглядели плавными без резких переходов. Не забудьте сгенерировать мипмапы, иначе получите неприятный для глаза эффект, при котором на изображении дальних текстур будут чётко выделяться движущиеся полосы. В расширение OpenGL ES 1.1 включена команда для автоматической генерации мипмапов всех размеров: gl.glTexParameterx(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE); Текстура состоит из элементов - пикселей. Пиксели текстуры принято называть текселами. Изображение полигона на экране тоже состоит из пикселей. При проецировании текстуры на полигон возможны две ситуации: когда один тексел попадает на несколько пикселей полигона (увеличение текстуры) и когда на один пиксель приходится несколько текселов (уменьшение текстуры). Сразу возникает вопрос: "какой из текселов будет присвоен данному пикселю при увеличении или уменьшении текстуры?". Процедура выбора наиболее подходящего тексела для данного пикселя называется фильтрацией текстур. Также для установки фильтра может выполняться интерполяция цветов нескольких текселов при записи в пиксель. Для фильтрации двумерных текстур используется команда glTexParameterf. Первый аргумент данной команды определяет тип текстуры, в нашем случает текстура двумерная поэтому используется GL_TEXTURE_2D. Второй аргумент устанавливает режим для которого применяется фильтр: GL_TEXTURE_MIN_FILTER при уменьшении или GL_TEXTURE_MAG_FILTER при увеличении текстуры.
gl.glTexParameterf(GL10.GL_TEXTURE_2D, //указывает, что используется двумерная текстура GL10.GL_TEXTURE_MIN_FILTER, // указывает, что установка действует при уменьшении GL10.GL_LINEAR_MIPMAP_LINEAR); //установленное правило фильтрации В режиме увеличения мип-мапы не используются, а применяется только основное, самое крупное изображение текстуры. Наилучшее качество при увеличении текстуры обеспечивает третий аргумент GL_LINEAR: gl.glTexParameterf(GL10.GL_TEXTURE_2D, Что произойдет если координаты текстуры выйдут за пределы диапазона от 0 до 1? Для управления выводом ненормированных координат текстур существует два режима так называемого "обёртывания" (wrap). По умолчанию целая часть текстурной координаты будет отброшена и оставлена только дробная часть. Например, если координата текстуры равна 2.7, то при проецировании текстуры на полигон она отобразится как 0.7. Таким образом, к границе текстуры как-бы "приклеивается" ёё копия, которая повторяется по количеству целых значений в координате S или T. Такой режим называется повтором (GL_REPEAT) и задается командами: gl.glTexParameterx(GL10.GL_TEXTURE_2D, При повторе необходимо обеспечить плавный переход между концом одного экземпляра текстуры и началом другого, т.е. текстура должна быть бесшовной. Кроме повтора изображения существует режим GL_CLAMP_TO_EDGE, при котором крайние текселы текстуры подтягиваются к границе полигона. После установки параметров текстуры мы можем приступить к загрузке изображения из графического файла. Сначала создадим объект класса Bitmap и загрузим в него картинку из ресурсов Android: Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), idpicture); где context-текущий контекст, idpicture - целочисленный идентификатор графического ресурса из файла R.java. Графические файлы обычно располагают в каталоге res в подкаталогах drawable-hdpi, drawable-ldpi и drawable-mdpi. Далее Android SDK автоматически присваивает им идентификаторы и записывает их в файл R.java. Например, если нам нужно получить идентификатор файла icon1.jpg мы можем использовать такое обращение к R.java: idpicture=R.drawable.icon1 Затем мы должны предать загруженный файл в OpenGL для формирования из него двумерной текстуры. Для этого существует команда: GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); bitmap нам больше не нужен, т.к. из него уже сформирована текстура и его можно удалить: bitmap.recycle(); Графическое изображение, а также правила фильтрации и "обёртывания" привязаны к имени текущей текстуры и являются её свойствами. Поэтому удобно определить отдельный класс и создавать текстуры в виде объектов этого класса.
Далее рассмотрим вопрос взаимодействия текстуры и освещения. При наложении текстуры на полигон освещение будет игнорировано если выполнена команда: gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_REPLACE); При использовании третьего аргумента GL_REPLACE текстура полностью перезаписывает пиксели на полигоне. Поэтому эффект освещения исчезает. Чтобы совместить освещение с текстурой нужно передать в качестве третьего аргумента параметр GL_MODULATE:
gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_ MODULATE); В этом случае цвет связанный с освещением умножается на цвет текстуры в данной точке изображения и эффект освещения становится заметным. Нужно отметить, что команда glTexEnvx не привязывает свои настройки к имени конкретной текстуры. Поэтому она не может входить в класс текстуры и должна вызываться каждый раз перед рисованием объекта.
Теперь мы должны научиться передавать координаты текстур в OpenGL по аналогии с координатами вершин. Рассмотрим это на примере нашего квадрата. Сначала подготовим буфер необходимой длины. Каждой вершине присвоены две текстурные координаты S и T. Каждая координата занимает 4 байта в памяти, итого 8 байт на вершину, 32 байта на квадрат: ByteBuffer bb = ByteBuffer.allocateDirect(32); Запишем координаты текстуры в буфер texcoordBuffer.position(0); texcoordBuffer.put(0); // координата S левого нижнего угла Запись в буфер можно также производить из заранее подготовленного массива с координатами текстур. Например так: float [] texcoordBufferArray={0,1, 0.75f,0.75f, 1,0, 0.25f,0.25f}; Нужно обязательно соблюдать следующее правило: порядок обхода вершин при записи в буферы должен быть одинаков для координат вершин, векторов нормалей и координат текстур. При этом изображение будет корректным. После того как в буфер переданы координаты текстуры для вершин мы должны включить использование двумерных текстур командой: gl.glEnable(GL10.GL_TEXTURE_2D); Выбираем текущую активную текстуру с именем names[0]: gl.glBindTexture(GL10.GL_TEXTURE_2D, names[0]); Если необходимо, чтобы при текстурировании учитывалось освещение, нужно включить режим модуляции для текущей текстуры: gl.glTexEnvx(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_ MODULATE); Разрешаем использование массивов для координат текстур: gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); Затем передаем буфер с координатами текстуры в OpenGL: gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, texcoordBuffer);
Данные с координатами текстуры переданы в OpenGL и будут учтены при следующем вызове команды glDrawArrays, которая нарисует полигон.
|