Android opengl es examples

OpenGL ES

Термин OpenGL ES часто попадается мне на глаза. О том, что это такое, можно почитать в Википедии и других местах. Для общего развития мне захотелось узнать, что это за зверь такой. Это статья неспециалиста по данной теме. Написал, чтобы оставить статью на память. Может потом буду возвращаться к статье и дополнять её.

Кстати, пример не заработал на эмуляторе, хотя вроде на форумах говорят, что последние сборки ADT для Eclipse поддерживают OpenGL ES на эмуляторе. Я проверял на своём устройстве. Примеры будут расчитаны только на OpenGL ES 2.0, который занимает 99.8% на устройствах. Говорят, что версия 2.0 сильно отличается от версии 1.1 и изучать платформу по старым примерам смысла нет.

Создаём стандартный проект. В коде главной активности пишем код:

Показать код (щелкните мышкой)

В коде идёт обращение к классу LessonOneRenderer. Создадим его:

Показать код (щелкните мышкой)

В манифесте сообщим системе, что собираемся использовать OpenGL ES версии 2.0:

Из того, что понял из статьи — В проектах используется компонент GLSurfaceView, на котором происходит рисование графики.

В методе onCreate() происходит проверка на поддержку второй версии OpenGL и подключается свой класс, в котором содержится логика отрисовки графики.

Также следует прописать одноимённые методы в методах активности onResume() и onPause().

В классе LessonOneRenderer идёт тарабарщина, которая мне пока не понятна. Я с трудом понимаю, как это можно запомнить и пользоваться. Поэтому я просто скопировал код, скомпилировал проект и запустил на телефоне. Получил работающую программу с цветными треугольниками и доволен как слон. А вы по-прежнему хотите изучать OpenGL ES?

Источник

Полный список

— создаем простейший пример с OpenGL

Продолжаем тему графики, и переходим на следующий уровень, который называется OpenGL ES. Расшифровывается это как OpenGL for Embedded Systems, т.е. OpenGL для встраиваемых систем (android-девайсы в нашем случае).

Пару лет назад я читал книгу по этой теме, делал из нее примеры и, в целом, без особых проблем понял, все что там было написано. Но та книжка была по OpenGL ES версии 1.0. Cейчас эта версия уже устарела и используются версии 2.0, 3.0 и 3.1. Эти версии по API существенно отличаются от 1.0 и несовместимы с ней. Поэтому мне самому придется изучать тему почти заново.

Первый урок будет похож на Урок 141. Мы выполним минимальный набор действий, чтобы заполнить экран каким-либо цветом, но в этот раз сделаем это с помощью OpenGL. Кстати, сразу хочу предупредить, что OpenGL это не обязательно 3D. Сначала мы немного порисуем 2D, а потом добавим объем.

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

Приступим к созданию нашего первого минимального примера. Обсудим его ключевые элементы.

1) Изображение надо на чем-то показывать. Для этого мы будем использовать компонент GLSurfaceView (далее — surface).

2) Изображение кто-то должен создавать, т.е. принимать от нас инструкции, что и как рисовать. Этим будет заниматься Renderer (далее — рендер).

3) Ну и нужна будет проверка, что девайс поддерживает OpenGL 2.0, иначе ничего не будет работать.

Начнем с создания класса рендера. Объект этого рендер-класса мы потом будем передавать в surface, которое в процессе своей работы будет вызывать методы рендера.

Рендер имеет три метода:

onSurfaceCreated — вызывается при создании/пересоздании surface. Т.е. метод будет вызываться при запуске приложения или, например, в уже запущенном приложении при выходе девайса из сна. Здесь будет выполняться установка OpenGL параметров и инициализация графических объектов.

onSurfaceChanged — вызывается при изменении размера surface. Самый распространенный пример — смена ориентации экрана.

onDrawFrame — вызывается, когда surface готово отобразить очередной кадр. В этом методе мы и будем создавать изображение.

Создаем класс OpenGLRenderer, который реализует интерфейс Renderer:

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import static android.opengl.GLES20.GL_COLOR_BUFFER_BIT;
import static android.opengl.GLES20.glClear;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glViewport;

public class OpenGLRenderer implements Renderer <

@Override
public void onDrawFrame ( GL10 arg0 ) <
glClear ( GL_COLOR_BUFFER_BIT ) ;

@Override
public void onSurfaceChanged ( GL10 arg0, int width, int height ) <
glViewport ( 0 , 0 , width, height ) ;

@Override
public void onSurfaceCreated ( GL10 arg0, EGLConfig arg1 ) <
glClearColor ( 0f , 1f , 0f , 1f ) ;
>

В onSurfaceCreated мы вызываем метод glClearColor и передаем ему RGBA-компоненты в диапазоне от 0 до 1. Тем самым мы устанавливаем дефолтный цвет, который будет отображаться после полной очистки surface.

А в методе onDrawFrame мы как раз выполняем эту очистку. Метод glClear с параметром GL_COLOR_BUFFER_BIT очистит все цвета на экране, и установит цвет, заданный методом glClearColor.

В методе onSurfaceChanged мы методом glViewPort задаем область surface, которая будет доступна для вывода изображения. Мы указываем левую нижнюю точку — (0,0) и размеры области — (width, height), т.е. изображение будет выведено на все surface.

Рендер готов. Теперь надо в Activity повесить surface и настроить его.

Читайте также:  Blu ray плеер для андроид

import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.ConfigurationInfo;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity extends Activity <

private GLSurfaceView glSurfaceView;

@Override
protected void onCreate ( Bundle savedInstanceState ) <
super .onCreate ( savedInstanceState ) ;
if ( !supportES2 ()) <
Toast.makeText ( this, «OpenGl ES 2.0 is not supported» , Toast.LENGTH_LONG ) .show () ;
finish () ;
return ;
>
glSurfaceView = new GLSurfaceView ( this ) ;
glSurfaceView.setEGLContextClientVersion ( 2 ) ;
glSurfaceView.setRenderer ( new OpenGLRenderer ()) ;
setContentView ( glSurfaceView ) ;
>

@Override
protected void onPause () <
super .onPause () ;
glSurfaceView.onPause () ;
>

@Override
protected void onResume () <
super .onResume () ;
glSurfaceView.onResume () ;
>

private boolean supportES2 () <
ActivityManager activityManager =
( ActivityManager ) getSystemService ( Context.ACTIVITY_SERVICE ) ;
ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo () ;
return ( configurationInfo.reqGlEsVersion >= 0x20000 ) ;
>

В onCreate мы сначала нашим методом supportES2 определяем, что девайс поддерживает OpenGL ES 2.0 и выше. Если нет, то закрываемся.

Если же все ок, то
— создаем GLSurfaceView,
— методом setEGLContextClientVersion говорим ему, что будем использовать OpenGL ES версии 2
— методом setRenderer передаем экземпляр нашего класса OpenGLRenderer. Теперь этот рендер будет отвечать за то, что будет нарисовано на surface
— методом setContentView ставим surface как основное View для Activity

Кроме этого, необходимо привязать surface к lifecycle-методам Activity: onPause и onResume, вызвав в них одноименные surface-методы.

Все готово. Запускаем

Экран заполнен зеленым цветом. Первое простейшее OpenGL-приложение готово. Не Need For Speed конечно, но с чего то ж надо начинать )

Три момента, на которых я хотел бы еще остановиться

1) Почему-то не работает alpha-компонент в методе glClearColor. Т.е. передаете последним параметром хоть 0 хоть 1, прозрачность не добавляется. На этот вопрос у меня пока ответа нет.

2) Координаты viewport, которые мы задаем методом glViewport никак не влияют на результат, и даже если задать область viewport только в половину surface, все равно в зеленый цвет будет закрашена все surface. По этому поводу я вычитал, что это норма. Метод glClear работает на все surface, независимо от размера viewport.

3) По поводу запуска приложений. Обычно пишут, что OpenGL ES не пашет на эмуляторах. Я не проверял на стандартном эмуляторе, но на Genymotion запуcкается без проблем. На крайняк всегда есть реальный девайс, можно тестить на нем.

Присоединяйтесь к нам в Telegram:

— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.

— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование

— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме

Источник

Изучаем OpenGL ES2 для Android Урок №2. Создание треугольников

Урок №2. Создание треугольников

Основу кода и идеи я черпал отсюда:
1. Сатия Коматинени, Дэйв Маклин, Саид Хашими. Android 3 для профессионалов. Создание приложений для планшетных компьютеров и смартфонов.: Пер. с англ. – М.: ООО «И.Д.Вильямс». 2012 – 1024 с.
2. http://www.learnopengles.com/android-lesson-one-getting-started/

На первом уроке (можно посмотреть здесь https://habrahabr.ru/post/278895/ или здесь albatrossgames.blogspot.com/2016/03/opengl-es-2-android-1-opengl.html#more ) мы с вами научились заливать экран одним цветом с помощью OpenGL ES. Пришла пора рисовать треугольники, а точнее, с помощью треугольников мы нарисуем парусник, который будет циклично двигаться слева направо.

Почему треугольники? Дело в том, что в OpenGL есть только три графических примитива: точка, линия и треугольник. Корпус яхты (трапеция) и море (прямоугольник) нарисованы тоже с помощью треугольников. Как известно, точку в нашем пространстве определяют три координаты (x, y, z). Так как наш рисунок плоский, то у всех точек рисунка одна координата по оси 0z (она перпендикулярна плоскости экрана и идет на нас) будет равна нулю. Для примера я указал координаты по оси 0х и 0у двух крайних точек большого паруса (грота).

В коде определение трех точек грота выглядит так:

Как вы видите, создается массив данных с плавающей запятой. Для каждой точки указывается её координата и цветовая гамма. Первая точка у нас белая, поэтому весовые коэффициенты у красного, зеленого и синего одинаковы и равны 1, а вот две остальные вершины я подсинил для красоты. Обход точек делается против часовой стрелки
Размерность координат в OpenGL условная и будет фактически определяться количеством пикселей экрана устройства по ширине и высоте.
Теперь нужно создать буфер, куда мы перекачаем данные о точках для OpenGL. Связано это с тем, что OpenGL написан на С-подобном языке. Поэтому мы переводим наши данные в другой вид, понятный для OpenGL, выделяя память.

Давайте рассмотрим каждую часть. Во-первых, мы выделили блок машинной памяти, используя ByteBuffer.allocateDirect (); эта память не будет управляться Сборщиком мусора (что важно). Необходимо сказать методу, насколько большой блок памяти должен быть в байтах. Так как наши вершины хранятся в массиве в виде переменных float и занимают 4 байта на каждый float, мы передаем triangle1VerticesData.length * mBytesPerFloat. (Напомню, что private final int mBytesPerFloat = 4;)

Читайте также:  Где посмотреть имя устройства андроид

Следующая строка говорит байтовому буферу, как он должен организовывать свои байты в машинном коде. Когда дело доходит до значений, которые охватывают несколько байтов, таких как 32-разрядные целые числа, байты можно записывать в разном порядке, например, от наиболее значимого значения до наименее значимого. Это похоже на написание большого числа либо слева направо или справа налево. Нам это всё равно, но важно то, что мы используем один и тот же порядок, что и система. Мы организуем это, вызывая order(ByteOrder.nativeOrder()). Наконец, лучше не иметь дело с отдельными байтами напрямую. Мы хотим работать с floats, поэтому вызываем FloatBuffer (), чтобы получить FloatBuffer, который содержит основные байты. Затем копируем, начиная с нулевой позиции, данные из памяти Dalvik в машинную память, вызывая mTriangle1Vertices.put(triangle1VerticesData).position(0);

Память будет освобождена, когда процесс прекращается, поэтому нам не нужно беспокоиться об этом.

Чтобы понять матрицы, нужно для начала разобраться, как мы «видим» объект в OpenGL.
Представьте, что вы держите в руках фотоаппарат и хотите сфотографировать наш парусник.

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

В начале, мы установили цвет фона сине-серый, аналогично, как делали в первом занятии.

Потом разместили камеру, фактически указали координаты т.К. Как вы видите, камера у нас сдвинута по оси 0z на 1,5 единицы (расстояние ОК).

В следующих строчках кода мы указываем координату точки, в которую смотрит камера.

Следующие строчки кода определяют, как ориентирована камера или положение up vector. Из-за неудачных переводов, в разных статьях допущены неточности в этом вопросе. В нашем коде сейчас

Это значит, что камера размещена обычно, так, если бы вы положили её на горизонтальный стол и смотрит на парусник в т.0.


Теперь представьте, что из камеры выходят три вектора, как из т.О. Поставив весовой коэффициент final float upY = 1.0f, мы говорим OpenGL, что вверх будет направлена ось 0У и видим картинку, как в начале статьи.
Но стоит нам поставить вот такие коэффициенты

мы увидим на эмуляторе следующее. Наш парусник будет карабкаться по наклонной вверх.

Камера повернулась на 45 градусов против часовой стрелки, если смотреть на ось 0z. Понятно, что если сделать такую комбинацию

вверх будет смотреть ось 0х камеры и кораблик будет плыть вертикально вверх.
Все наши данные мы передаем методу setLookAtM.
Matrix.setLookAtM(mViewMatrix, 0, eyeX, eyeY, eyeZ, lookX, lookY, lookZ, upX, upY, upZ);

Видимый объем камеры

Рассмотрим следующий кусок кода.

Метод onSurfaceChanged позволяет отработать изменение ориентации самого устройства.
Повернем на эмуляторе наш гаджет и видим такую картину

Не очень красиво, но в принципе то, что мы нарисовали.
Следующая строчка кода устанавливает размер экрана. Сначала устанавливаем координаты нижней точки левого угла экрана (0,0), а потом ширину и высоту экрана.
GLES20.glViewport(0, 0, width, height);
Давайте еще раз рассмотрим наш рисунок:

Объем, который видит наша камера, заключен в объеме усеченной пирамиды (A1,B1,C1,D1, A2,B2,C2,D2).
Сначала мы находим отношение ширины к высоте устройства (ratio).
final float ratio = (float) width / height;
Потом задаем координату по 0х левой и правой стороны видимого параллелепипеда (A1,B1,C1,D1).
final float left = -ratio;
final float right = ratio;
Задаем координату по 0у нижней и верхней стороны параллелепипеда (A1,B1,C1,D1).
final float bottom = -1.0f;
final float top = 1.0f;
Расстояние от камеры до передней стороны (КО1)
final float near = 1.0f;
Расстояние от камеры до задней стороны (КО2)
final float far = 10.0f;
Применяем матричное преобразование
Matrix.frustumM(mProjectionMatrix, 0, left, right, bottom, top, near, far);
Есть несколько различных видов матриц, которые мы используем:
1. Матрица модели. Эта матрица используется для размещения модели где-то в «мире». Например, если у вас есть модель автомобиля, и вы захотите расположить её на расстоянии 1000 м на восток, вы будете использовать матрицу модели.
2. Матрица вида. Эта матрица представляет собой камеру. Если мы хотим, посмотреть на нашу машину, которая находится в 1000 м на восток, мы должны передвинуть себя на 1000 м на восток. Или можно остаться неподвижными, а весь остальной мир передвинуть на 1000 м на запад. Чтобы сделать это, мы будем использовать матрицу вида.
3. Матрица проекции. Так как наши экраны являются плоскими, то нам нужно сделать окончательное преобразование в «проект» нашего вида на экране и получить 3D-перспективу. Для этого используют матрицу проекции.

Определение вершинных и фрагментных шейдеров
Даже самые простые рисунки в OpenGL ES 2.0 требуют создания программных сегментов, которые называются шейдерами. Шейдеры формируют ядро OpenGL ES 2.0. Вершины обрабатываются вершинными шейдерами и имеют дело только с точками вершин. Пространства между вершинами обрабатывают с помощью фрагментных шейдеров, они имею дело с каждым пикселем на экране.

Читайте также:  Гугл поиск для android

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

Скажем, мы определили треугольник, у которого вершины будут красная, зеленая и синяя, а его размер будет занимать 10 пикселей на экране. При выполнении фрагментного шейдера, он будет содержать различный изменяющийся цвет для каждого пикселя. В какой-то точке, что изменение будет красным, но на полпути между красным и синим, это может быть пурпурный цвет.

Рассмотрим наш вершинный шейдер

Через униформы (uniform) в шейдеры передаются внешние данные, которые могут быть использованы для расчетов. Униформы могут быть использованы только для чтения. Униформы могут быть переданы как в вершинный, так и в фрагментный шейдеры. В нашем случае униформа одна — это матрица модели-вида-проекции u_MVPMatrix и передается она в вершинный шейдер. Ключевое слово mat4 означает, что это матрица размером 4х4 состоящая из чисел с плавающей точкой. Униформы никак не связаны с конкретной вершиной и являются глобальными константами. Для названия униформ обычно используют префикс u_.
Атрибуты (attribute) — это свойство вершины. У вершины могут быть различные атрибуты. Например, координаты положения в пространстве, координаты вектора нормали, цвет. Кроме того, вы можете передавать в вершинный шейдер какие-либо свои атрибуты. Важно понять, что атрибут-это свойство вершины и поэтому он должен быть задан для каждой вершины. Атрибуты передаются только в вершинный шейдер. Атрибуты доступны вершинному шейдеру только для чтения. Нельзя определять атрибуты во фрагментном шейдере. В дальнейшем для удобства будем обозначать атрибуты с префиксом a_.

Определим в вершинном шейдере три атрибута:
attribute vec4 a_Position;
Переменная a_Position – атрибут вершины, который имеет дело с положением вершины (координатами), это четырехкомпанентный вектор (vec4).
Атрибут цвета вершины
attribute vec4 a_Color;
Атрибут интерполяции цвета
varying vec4 v_Color;
Рассмотрим код функции main подробнее:
v_Color = a_Color;
Передаем информацию о цвете вершин во фрагментный шейдер.
gl_Position = u_MVPMatrix * a_Position;
Трансформируем положение вершин с помощью матрицы и записываем в новую переменную gl_Position.
Системная переменная gl_Position — это четырех-компонентный вектор, определяющий координаты вершины, спроецированные на плоскость экрана. Переменная gl_Position обязательно должна быть определена в вершинном шейдере, иначе на экране мы ничего не увидим.

Приступим к рассмотрению фрагментного шейдера.

Точность по умолчанию устанавливаем среднюю, так как она не нужна нам высокой в случае фрагментного шейдера. В вершинном шейдере точность по умолчанию высокая.
precision mediump float;
Конечная цель фрагментного шейдера — это получение цвета пикселя. Рассчитанный цвет пикселя должен быть обязательно записан в системную переменную gl_FragColor. В нашем простейшем примере мы не вычисляем цвет пикселя во фрагментном шейдере, а просто присваиваем значение цвета v_color, полученного путем интерполяции из цветов вершин:
gl_FragColor = v_color;

Загрузка шейдеров в OpenGL

Связывание вершинного и фрагментного шейдеров вместе в программе

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

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

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

Установка перспективной проекции

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

Вывод объектов на экран
Вывод производим в методе onDrawFrame(GL10 glUnused)
Чтобы треугольники двигались по оси 0х, применяем матрицу перемещения и задаем увеличение смещения по х на 0,001 за каждое обновление поверхности. Как только х достигает 1 или правого края экрана, обнуляем его.

Источник

Оцените статью