Разработка 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.jpg
4abf38be0b5a72421b49760eab402968.jpg

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

Второй - это менеджер виртуальных устройств. Эмуляторов, говоря другими словами. Нажмем на него.

Перед нами появилось окно виртуальных устройств. Скорее всего, если Вы еще не создали ничего без меня, то список устройств будет пуст.

Без паники! Это легко исправить. Кликаем по кнопке “Create Virtual Device…” и выставляем нужные нам настройки. Все просто!

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

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

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

Да, я тоже долго искал и думал, что просто не могу его заметить. Его там нет. Почему-то его не посчитали нужным вынести на панель, но мы это исправим.

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

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

Переходим в файл activity_main.xml и находим там описание TextView.

Оно будет выглядеть примерно следующим образом:

<TextView android:text="Hello World!" android:layout_width="wrap_content"
 android:layout_height="wrap_content" />

Заменяем его этим:

<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 для удобства, но нам нужна только одна:

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
}

Остальное безжалостно удаляем и вот что у нас получается:

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 необходимо создать переменную, хранящую его тип:

private GLSurfaceView glSurfaceView;

И описать ее в методе onCreate():

glSurfaceView = (GLSurfaceView)findViewById(R.id.glSurfaceView);

У класса GLSurfaceView есть метод setRenderer() который принимает в качестве параметра класс Renderer.

Если мы хотим видеть что-то кроме черного экрана в нашем приложении, нам придется создать свой экземпляр этого класса.

Приступим. Создадим новый класс и назовем его OpenGLRenderer:

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) {

   }
}

Здесь все прекрасно, но параметры методов мне не нравяится и я всегда немного все переделываю до следующего вида:

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 добавляем следующий код:

glSurfaceView.setRenderer(new OpenGLRenderer());

/ Я намеренно вернул вас в класс MainActivity для того, чтобы Вы сразу подключили Renderer к glSurfaceView и не забыли о нем.*

*А теперь я намеренно верну вас назад в класс OpenGLRenderer. /

Лучше некуда! Теперь это уже что-то похожее на реальное приложение, но оно совсем скучное и совсем ничего не делает. Мы с Вами совсем скоро это исправим, но, мне кажется, Вы хотите знать что значат все эти методы. И я Вам расскажу.

public void onSurfaceCreated(GL10 gl, EGLConfig eglConfig)

Этот метод вызывается при запуске приложения и всякий раз, при его перезапуске.

В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0 и EGLConfig - EGL конфигурация созданной поверхности(glSurfaceView).

public void onSurfaceChanged(GL10 gl, int width, int height)

Этот метод вызывается при изменении размеров устройства вывода. В нашем случае это дисплей смартфона.

В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0 и размеры поверхности(glSurfaceView) экрана в переменных width и height.

public void onDrawFrame(GL10 gl)

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

В качестве параметров несет в себе GL10 - интерфейс OpenGL1.0.

Мы проделали уже не мало работы, но наше приложение так ничего и не рисует. Это слегка огорчает, не так ли?

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

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

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

На этом все. Ждите следующих статей

:)