Разработка OpenGL приложения под Android. Часть 1. Знакомство.



c8b5ef0c246d3709bb9286d0878a8121.png


Доброго времени суток.

Это вторая часть цикла статей "Разработка OpenGL приложения под Android".

Первую часть можно найти здесь: Разработка OpenGL приложения под Android. Часть 0. Введение.

Начнем с того, что сегодня самый лучший день для того, чтобы написать свое первое приложение для android. Если у Вас что-то не получалось до этого момента - будьте уверены, сегодня все получится!

И так. Вы установили новейшую версию Android Studio и все необходимые компоненты для работы с Android 4.4? Немного пощупали, изучили основные принципы работы в данной IDE и готовы покорять мир 3D графики? Отлично! Тогда приступим к работе.
  • В первую очередь создадим проект и назовем его OGLPart1.
Вы можете назвать его как угодно, но мне так будет удобнее. Уроков будет много и что бы не запутаться в них я буду нумеровать проекты эквивалентно статьям. Здесь все просто. Все, что от Вас требуется это указать имя проекта, папку для его хранения и минимальную поддерживаемую версию android. Стандартная процедура.

  • Теперь перед нами окно выбора шаблона приложения.
21a2dfde14bee2f7a356e520e9eeb99c.jpg

Я рекомендую Вам выбрать Blank Activity. В ином случае Вам может показаться, что в приложении много "лишнего" кода. Для начала этого вполне достаточно.
  • Диалог настройки активити
Он предложит нам сменить имя активити, заголовок .xml файла, содержащего его описание и прочие параметры.

Оставим все как есть.
При желании все это можно сменить позже, так что не пугайтесь и не волнуйтесь если что-то сделали не так.
Жмем кнопку "Finish" и через некоторое время перед нами появляется activity_main.xml файл. Точнее его графическое представление.

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

8405dd5aeea239f803e94888be65bc49.jpg
Слева Вы видите структурное дерево проекта. Именно там нужно выбирать файл для работы с ним.
Сверху расположены различные элементы управления.

Интересными, в данный момент, для нас являются два:

bff4130c2a1ba885e09aaa07dca9b4fd.jpg4abf38be0b5a72421b49760eab402968.jpg
Первый - зеленый треугольник. Эта кнопка служит для компиляции и запуска наших гениальных программ.
Второй - это менеджер виртуальных устройств. Эмуляторов, говоря другими словами. Нажмем на него.

Перед нами появилось окно виртуальных устройств. Скорее всего, если Вы еще не создали ничего без меня, то список устройств будет пуст.
Без паники! Это легко исправить. Кликаем по кнопке "Create Virtual Device..." и выставляем нужные нам настройки. Все просто!

/* Только не забываем, что минимальная версия Android на девайсе тоже должна быть 4.4. */

Готово! Теперь при нажатии на кнопку запуска приложения оно будет запускаться через наш эмулятор.

Перед тем как окунуться с головой в программирование нам нужно закинуть на форму специальный элемент GLSurfaceView. Именно он предназначен для работы с OpenGL командами и вывода изображения на дисплей устройства. Поищите его в списке элементов...
Да, я тоже долго искал и думал, что просто не могу его заметить. Его там нет. Почему-то его не посчитали нужным вынести на панель, но мы это исправим.

Видите TextView с текстом "Hello, world!"? Мы превратим его в GLSurfaceView. Как Вам идея? Мне нравится, а у Вас все равно нет выбора, раз Вы до сих пор читаете.

/* Вообще, Вы можете сделать так с любым элементом или без элемента вообще, просто добавив нужный код в activity_main.xml */

Переходим в файл activity_main.xml и находим там описание TextView.
Оно будет выглядеть примерно следующим образом:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

 <TextView android:text="Hello World!" android:layout_width="wrap_content"
 android:layout_height="wrap_content" />
Заменяем его этим:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

<android.opengl.GLSurfaceView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:id="@+id/glSurfaceView"
 android:layout_alignParentEnd="true"
 android:layout_alignParentBottom="true"
 android:layout_alignParentStart="true"
 android:layout_alignParentTop="true" />
Круто! У нас теперь есть настоящий GLSurfaceView с id glSurfaceView, растянутый на все окно. Почти...
Остались зазоры по краям. Это происходит из-за того, что у RelativeLayout свойство padding по всем сторонам выставлено по умолчанию в 16dp. Что бы это исправить достаточно добавить в его описание такую строчку: android:padding="0dp"

/* Больше я не буду подробно останавливаться на интерфейсе Android Studio и том, что касается визуальной настройки приложения. Наша статья не об этом. Мы знакомимся с OpenGL 1.0. */

  • Переходим к файлу MainActivity.java.

Студия создала для нас несколько функций в классе MainActivity для удобства, но нам нужна только одна:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
}
Остальное безжалостно удаляем и вот что у нас получается:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

package com.freakybrainstorm.oglpart1; /* Здесь у Вас будет название Вашего пакета. У каждого свое */

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;

public class MainActivity extends ActionBarActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
   }
}
Здорово! Теперь, для взаимодействия с нашим GLSurfaceView необходимо создать переменную, хранящую его тип:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

private GLSurfaceView glSurfaceView;
И описать ее в методе onCreate():
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

glSurfaceView = (GLSurfaceView)findViewById(R.id.glSurfaceView);
У класса GLSurfaceView есть метод setRenderer() который принимает в качестве параметра класс Renderer.
Если мы хотим видеть что-то кроме черного экрана в нашем приложении, нам придется создать свой экземпляр этого класса.
Приступим. Создадим новый класс и назовем его OpenGLRenderer:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

package com.freakybrainstorm.oglpart1;

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class OpenGLRenderer  implements GLSurfaceView.Renderer {
   @Override
   public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
      
   }

   @Override
   public void onSurfaceChanged(GL10 gl10, int i, int i1) {

   }

   @Override
   public void onDrawFrame(GL10 gl10) {

   }
}
Здесь все прекрасно, но параметры методов мне не нравяится и я всегда немного все переделываю до следующего вида:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

package com.freakybrainstorm.oglpart1;

import android.opengl.GLSurfaceView;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class OpenGLRenderer implements GLSurfaceView.Renderer {   
   @Override
   public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
      
   }

   @Override
   public void onSurfaceChanged(GL10 gl, int width, int height) {

   }

   @Override
   public void onDrawFrame(GL10 gl) {

   }
}
/* Возможно, у Вас не будет необходимости в этом и Ваша Android Studio выдаст такой код сразу. */

Каракас нашей реализации класса Renderer готов.
Следующим нашим шагом будет его "установка" в glSurfaceView, так что возвращаемся в класс MainActivity и после описания glSurfaceView добавляем следующий код:
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

glSurfaceView.setRenderer(new OpenGLRenderer());
/* Я намеренно вернул вас в класс MainActivity для того, чтобы Вы сразу подключили Renderer к glSurfaceView и не забыли о нем.
А теперь я намеренно верну вас назад в класс OpenGLRenderer. */


Лучше некуда! Теперь это уже что-то похожее на реальное приложение, но оно совсем скучное и совсем ничего не делает. Мы с Вами совсем скоро это исправим, но, мне кажется, Вы хотите знать что значат все эти методы. И я Вам расскажу.
/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig)
Этот метод вызывается при запуске приложения и всякий раз, при его перезапуске.
В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0 и EGLConfig - EGL конфигурация созданной поверхности(glSurfaceView).

/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

public void onSurfaceChanged(GL10 gl, int width, int height)
Этот метод вызывается при изменении размеров устройства вывода. В нашем случае это дисплей смартфона.
В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0 и размеры поверхности(glSurfaceView) экрана в переменных width и height.

/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

public void onDrawFrame(GL10 gl)
Этот метод вызывается для отрисовки текущего кадра. Для простоты можно сказать, что он постоянно работает и постоянно зациклен.
В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0.


Мы проделали уже не мало работы, но наше приложение так ничего и не рисует. Это слегка огорчает, не так ли?
Обещаю, больше в этом уроке не будет ничего скучного. Мы начинаем КВН рисовать!

Весь код класса OpenGLRenderer:

/*http://esate.ru, Freaky_Brainstorm(Lipka Aleh)*/

package com.freakybrainstorm.oglpart1;

import android.opengl.GLSurfaceView;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class OpenGLRenderer implements GLSurfaceView.Renderer {

 private FloatBuffer vertexBuffer;
 private float vertices[];
 private float angle = 0.0f;

 @Override
 public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig) {
      //Устанавливаем цвет очистки("заливки") экрана в черный
      gl.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
      
      //Устанавливаем X, Y, Z координаты точек вершин и записываем их в массив vertices
      vertices = new float[]{
           -0.5f, -0.5f, 0.0f,
           -0.5f, 0.5f, 0.0f,
           0.5f, -0.5f, 0.0f,
           0.5f, 0.5f, 0.0f
      };

      //Создаем буфер вершин, содержащий наш массив
      ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
      byteBuffer.order(ByteOrder.nativeOrder());
      vertexBuffer = byteBuffer.asFloatBuffer();
      vertexBuffer.put(vertices);
      vertexBuffer.position(0);
 }

 @Override
 public void onSurfaceChanged(GL10 gl, int width, int height) {
      //Устанавливаем область отрисовки
      gl.glViewport(0, 0, width, height);
 }

 @Override
 public void onDrawFrame(GL10 gl) {
      //Очищаем буферы. В нашем случае буфер цвета и буфер глубины
      gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

      //Включаем поддержку массивов вершин
      gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
      //Устанавливаем цвет серый
      gl.glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
      //Указываем размер, тип и буфер с точками
      //Третий параметр, который равен нулю, нас пока не интересует
      gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
      //Рисуем по принципу GL_TRIANGLE_STRIP
      gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);

      //Сбрасываем матрицу(об этом ниже)
      gl.glLoadIdentity();

      //Вращаем текущую матрицу(наш объект, если сильно утрировать)
      gl.glRotatef(angle, 1.0f, 1.0f, 1.0f);
      angle++;
 }
}
  • Теперь несколько слов по функции glLoadIdentity():
Эта функция умножает текущую матрицу на единичную и, говоря другими словами, "обнуляет" ее.
Это нужно для того, что бы проделанные нами трансформации корректно отображались на экране.
Наша переменная angle содержит сначала 1, затем 2, затем 3 и так далее. И в каждый кадр у нас происходит поворот на этот угол.
Ели бы мы не сбрасывали матрицу, мы бы поворачивали объект ЕЩЕ НА 1, ЕЩЕ НА 2, ЕЩЕ НА 3 и т.д. градуса, а с применением glLoadIdentity() мы просто поворачиваем НА 1, НА 2, НА 3 и т.д. градуса.
Надеюсь, это понятно. Если нет - попробуйте запустить код без glLoadIdentity().

4ccff89a1cb063b71ca459428f8514d4.jpg
На этом все. Ждите следующих статей :)
Файлы:
OGLPart1.7z (2.64 МБ)
1.2676       7817        24.10.2015 15:03:00        18

0.0697  
24.10.2015 21:12:42
Мощно, никогда не писал под андроид. Прямо начал думать скачать Android Studio )
Ссылка 0.0697  
0  
24.10.2015 21:17:19
Благодарю :)
Сейчас в разработке следующая статья по матрицам. Разберем использование одновременно экранных и мировых координат.
Ждите)
0.0481  
24.10.2015 22:10:03
Круто. Радуют большие и качественные статьи :)
Ссылка 0.0481  
0  
24.10.2015 22:13:44
Спасибо.
Комментарии к статьям радуют не меньше :)
Я только пробую себя в написании статей, может есть какие-то пожелания или вопросы?)
0  
31.10.2015 17:28:35
Спасибо, буду пробовать. android studio целый гиг оказывается весит :D
0.0481  
31.10.2015 17:32:03
Да, а если учесть еще эмуляторы и разные версии андроид, то и того больше :)
0  
31.10.2015 18:16:54
при установке вылезла ошибка установки intel haxm
в лог файле написано

This computer meets the requirements for HAXM, but Intel Virtualization Technology (VT-x) is not turned on. HAXM cannot be installed until VT-x is enabled.
Please refer to the Intel HAXM documentation for more information.


это не повредит выполнению уроков?
0  
31.10.2015 19:30:53
Это средство виртуализации. Включать нужно в BIOS. Что-то похожее было у меня на AMD процессоре. Погуглите:) Эмулятор ругается именно на это.
1.2845  
31.10.2015 18:49:14
Выполнил урок. возникло несколько проблем и при эмуляции не запустилось:

1.
Переходим в файл activity_main.xml и находим там описание TextView.
долго тупил
в итоге: textview находится в content_main.xml, не в activity_main.xml

2.
android:padding="0dp" не сразу понятно, что надо добавлять в RelativeLayout, а не в android.opengl.GLSurfaceView

3. Сразу после того , как добавили
glSurfaceView = (GLSurfaceView)findViewById(R.id.glSurfaceView);
надо добавить
import android.opengl.GLSurfaceView;
а то ругается :)

4. в итоге запуск без девайса не вышел.

C:\Users\zver\AppData\Local\Android\sdk\tools\emulator.exe -avd Nexus_4_API_23 -netspeed full -netdelay none
emulator: ERROR: x86 emulation currently requires hardware acceleration!
Please ensure Intel HAXM is properly installed and usable.
CPU acceleration status: HAX kernel module is not installed!


C:\Users\zver\AppData\Local\Android\sdk\tools\emulator.exe -avd Nexus_5_API_23_x86 -netspeed full -netdelay none
emulator: ERROR: x86 emulation currently requires hardware acceleration!
Please ensure Intel HAXM is properly installed and usable.
CPU acceleration status: HAX kernel module is not installed!
Ссылка 1.2845  
0  
31.10.2015 19:38:45
content_main.xml это тоже самое, студия генерирует разные имена файлов в зависимости от версии SDK.

Это происходит из-за того, что у RelativeLayout свойство padding по всем сторонам выставлено по умолчанию в 16dp. Что бы это исправить достаточно добавить в его описание такую строчку: android:padding="0dp"

Я думал, это понятно :) Просто Вы мало знакомы с android studio :)

На счет зависимостей, да. Я их не указываю в статьях. Студия позволяет легко добавлять их сочетанием клавиш. У меня это ctrl + space
0.2845  
31.10.2015 18:55:42
урок понравился , в целом все быстро получилось 8)
(подключил телефон и запустилось)

не совсем ясен код из MainActivity.java
R.id.glSurfaceView

что такое R.? это объект RelativeLayout?
Ссылка 0.2845  
0  
31.10.2015 19:40:38
Считайте, что R это некий контейнер, содержащий все элементы активити и прочее. Графику из drawable, например :)
0  
31.10.2015 20:41:52
ясненько, пока буду так считать :D
0  
31.10.2015 19:46:14
Еще момент. В конце каждого урока есть архив с проектом, который я собирал. Неясные моменты можно там подсмотреть ;)
0  
31.10.2015 20:41:14
да, в какой-то момент пришлось это сделать =)
1.0762  
11.11.2015 05:50:41
Спасибо за урок
Без отдельного курса по Android Studio конечно никак. Удобнее когда все на одном сайте :D

p.s. @admin форма отправки сообщения к кривой кнопкой (chrome,win7) :)
Ссылка 1.0762  
0.0298  
11.11.2015 08:49:08
Благодарю за фидбек, но курсов по самой среде Android Studio, в ближайшем будущем, не планируется.
0.2244  
12.11.2015 01:23:22
В кои-то веки поправил :)
Кнопки переименовал и сделал разных цветов, т.к. даже сам часто щелкал на нижнюю, вместо того, чтобы отправить.
^