Приветствую всех любителей тех устройств, что помещаются в карман, а также тех, кто держит свой карман шире 7". Сейчас мы займемся искусством программирования кросс-платформенных графических приложений, то есть таких приложений, которые работают на мобильной платформе (смартфоны и планшеты Android) и под Windows (стационарные PC, ноутбуки, нетбуки, планшеты). При этом наши приложения будут графическими, графика основана на OpenGL (OGL) и его мобильном варианте OpenGL ES (GLES). Я использую Embarcadero Delphi XE7. Важная особенность – в этом проекте я не использую платформу FM (FireMonkey), мы будем писать все сами и с нуля, как в старые добрые времена.

image

Предлагаю сразу разделить работу пополам:
— я пишу кросс-платформенную графическую библиотеку, рассматриваю примеры её использования, расписываю детали реализации, отвечаю на вопросы;
— ваша часть не менее ответственна: от вас мне нужен тест демок на различных мобильных устройствах, рапорты о выполнении, отзывы и критика, новые идеи и свежее мясо новые пользователи как библиотеки так и собственно приложений.

1. Первая демка
Android mc.apk 3.8 MB yadi.sk/d/n1wDuX1RhC4QX
Windows mc.zip 1.9 MB yadi.sk/d/tBKQxaV7ff8KW
Исходные тексты в репо: sourceforge.net/p/delphioga/code/ci/default/tree
В первой версии программа не хитрая – она просто выводит на экран 4096 угловатых разноцветных шариков и позволяет полетать мимо них в режиме “свободной камеры”. Сферы я раскрасила по принципу цветового пространства RGB, ещё они вращаются. На этом примере я хочу рассмотреть методы построения 3D примитивов (в частности сфер) и приёмы оптимизации отрисовки (Frustum сulling, детализация объектов).
Но нам никак не обойтись без небольшой вводной части.

2. Инструкция по эксплуатации, смартфон/планшет Android
1. Устанавливаем mc.apk, это архив инсталляции программы. Если у вас есть вопросы как это сделать, я готова помочь.
2. При установке apk возможно предупреждение, что программа требует доступа к Internet. На самом деле программа скомпилирована в режиме Debug, поэтому ей потенциально нужен доступ к локальной сети для возможности работы отладчика (gdbserver) и консоли логов logcat. Логи нам еще потребуются. Из самой программы к сети я не обращаюсь никак, там есть только рисование графики на экране.
3. После установки у вас добавится новый ярлычок на Рабочем столе “mc”. Запускаем программу. Любуемся на заставку — огонек FM (я пока не меняла иконки мобильного проекта Delphi по умолчанию). Обращаем внимание на время запуска приложения, сколько оно у вас приблизительно в секундах и их долях? Мы можем сравнить это с временем загрузки примеров FM приложений, пока что сравнение в нашу пользу.
4. Вот вы и на месте, добро пожаловать. Используйте ваши пальцы и тачскрин для изменения направления взгляда и направления движения. Справа вверху есть парочка экранных кнопок – вперед и назад. Если у вас 2 руки, вы можете одновременно двигаться и поворачиваться. Для этого потребуется определенная сноровка, уж чего-чего, а простой жизни я не обещала.
5. При переключении в другое приложение или “засыпании” планшета (давайте мобильное устройство именовать планшетом, потому что у меня планшет) программа приостановит отрисовку графики (контекст GLES и само окно приложения удаляются, так положено делать на Android), однако сама программа не прекратит работу. При переключении обратно в приложение или “пробуждении” планшета окно и контекст будут созданы заново, мы продолжим наше путешествие с места остановки. Следует обращать внимание на этот нюанс при тестировании, некоторые игры не умеют корректно “восстанавливаться”, мы же должны уметь.
6. Поверните планшет горизонтально, потом вертикально. Работу приложения следует проверять в каждом из этих двух режимов. Разумеется такой фокус сработает, только если у вас ориентация дисплея устройства установлена в “авто”.
7. Выйти из демки просто – нажмите системную кнопку “назад”.
8. Напишите в комментариях к статье тип мобильного устройства, которое вы применяли (фирма-производитель, модель), я постараюсь отыскать его характеристики, типы CPU и GPU, версию OS. Эти данные я включаю в файл hardware.txt в репо.

image

3. Инструкция по эксплуатации, Windows
1. Распаковываем архив zip. Не удивляемся объему exe файла, демка скомпилирована в режиме Debug, в релизе exe весит 1.5 MB. Запускаем mc.exe.
Никаких системных предупреждений о использовании сети, как в Android не будет, потому что сеть в демке не используется, на PC отладчик и логи работают локально.
2. Вот вы и на месте, добро пожаловать. Для изменения направления взгляда и направления движения “потащите” экран при нажатой левой кнопке мышки (ЛКМ). Для перемещения в пространстве работают клавиши [WASD] на клавиатуре (либо стрелки, либо стрелки на NumPad). Экранные кнопки для движения вперед-назад тоже есть, они кликабельны. [F1] выводит справку, [ESC] позволяет выйти из программы.
3. Обратите внимание на второе окно (при запуске программы на панели управления появляется два окна), это консоль отладки. Подобную консоль для Android (logcat) мы рассмотрим позже. После GPU Vendor, Renderer (фирма/тип вашей видеокарты) там пишет Open GL version. Скажем так, если версия OpenGL ниже 3.0, то некоторые будущие возможности библиотеки не пойдут на вашей старой видеокарте. Впрочем, прикладное приложение может содержать 2 ветки реализации таких возможностей и работать в любом случае.
4. VSYNC в этом примере выключен, FPS может быть куда больше 60 Гц (попробуйте отвернуться от видимых объектов), нагрузка на GPU и CPU максимальна. Я так делаю для тестирования скорости отрисовки фрейма, если включить VSYNC нагрузка станет нормальной.

4. Отладочный вывод на экран
На скриншотах слева вверху есть некоторые желтые буквы и цифры.
FPS – среднее число кадров в секунду, наш главный инструмент в оценке быстродействия рисования. Под Android VSYNC включен, FPS лимитирован частотой обновления экрана (60 Гц), впрочем я так подогнала нагрузку на GPU в демо, что на большинстве мобильных устройств FPS будет “проседать”. Это не страшно пока управление и анимация остаются адекватными. Под Windows же VSYNC выключен, FPS не имеет лимита;
TDF – среднее время отрисовки одного фрейма в миллисекундах, по сути величина обратная FPS; она нужна чтобы понимать “тайминг” — какого порядка интервалы времени у нас фигурируют для рисования кадра; при FPS 60 Гц это время около 17 мс;
NFR – число отрисованных фреймов от старта приложения; при FPS 60 Гц в первую же секунду эта цифра достигнет 60;
T – время в мс от старта приложения, его я использую как аргумент для функций анимации, в примере от него зависит вращение сфер;
POS — координаты камеры в 3D мире XYZ, при старте оси расположены так: X направо, Y вверх, Z на нас;
AH, AV — углы поворота камеры относительно горизонтали и вертикали в градусах; об угле крена лучше поговорить отдельно;
N – общее число объектов во Вселенной;
NV – столько объектов целиком или частично попадают в область видимости (фрустум, усеченная пирамида поля зрения камеры);
HD – столько ближних объектов отображаются с несколько лучшим качеством отрисовки, дальние объекты рисуются попроще;
SCR — разрешение экрана.

5. Как выглядит процесс разработки
Я подключаю планшет к PC под Windows USB-кабелем (можно и через Wi-Fi). Сначала пишу платформенно-зависимый код неких функций для Android, запускаю программу на планшете. Эта часть работы требует много терпения, приложение сравнительно долго компилируется и запускается на планшете. Отладка доступна (точки остановки, пошаговое выполнение, просмотр состояния переменных). Также использую отладочный вывод приложения в logcat, смотреть логи могу прямо на PC в реальном времени. Когда на планшете заработало, я пишу платформенно-зависимый вариант кода функций для Windows, это уже проще. После этого начинается самое интересное – основную часть программы я пишу универсальным кодом вызывая одноименные платформенные функции, проверяю на PC и лишь изредка заливаю на планшет, как правило, там все работает.

6. Под капотом
Заглянем немного в исходные тексты. В папке common у меня лежит собственно библиотека. Начнем с файла Draw.pas.
На чем основана кросс-платформенная графика? Под Windows я использую модуль OpenGL.pas, но только те функции, которые имеют аналоги в GLES под Android (модули Androidapi.Egl.pas, Androidapi.Gles.pas, Androidapi.Gles2.pas).
uses
  Types,SysUtils,
{$IFDEF Android}
  Androidapi.Egl, Androidapi.Gles, Androidapi.Gles2,
{$ENDIF}
{$IFDEF MSWINDOWS}
  Winapi.OpenGL,
{$ENDIF}
  Vectors, Colors, Textures, DrawPolyhedron, DrawSphere;

Там где OGL и GLES совпадают разницы в синтаксисе мало, вот разве что glOrtho пришлось подровнять. При помощи директив условной компиляции типа {$IFDEF ANDROID} и {$IFDEF MSWINDOWS} мы будем описывать платформенно-зависимую реализацию.
{$IFDEF ANDROID}
procedure glOrtho (left, right, bottom, top, zNear, zFar: GLfloat);
begin
  glOrthof(left, right, bottom, top, zNear, zFar);
end;
{$ENDIF} 

Что ещё не совпадает? На GLES нет glBegin/glEnd и соответственно всего связанного с ними семейства функций. Это не страшно, будем рисовать в стиле OGL 2.0 передавая за один раз указатели на массивы вершин (нормалей, цветов, текстурных координат). Например, один простейший треугольник можно нарисовать так (цвет текущий, нормалей и текстур нет):
procedure Triangle(p1, p2, p3: TV3); overload;
begin
  SetLength(ap3, 3);
  ap3[0] := p1;
  ap3[1] := p2;
  ap3[2] := p3;
  glVertexPointer(3, GL_FLOAT, 0, @ap3[0]);
  glDrawArrays(GL_TRIANGLES, 0, Length(ap3));
end;

Чтобы нарисовать множество треугольников 3D фигуры не следует вызывать процедуру Triangle много раз. Следует сложить все вершины всех треугольников в один массив и нарисовать их единственным вызовом glDrawArrays. Эта функция используется в текущем демо для рисования каждой сферы, как с GL_TRIANGLES так и с GL_TRIANGLE_STRIP:
// отобразить готовый массив вершин c нормалями 
procedure DrawAVNT(avn: TAVertexN; met:GLenum; Position,Rotation,Scale: TV3);
begin
 glPushMatrix;
 Transform(Position,Rotation,Scale);
 glVertexPointer(3, GL_FLOAT, SizeOf(TVertexN), @avn[0].V);
 glNormalPointer(GL_FLOAT, SizeOf(TVertexN), @avn[0].N);
 glDrawArrays(met,0,Length(avn));// GL_TRIANGLES
 glPopMatrix;
end;


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

PS/05.06.2015
В библиотеку добавлена загрузка 3D моделей в формате obj.

image

Дополнительный источник информации по теме: forum.sources.ru/index.php?showtopic=401121

Комментарии (28)


  1. vedenin1980
    20.05.2015 15:27
    +2

    А нельзя кат перенести выше? Ужасно ленту загромождает, сократите текст до ката раза в 3-4, пожалуйста.


  1. Arik
    20.05.2015 15:36

    То чувство, когда до ката контента больше, чем после...)


  1. Blackmorsha Автор
    20.05.2015 15:39
    +1

    Поправилась, текст сократила. Если надо ещё меньше — подумаю над заменой картинки.


  1. Torvald3d
    20.05.2015 15:40
    +1

    Если понравится — у меня уже есть наброски продолжения статьи

    Интересно, что будет дальше. Продолжайте.


    1. Blackmorsha Автор
      23.05.2015 09:46
      +1

      Материала у меня хватает. По тематическим хабам и тегам сложились определенные ленты, я не стану их заспамливать частыми статьями. Все-таки моя тема несколько нестандартная, еще немного и можно её в «Ненормальное программирование» помещать. Сами посудите: разработка приложений для Android не на Java — это уже некая альтернативщина. А тут я беру Embarcadero Delphi XE7 у которого разработка мобильных приложений определена через платформу FireMonkey. Там эти самые приложения можно печь как пирожки. А мне не хочется пирожков из готовых полуфабрикатов, домашняя кухня вкуснее, хотя и требует гораздо больших усилий.


  1. impwx
    20.05.2015 15:58
    +5

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


    1. Blackmorsha Автор
      20.05.2015 16:48
      +1

      Не волнуйтесь, уж если мы залезли «под капот», то не не вылезем оттуда пока не разберем «движок» до гайки. Если идти последовательно, то начать наверное придется с создания окна, инициализации EGL, создания и настройки контекста GLES. Можно подойти с другой стороны и начать с рисования — треугольник, плоские фигуры, 3D примитивы, поверхности, текстурирование, меши. Я как раз собиралась написать в первой версии фигуры вращения и хоть простенькие частицы. Вообще-то начинать принято с выбора среды разработки, но это скользкая тема. А мне бы хотелось начать сразу с тестирования мобильной демки, вон какие подробные инструкции написала. В общем главное начать :))


      1. impwx
        20.05.2015 16:57
        +1

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


        1. Blackmorsha Автор
          29.05.2015 19:39

          Та серия статей от haqreu была про «софт-рендер» (кроме «аддендума» про GLSL шейдеры).
          В шейдерах можно делать многое, на ffp можно делать разное, на CPU (софт-рендер) можно сделать всё © Б.Т.' 2015.

          «Настройка окружения до минимальной рабочей версии» — так это же и есть моя «Первая демка» предъявленная в статье. Этот вопрос уже доступен тем, кто скачал из репо исходники, я там на комментарии и ссылки не поскупилась (модули AppWindow.pas, AppForm.pas).
          Разбирать такое построчно выйдет длинновато и скучновато, не факт что решусь.

          То ли дело рисование граф. примитивов. Вот, фигуры вращения сделала. Вчера было «500 лет граненному стакану», как никак :))

          image


  1. Blackmorsha Автор
    20.05.2015 17:41
    +1

    У haqreu в его «Кратком курсе...» рассмотрено рисование на предельно низком уровне (в хорошем смысле этого слова) — вообще без OpenGL драйвера, чистый софт рендеринг. Я же могу написать о практическом рисовании в реальном времени на экране средствами GPU, только GPU будет мобильный, а полученный текст на GLES будет полностью совместим с OpenGL на PC. Дело в том, что у меня на GLES нет чего-то подобного glu или glut (графической библиотеки), начинать приходится с вывода треугольника. Там даже квада нет (на GL_QUADS круто секономили), я квады из 2-х треугольников рисую.
    Ладно, графика никуда от меня не убежит, надеюсь. ОК, принято. Разберу работу программы от старта (что создаём, что и как инициализируем и зачем) до цикла рисования и обработки тачскрина.
    Еще момент: тексты с OpenGL хорошо переносятся на другие языки программирования. Но под мобильную платформу нужно еще иметь компилятор, который выдаст бинарный .so и архивчик с инсталляцией apk на выходе (Delphi пошел по пути ndk). Поэтому я не даю гарантий, что моя последовательность действий при старте приложения прямо применима для Java (Java это sdk и там несколько иная кухня на машине Dalvik).


  1. Torvald3d
    20.05.2015 17:58
    +1

    Мне повезло и у меня есть две руки, это позволило найти один баг — если сначала начать вращать камеру, а потом не отрывая пальца начать двигаться (стрелочками), после чего оторвать палец от вращения камерой — состояние поворота камеры сбросится на предыдущее.
    Еще один момент — у вас при смене ориентации экрана то ли меняется fov у камеры то ли ее центровка — в общем при вращение наблюдаются явные искажения. Заметил это, запустив демку в вертикальном режиме, потом повернув в горизонтальный.
    Фпс при обзоре всех шариков — около 12.
    Телефон Samsung Galaxy Note 3 n9000, gpu Mali-T628MP6


    1. Blackmorsha Автор
      20.05.2015 19:32

      Зачет, вот что значит прямые руки. Оба замечания поставила на первое месте в моём todo, после фикса отпишусь.
      Я замечала перескоки камеры при «двуручном» управлении, но срабатывает «синдром разработчика» — как-то так жму что летаю без глюка.
      Поэтому и нужен объективный сторонний тест. Хотелось бы нормально отладить все нюансы простой демки и только потом двигаться дальше. Лично я выступаю за качество софта, функциональность мы еще успеем расширить, потом и статьи хорошие напишем.
      Сам принцип управления полетом следует обсудить. На PC это допустим клавиши WASD + мышь, а как сделать на тачскрине?

      У меня задаётся фиксированный FOVY=90 градусов, по горизонтали это выходит больше * AspectRatio (соотношение сторон дисплея). Угол конечно широковат, тестирую так, такие вещи как угол обзора камеры принято выносить в настройки. При смене ориентации экрана… то же самое, FOVY=90, только теперь Y другой. На практике поворот устройства в вертикальную ориентацию приводит в итоге к «приближению» изображения (перескок координат в сторону стал виден только на низком FPS, он связан с тем, что я тороплюсь применить новые длину и ширину дисплея, они в EGL должны еще «устаканится»). Вывод — при повороте нужно корректировать FOV, точнее считать AspectRatio как обратную величину.

      Модуль Camera.pas, здесь будут правки.

      procedure TCamera3D.Apply(aWidth, aHeight: Single);
      begin
      Aspect := aWidth / aHeight;
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity;
      gluPerspective(FieldOfView, Aspect, zNear, zFar);
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity;

      glRotatef(Pitch, -1, 0, 0);
      glRotatef(Yaw, 0, 1, 0);
      with Position do
      glTranslatef(-x, -y, -z);

      glLightfv(GL_LIGHT0, GL_POSITION, @LightPosition); // in current GL_MODELVIEW projection
      end;


      Еще я явно перестаралась с нагрузкой на GPU в демке. 4096 сфер * 80 треугольников в каждой = 327680 треугольников. Смысл теста был такой — в центре шаро-куба FPS выше, так как в один момент можно наблюдать только часть объектов, фрустум кулинг проверяла.

      Еще раз спасибо за грамотный репорт. Если у Вас есть соображения что можно сделать полезного из моих скромных наработок — напишите, именно Ваше мнение мне будет ценно.


      1. Torvald3d
        20.05.2015 19:56
        +1

        На тачскрине можно сделать так же как и на PC — кнопочки WASD + палец для обзора. WASD можно заменить на виртуальный стик.


        1. Blackmorsha Автор
          20.05.2015 21:45

          Виртуальный стик? У меня примитивный GUI (что впрочем, даёт простор для творчества), кнопки рисую сама, координаты касания на тачскрине определяю, попадание в кнопку вычисляю. Кнопка может быть не квадратной. Пока думалось так — окружность, 4 сектора, повернуть на 45 градусов — получится 4 кнопки, как на пульте телевизора. Стик вижу иначе — круглая область, где в зависимости от точки касания сразу будет направление перемещения по 2 осям (в локальной системе координат камеры). Это все надо пробовать. Юзабилити управления — одна из важнейших задач для меня.
          Свой скайп и VK написала в Диалог.


    1. Blackmorsha Автор
      21.05.2015 06:13
      +1

      Ура, у меня появилась некая «карма» и я поставила свой первый +1 на Хабре. Вот за что поставила: за внимательное чтение статьи и точное выполнение условий авторского эксперимента, за корректный баг-репорт, разумный юмор и позитивное отношение.
      И это хорошо.


  1. MrShoor
    20.05.2015 20:14
    +1

    Пожалуйста, используйте для кода теги:
    <source lang=«delphi»>
    </source>


    1. Blackmorsha Автор
      20.05.2015 20:41

      Спасибо, я искала эту возможность. Сейчас проверю. Рассмотрим функцию поворота камеры в пространстве которая используется в текущем демо. Это тот же модуль Camera.pas.

      // такой поворот имеет вырожденные точки сверху и снизу
      procedure TCamera3D.MoveForvard(V:Single);
      var i:Integer; ah,av: Single; rz,rh,rv,dr:TV3;
      begin
       ah:=Yaw*g2r;   // horizontal angle in XZ plane
       av:=Pitch*g2r; // vertical angle
       // polar to decart from -Z
       dr.x:=cos(av)*sin(ah);
       dr.y:=sin(av);
       dr.z:=-cos(av)*cos(ah);
       Position:=Position+dr*V;
      end;
      

      Для поворота камеры в итоге я планирую использовать другой вариант.
      Вот тут лежит моя предыдущая демка под Win, камера заметно лучше — bionica-game.tumblr.com
      Там есть вопрос с нарастающим креном, углы Эйлера — поворот по 2-м осям непременно даст поворот по 3-й оси.
      Придется выбрать из двух зол, либо ввести управление креном.


      1. MrShoor
        20.05.2015 20:43
        +1

        А для того чтобы проверить — есть кнопочка предпросмотра.

        вот тут


        1. Blackmorsha Автор
          20.05.2015 21:15

          Прошу прощения, про камеру я хотела рассказать Torvald3d, вышла путаница. С другой стороны, это комментарии к моей статье, я тут стараюсь отвечать оптом на все на вопросы читателей. У меня могут быть и встречные вопросы. Я совсем не разбираюсь в хабро-рейтингах, кармах и т.п.
          Задачка: статью выложила сегодня после обеда, сразу получила минуса за неправильно поставленный кат (в ленте смотрелось некрасиво), это справедливо. 17 человек добавили статью в избранное, за проголосовало 4, против проголосовало 6 (кажется все из за ката). Я понимаю что в минусах.
          Вопрос 1: нужно ли писать продолжение статьи при таких раскладах?
          Вопрос 2: может нужно заменить картинку над катом, чтобы в ленте никому не мешало?
          Вопрос 3: те 17 что добавили в избранное, они не все имели возможность голосовать, или имели?


          1. Blackmorsha Автор
            20.05.2015 21:20

            Вопрос 4: сколько мне надо набрать кармы, чтобы, например, поставить + Torvald3d, его репорт попал в точку.


          1. Error1024
            20.05.2015 21:32
            +1

            Делайте продолжение!


            1. Blackmorsha Автор
              29.05.2015 21:59

              Работаю над продолжением статьи.
              Основная проблема — длинные ступни у NPC. Никак не могу подобрать им подходящие ботинки :))

              image


          1. Torvald3d
            20.05.2015 22:20
            +1

            Да не заморачивайтесь вы этими хабарейтингами. Я обычно прислушиваюсь к комментариям и стараюсь их учесть в следующих статьях.
            Писать нужно, учтя все, за что огребли в предыдущих статьях)
            На счет картинки — всегда стараюсь писать поменьше текста до ката и выставить покруче картинку — это привлекает внимание. Из-за большого количества постов, на сплошном тексте обычно взгляд не задерживается.


            1. Blackmorsha Автор
              27.05.2015 15:16
              +1

              Уфф, я сделала-таки фигуры вращения. Еще оптимизировать надо, импорт path с SVG надо бы, сечения solid фигур с «крышками», но это уже детали. Придумала: следующая моя демка (и, возможно, статья) будет посвящена проблеме космического полицейского мусора. Знаете ли Вы, что только с начала этого года 40% аварий НЛО произошло при их столкновении с неопознанными орбитальными мусорными объектами (рус.«НОМО», англ.«UOTO»). Что там только не летает — страшно подумать. Гринпис, спасем орбиту!