LXF167:Android
|
|
|
Android » Программирование Работа с сенсорным экраном при помощи библиотек OpenGL
Содержание |
Текстуры и OpenGL: На сенсорном экране
OpenGL в Android позволяет создавать улучшенную графику и использовать гибкий открытый подход к трехмерной графике. В предыдущей статье мы работали с фигурами, цветами, камерами и проекциями экрана и познакомились с перемещением фигуры; теперь мы продолжим работу с примером и будем перемещать фигуру по экрану в ответ на касания сенсорного экрана, а также познакомимся с умеренно сложной, но полезной технологией наложения текстур. Как и прежде, мы пользуемся OpenGL ES2.0, который поддерживается в Android 2.2 и выше. Помните, что код нельзя будет проверить в эмуляторе, так как он не работает с OpenGL 2, и для отладки вам потребуется реальное устройство.
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
В развитие предыдущей статьи Джульетта Кемп переходит к текстурам и сенсорному экрану для перемещения фигур.
Перемещение и сенсорный экран
Прежде всего кратко ознакомимся с перемещением (объекта по экрану), не касаясь работы с сенсорным экраном. Это просто еще одна операция с матрицей, translateM(). Добавьте следующие две строки после поворота в CISGLRenderer.onSurfaceCreated():
Matrix.setIdentityM(iMatrix, 0);
Matrix.translateM(iMatrix, 0, 0.2f, 0.2f, 0f);
Также понадобится создать переменную класса iMatrix и добавить в onDrawFrame() строку
Matrix.multiplyMM(uMVPMatrix, 0, tMatrix, 0, uMVPMatrix, 0);
после других операций умножения матриц.
Эти вызовы переместят куб на 0.2 координаты влево и на 0.2 координаты вверх по экрану.
Помните, что порядок множителей при умножении матриц имеет значение! Подробности см. во врезке.
В качестве альтернативного варианта можно немного перемещать куб в каждом кадре, добавив следующую строку в onDrawFrame():
Matrix.translateM(iMatrix, 0, 0.001f, 0.001f, 0);
Скомпилируйте и запустите программу, и вы увидите, как куб медленно движется по экрану.
События сенсорного экрана
Двигать кубиками – дело хорошее, но интереснее заставить куб перемещаться в контексте события сенсорного экрана.
Напишем код, который при движении пальцем по сенсорному экрану будет перемещать куб вослед. Чтобы происходящее было яснее, стоит закомментировать строку с поворотом на постоянный угол в CISGLRenderer.onDrawFrame() (так что происходит только перемещение) и строку насчет первоначального поворота в CISGLRenderer.onSurfaceCreated() (чтобы объект перемещался по обычным, а не повернутым осям X/Y/Z, о чем сказано ранее).
Для обработки событий сенсорного экрана нужно создать собственный класс SurfaceView, CISSurfaceView:
public class CISSurfaceView extends GLSurfaceView {
private CISGLRenderer renderer;
private float prevX = 0;
private float prevY = 0;
public CISSurfaceView(Context c) {
super(c);
setEGLContextClientVersion(2);
renderer = new CISGLRenderer(this.getContext());
setRenderer(renderer);
}
public boolean onTouchEvent(MotionEvent e) {
float x = e.getX();
float y = e.getY();
float height = getHeight();
float width = getWidth();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = (x - width/2) * -1;
float dy = (y - height/2) * -1;
renderer.diffX = (dx - prevX) / (width/2);
renderer.diffY = (dy - prevY) / (height/2);
prevX = dx;
prevY = dy;
}
return true;
}
}
Метод конструктора просто перемещает код из основного Занятия в Представление. Это производится в методе onTouchEvent(). Здесь мы получаем значения X и Y из обнаруженного события MotionEvent, затем высоту и ширину экрана.
Сейчас нам интересно только действие ACTION_MOVE, оно регистрируется при перемещении пальца по экрану. Это действие возникает повторно (и очень часто!) до тех пор, пока вы не снимете палец с экрана, так что этот метод будет вызываться много раз (в чем вы убедитесь, если добавите в него строку для записи сообщения в лог).
dx и dy используются для преобразования координат сенсорного экрана X и Y в координаты системы с точкой отсчета в центре экрана. Отсчет координат сенсорного экрана ведется из левого нижнего угла, ось x уходит вверх, а ось y – влево. В OpenGL используется система координат с началом отсчета в центре экрана (ось x по-прежнему уходит вправо, а ось y – вверх). dx и dy содержат расстояние до точки нажатия от центра экрана в пикселях.
Затем мы получаем разницу между предыдущими координатами (prevX и prevY) и текущими координатами (dx and dy) и преобразуем их из пикселей в координаты OpenGL. В OpenGL по умолчанию за 1 берется расстояние от центра до каждого края – очевидно, сенсорный экран имеет (0.5 * высота) пикселей от центра до верхнего края и (0.5 * ширина) пикселей от центра до каждой стороны, поэтому мы используем эти значения для генерирования координат OpenGL.
Наконец, мы передаем эти значения рендереру. Замените статичную строку translateM() в методе onDrawFrame() класса CISGLRenderer следующей:
Matrix.translateM(iMatrix, 0, diffX, diffY, 0);
(также понадобится объявить diffX и diffY как публичные переменные класса).
Скомпилируйте и запустите программу, и вы увидите, как куб перемещается вслед за пальцем.
Исправляем недочеты
Однако на самом деле он перемещается не вслед за пальцем, а по осям, сильно напоминающим ортогональные. Это связано с взаимодействием с проекционной матрицей и матрицей проекции камеры. Есть два способа это исправить. Один из них – изменить направление перемещения:
Matrix.translateM(iMatrix, 0, -diffX, diffY, 0);
Однако лучшим решением будет использовать другую матрицу для перемещения вслед за пальцем и домножать на нее заранее. Врезка
- Метамодернизм в позднем творчестве В.Г. Сорокина
- ЛитРПГ - последняя отрыжка постмодерна
- "Ричард III и семиотика"
- 3D-визуализация обложки Ridero создаем обложку книги при работе над самиздатом.
- Архитектура метамодерна - говоря о современном искусстве, невозможно не поговорить об архитектуре. В данной статье будет отмечено несколько интересных принципов, характерных для построек "новой волны", столь притягательных и скандальных.
- Литература
- Метамодерн
- Рокер-Прометей против изначального зла в «Песне про советскую милицию» Вени Дркина, Автор: Нина Ищенко, к.ф.н, член Союза Писателей ЛНР - перепубликация из журнала "Топос".
- Как избавиться от комаров? Лучшие типы ловушек.
- Что делать если роблокс вылетает на windows
- Что делать, если ребенок смотрит порно?
- Почему собака прыгает на людей при встрече?
- Какое масло лить в Задний дифференциал (мост) Visco diff 38434AA050
- О чем может рассказать хвост вашей кошки?
- Верветки
- Отчетность бюджетных учреждений при закупках по Закону № 223-ФЗ
- Срок исковой давности как правильно рассчитать
- Дмитрий Патрушев минсельхоз будет ли преемником Путина
- Кто такой Владислав Поздняков? Что такое "Мужское Государство" и почему его признали экстремистским в России?
- Как правильно выбрать машинное масло в Димитровграде?
- Как стать богатым и знаменитым в России?
- Почему фильм "Пипец" (Kick-Ass) стал популярен по всему миру?
- Как стать мудрецом?
- Как правильно установить FreeBSD
- Как стать таким как Путин?
- Где лучше жить - в Димитровграде или в Ульяновске?
- Почему город Димитровград так называется?
- Что такое метамодерн?
- ВАЖНО! Временное ограничение движения автотранспортных средств в Димитровграде
- Тарифы на электроэнергию для майнеров предложено повысить
Для этой цели мы поместим в наш код следующий дополнительный фрагмент:
private float[] tMatrix = new float[16];
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
[ ... ]
Matrix.setIdentityM(tMatrix, 0);
}
public void onDrawFrame(GL10 gl) {
Matrix.translateM(tMatrix, 0, diffX, diffY, 0);
Matrix.setIdentityM(uMVPMatrix, 0);
Matrix.multiplyMM(uMVPMatrix, 0, mMatrix, 0, uMVPMatrix, 0);
Matrix.multiplyMM(uMVPMatrix, 0, tMatrix, 0, uMVPMatrix, 0);
Matrix.multiplyMM(uMVPMatrix, 0, vMatrix, 0, uMVPMatrix, 0);
Matrix.multiplyMM(uMVPMatrix, 0, projMatrix, 0, uMVPMatrix, 0);
Matrix.multiplyMM(uMVPMatrix, 0, iMatrix, 0, uMVPMatrix, 0);
[ ... ]
}
Перед выполнением с новой матрицей любых действий важно так установить значения ее элементов, чтобы она сделалась единичной (в противном случае она будет нулевой и останется такой, что бы вы ни делали).
Затем мы используем ее для передачи значений diffX и diffY и умножаем ее на унифицированную матрицу после поворота, но до умножения на проекционную матрицу/матрицу проекции камеры.
Снова скомпилируйте и запустите программу, и теперь куб должен перемещаться в нужном направлении. О дальнейшем улучшении кода см. во врезке «Начало и окончание движения» на предыдущей странице.
Текстуры
Пока что наш куб был цветным, и это очень мило, но одна из прекрасных возможностей OpenGL – возможность накладывать на грани текстуры. Разумеется, это более гибкий вариант, чем просто раскраска.
Чтобы начать работу с текстурами, которые могут быть довольно сложными, вернемся назад и нарисуем двумерную фигуру. Первое, что нужно сделать – переписать вершинный и фрагментный шейдеры (в CISGLRenderer) так, чтобы в них использовались текстуры вместо цвета:
private final String vertexShaderCode =
“uniform mat4 uMVPMatrix; \n” +
“attribute vec4 vPosition; \n” +
“attribute vec2 aTexture; \n “ +
“varying vec2 vTexture; \n” +
“void main(){ \n” +
“ gl_Position = uMVPMatrix * vPosition; \n” +
“ vTexture = aTexture; \n” +
“} \n”;
private final String fragmentShaderCode =
“precision mediump float; \n” +
“uniform sampler2D u_texture; \n” +
“varying vec2 vTexture; \n” +
“void main(){ \n” +
“ gl_FragColor = texture2D(u_texture, vTexture); \n” +
“} \n”;
Этот код очень похож на предыдущий код шейдера; мы просто убрали операции с цветом и добавили операции с текстурой.
В вершинном шейдере есть атрибут текстуры, из которого создается переменная для передачи фрагментному шейдеру. Затем во фрагментном шейдере используется переменная sampler2D, которая позволяет нам получить образец текстуры (“Uniform” означает, что переменная не изменяется в течение одного вызова шейдера, так что для каждого фрагмента значение остается прежним).
Затем мы используем метод OpenGL texture2D для связи переменной образца текстуры с переменной vTexture, передаваемой из вершинного шейдера.
Потом добавим строку в метод onSurfaceCreated() для получения ссылки на переменную aTexture точно так же, как ранее с переменными vPosition и aColour:
textureHandle = GLES20.glGetAttribLocation(program, “aTexture”);
Наконец, добавим в метод onDrawFrame() строку для рисования квадрата Square с текстурой (мы напишем этот метод через минуту):
square.loadTexture(context, R.drawable.test); square.draw(positionHandle, textureHandle, context, R.drawable.yurt);
Заметьте, что для получения переменной context мы добавили конструктор в класс CISGLRenderer – просмотрите код на нашем DVD. Это понадобится для загрузки битовой карты в методе loadTexture().
Вам также понадобится битовая карта, здесь это фотография (юрты!), но вы можете заменить ее на любое другое изображение, сохранив его в res/drawable.
Перейдем к Square.java и нашему новому методу draw(). Для начала нужно загрузить текстуру:
public void draw(int positionHandle, int textureHandle, Context context, int bitmap_id) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), bitmap_id);
GLES20.glDeleteTextures(1, textures, 0);
GLES20.glGenTextures(1, textures, 0);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[0]);
// Установка MIN_FILTER нужна, когда не применяются mipmaps
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D,
GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
[ ... ]
}
Сначала нужно получить битовую карту из ресурсов приложения (учтите, что она должна быть квадратной, в противном случае она может выглядеть странно. Также хорошо обрабатываются текстуры, высота и ширина которых являются степенями двойки).
После этого мы задаем текстуры. glDeleteTextures() удаляет все текстуры, уже находящиеся в первом элементе массива textures, чтобы исключить все возможные конфликты при обращении к нему. glGenTextures() генерирует имя текстуры (мы взяли всего одно, так как используется одна текстура) и сохраняет его в массиве. glActiveTexture() указывает, какой модуль текстуры сделать активным, здесь используется базовый модуль текстуры (GL_TEXTURE0). Наконец, glBindTexture() связывает указанную цель (GL_TEXTURE_2D) с первым элементом нашего массива textures.
В следующей строке задается параметр MIN_FILTER для цели нашей текстуры (GL_TEXTURE_2D). Этот параметр управляет тем, как OpenGL вычисляет текстуру для пикселя, когда этот пиксель попадает в место, выходящее за пределы одного элемента текстуры. Представьте себе текстуру как кусок целлофана, покрывающий поверхность; OpenGL сжимает или растягивает его, чтобы он плотно прилегал к поверхности, и уменьшающая функция – часть этот процесса. Если вы не пользуетесь множественными отображениями (см. следующий абзац), возможные варианты – GL_NEAREST, при котором используется элемент текстуры, ближайший к центру обрабатываемого пикселя, или GL_LINEAR, при котором используется средневзвешенное значение четырех ближайших элементов текстуры. С GL_NEAREST изображение обычно получается чуть более четким, но лучше поэкспериментировать и посмотреть, что дает лучшие результаты в конкретном приложении.
Множественное отображение [mipmapping] – это способ предоставления нескольких версий изображения для различных масштабов. По сути это означает, что вместо генерации меньшей версии основного изображения на лету рендерер может взять готовое изображение меньшего размера с меньшей детализацией из набора отображений и воспользоваться им.
Рендереры также могут интерполировать изображения разного масштаба для получения изображений произвольного размера. Это позволяет проделывать масштабирование более эффективно и заодно уменьшить количество артефактов.
Если вы пишете игры или другие программы, интенсивно использующее процессор, обязательно используйте множественное отображение. Здесь мы ограничимся одной версией одного изображения и оставим работу рендереру. Можно задать и другие параметры, но для данного рисунка они не нужны. Например:
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
Здесь задан параметр оборачивания для s- и t-координат текстуры (они соответствуют x- и y-координатам) для GL_REPEAT.
Наконец, с помощью вспомогательного метода из GLUtils мы задаем нашу битовую карту в качестве текстуры и удаляем битовую карту, поскольку она нам больше не нужна.
Еще нужно задать переменную squareTextureBuffer точно так же, как мы ранее задавали буфер цвета, затем добавить несколько строк в draw(), и все будет готово:
float verticesTexture[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f,
}
public Square() {
[ ... ]
squareTextureBuffer = ByteBuffer.allocateDirect(
verticesTexture.length * 4).order
(ByteOrder.nativeOrder()).asFloatBuffer();
squareTextureBuffer.put(verticesTexture).position(0);
}
public void draw(...) {
[ ... ]
// Удалить строки __colourHandle__ заменив этим:
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 0, squareTextureBuffer);
GLES20.glEnableVertexAttribArray(textureHandle);
[ ... ]
}
Обратите внимание, что координаты вершин текстуры – в диапазоне от 0 до 1, так как оригинал текстуры находится в левом нижнем углу битовой карты, а не в центре экрана (как в случае с окном OpenGL). Однако, скомпилировав и запустив программу, вы увидите, что изображение выглядит не совсем правильно.
Чтобы лучше понять, что происходит, взгляните на схему на предыдущей странице. Рисуя квадрат, OpenGL проходит вершины в том порядке, в котором они перечислены.
Но так как мы пользуемся методом прорисовки «с разделением на треугольники», то на самом деле он возьмет первые три координаты – (-0.5,-0.5) = левый нижний угол, (0.5, -0.5) = правый верхний угол, (-0.5, 0.5) = левый верхний угол – и нарисует первый треугольник, затем возьмет его последнюю сторону и использует ее вершины в качестве первых двух вершин второго треугольника, затем добавит к ним последнюю координату, (0.5, 0.5) = правый верхний угол. Два треугольника образуют квадрат, и на этом процесс завершен, как показывает диаграмма.
Восстанавливаем порядок
Текстура рисуется так же, вершина за вершиной. Однако текстуры OpenGL начинаются в левом нижнем углу, тогда как большинство изображений на компьютере – в левом верхнем.
Поэтому ось y переворачивается (хотя ось x по-прежнему идет слева направо), и это нужно учитывать при прорисовке.
Таким образом, чтобы порядок сегментов был верным, изменим очередность вершин. Они должны идти в направлении прорисовки квадрата и быть перевернутыми (прорисовываться сверху вниз, а не снизу вверх). Диаграмма должна помочь вам представить это. На DVD вы найдете код для трех различных порядков вершин и результаты его запуска.
Если у вас проблемы с шейдерами, с помощью GLES20 можно получить небольшое количество отладочной информации из программы OpenGL и шейдеров.
Если у вас еще более странные результаты, кроме проверки порядка вершин попробуйте также изменить размер исходного изображения с текстурой и/или задать параметры GL_TEXTURE_WRAP, о которых мы говорили ранее.
Трехмерные текстуры
Итак, у нас есть квадрат с текстурой; а как насчет куба с текстурами? Можно просто распространить код для квадрата на куб (будьте внимательны с порядком вершин!), но если вы попробуете скомпилировать и запустить его, то увидите, что текстура появилась только на передней грани куба (а если вы вернули еще и код поворота, то только на задней грани). Другие грани нечеткие.
Чтобы это работало правильно, нам, к сожалению, придется отказаться от рисования куба треугольниками и использовать список вершин в порядке прорисовки (по четыре вершины на грань). Затем мы указываем четыре координаты углов текстуры, один раз для каждой грани. Весь код можно найти на DVD. Также нужно немного изменить вызов glDrawElements(), изменив метод прорисовки с GL_TRIANGLE_STRIP на GL_TRIANGLES.
В остальном код почти такой же, что и для квадрата, но буферам даны новые имена (кроме того, битовая карта задается в конструкторе, а не в методе draw(); какой способ выбрать, зависит от личных предпочтений и других возможных действий с кубом). Скомпилируйте и запустите программу, и вы увидите куб с текстурами на всех гранях. Можете изменить порядок отрисовки вершин текстуры, чтобы она располагалась правильно на всех гранях куба; вам поможет чертеж куба с порядком прорисовки его вершин (Найти правильный «путь наверх» в кубе может быть несколько сложнее, чем в плоском квадрате!). Другой способ прорисовки изображения на всех гранях куба – CubeMap, но он выходит за рамки этой статьи. Раскраску граней можно вернуть, чтобы ее было видно вместе с текстурами (просто восстановите значения vColour и aColour и добавьте vColour к gl_FragColour).
Конечно, с OpenGL можно сделать намного больше, и в Сети есть масса ресурсов вам в помощь. Особенно интересно поиграть с освещением; также стоит подумать о перемещении точки обзора по экрану. |