Более полу года проработав разработчиком для Android я решил попробовать написать живые обои с использованием OpenGL. Пробежав по сети было обнаружено несколько движков общего назначения и множество любительских поделок. Два достаточно взрослых движка предлагали быстрый нативный код и хорошую документацию:
— Andengine;
— LibGDX.
К несчастью, первый не умеет загружать модели и ограничивается двумя измерениями, во втором же работа с моделями выполнена в расширениях и написана на Java, что приводит к перерасходу памяти и медленной работе.
Unity3D не рассматривался из-за его платности, да и не заточен он для живых обоев.
Linderdaum выполнен полностью в нативном коде, но не содержит обертки для живых обоев и страшно медлителен, на Desire в простой сцене количество кадров в секунду колебалось от 3 до 8.
Просмотр маленьких проектов обнаружил min3d и Rajawali, причем первый написан для OpenGL 1.1 и уже год не поддерживается, а второй, хотя и работает с более продвинутой версией API, не содержит многих полезных функций из первого. Кроме того первый не умеет работать с живыми обоями.
Просмотрев примеры, я все же остановился на min3d. Использование шейдеров не столь актуально для простой сцены, а примеры написаны просто и доступно. Осталось добавить в него поддержку живых обоев и внести все исправления собранные сообществом со времени его последнего обновления.
В принципе, надстроек над OpenGL для живых обоев написано достаточно много, я использовал код от Robert Green с именем пакета net.rbgrn.opengl, которые реализует сервис и все необходимые классы. Для работе с живыми обоями, из min3d пришлось убрать жесткую привязку к активити, после чего стало возможно собрать примеры.
Так как автор давно бросил свое детище, а попытки с ним связаться окончились ничем, на Google Code был создан проект min3d-live-wallpaper-fork, куда и были сохранены результаты проделанной работы.
Хотелось бы сказать несколько слов о модели функционирования живых обоев в Android. Живые обои это сервис выполняемый системой в основном потоке лаунчера, поэтому если они тормозят – интерфейс будет тормозить вместе с ним. При активных жывых обоя, когда пользователь заходит в окно предварительного просмотра создается еще один экземпляр сервиса. Поэтому, если разработчик наставил статических переменных и очень плотно работал с памятью, его ожидают интересные сюрпризы. Избежать их можно тщательно следя за завершением активных потоков, очисткой растровых изображений использования менеджера ресурсов без их статической инициализации.
Так как min3d изначально затачивался для работы с активити, пришлось дописать текстурный менеджер так, чтобы отмена загрузки текстуры в предварительном просмотре не удалял текстуру из фона рабочего стола. Кроме того изначально текстуры можно было добавлять только в блоке инициализации сцены, для динамической загрузки был добавлен не слишком изящный код ожидающий команду загрузки и загружащий ее в цикле перерисовки экрана.
Собственно на этом подготовительные работы заканчиваются, ставим задачу и пытаемся ее решить. Базовый концепт прост до примитивности – вывести на экран две смоделированных сердца, заставить их биться, добавить симпатичный фон и возможно простенькие частицы.
Модель я рисовал в Blender, так как сердце симметрично, была смоделирована его четверть и при помощи модификатора зеркало получена готовая модель. Так как, что пользователь будет видеть только одну сторону сердца, оборотная сторона была отрезана, сцена освещается цветными источниками света, поэтому сердце не текстурировалось. Результирующий файл составил 17 килобайт, что отлично подходит для медлительного Java парсера моделей.
![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/863/76d/67d/86376d67d18b07c3829bf5716be2eb3a.png)
Не долго думая на фон была установлена текстура с рендером из того же Blender. Из-за ограниченности авторской фантазии было решено вывести на фон изящные бокалы. Нарисовав в Inkspace огибающую для бокала я импортировал в Blender файл svg формата, преобразовал кривую в ломаную и применив модификатор Screw получил весьма симпатичный бокал. Клонировав объект и настроив материалы был получен простенький фон:
![](https://habrastorage.org/r/w1560/storage2/ee4/29b/cac/ee429bcac4a49961e6ea643984a75b91.png)
Пришло время реализовывать все это в коде. Как уже было сказано в начале, все функции связанные с живыми обоями были вынесены в отдельные классы. Нам осталось только расширить уже готовую модель. Пишем класс описывающий сервис живых обоев.
Здесь мы наследуемся от класса WallpaperTemplate и инициализируем класс рендера TwoHeartsRenderer. Именнно в этом классе мы будем рисовать сцену. Заготовка этого класса наследуется от CommonRender и содержит два основных перегруженных метода
initScene() – инициализация сцена
updateScene() – обновление сцены
Эти два метода аналогичны методам из min3d, поэтому уже сейчас можно собрать один из готовых примеров и получить свои живые обои. Я же решу поставленную задачу с двумя сердцами.
Первое что нам понадобится это выгруженные в понятный min3d формат модели. Движек поддерживает md2, obj, 3ds. Из этих форматов я остановился на obj, так как он полностью текстовый – разобраться в нем проще всего. Кроме того, Blender содержит готовый экспортер, работающий с материалами. Выгрузив модель, ее необходимо скопировать в папку ресурсов raw приложения для Android. Точку в имени файла необходимо заменить на знак подчеркивания, чтобы соответствовать стандартам именования ресурсов. Теперь загрузим ее в код. Для хранения данных о объектах в min3d существует формат Object3dContainer, а для загрузки моделей парсер Iparser. Загрузка модели заняла всего несколько строк.
Первые две строки инициализируют парсер и устанавливают формат модели, путь в ресурсах и параметр, определяющий необходимость генрирования MipMap карт для текстур. Далее мы загружаем модель в переменную mModelHeartHe и задаем ее размер и позицию. Последняя строка включает работу с освещением.
Модель загружена, для ее добавления в сцену достаточно выполнить команду
Аналогичным образом в сцену добавляются все объекты, при использовании прозрачных текстур необходимо соблюдать очередность добавления от дальнего к ближнему объекту. Дописывать сортировку Z-буффера я не стал, так это сильно ударило бы по производительности.
Как я уже говорил, все операции по модификации сцены выполняются в методе updateScene(). Для вращения модели по оси Z достаточно просто вставить строку
После того, как были реализованы два бьющихся сердца с фоном сцена все еще выглядила примитивно, поэтому было принято решение поизобретать велосипед и написать простейшую систему частиц. Из больших сердец должны были разлетаться много маленьких. Класс Particle, получился несколько безобразным, поэтому здесь я его не привожу. Параметры для частицы задаются в виде скорости, приращения, гравитации, трения и цветового сдвига. Более сложные модели увеличили бы нагрузку на процессор при их обновлении, а более простые не позволили бы реализовать поставленную задачу. Для теста в состав min3d были включены классы ParticleBox.java, ParticleCommon.java, ParticleCube.java и ParticleSprite.java, позволяющие использовать в качестве частицы куб, модель или спрайт. Естественно, самые быстрые частицы состоят из спрайтов.
Спрайт частицы частицы был получен из того же сердца, сглаженного и отрендеренного в Blender с бликом. Получилось вот так![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/393/7e4/2f3/3937e42f3780c9e7e8d9f5c75198c874.png)
Было создано два эмиттера частиц с центрами внутри сердец. Так как сортировки Z-буффера нет, используется простейший способ избежать проблем с прозрачностью. Каждаю последующая частица выводится чуть выше предыдущей. Это конечно не академическое решение, но зато работает быстро.
После запуска эмиттеров сцена выглядит примерно так
![image](https://habrastorage.org/r/w1560/getpro/habr/post_images/9b1/352/ec3/9b1352ec3721cbb1ac95750f00642560.png)
Осталось добавить интерактивнось. Для живых обоев доступно сообщение о нажатии, делим экран на 4 зоны, по клику в зонах сердец запускаем поток, ускоряющий биение на одну секунду.
Для полноценной программы осталось стилизовать меню настроек. В нем пользователь может настроить количество вылетающих частиц или выключить их полностью, а также, указать насколько сильно ускорять биение сердец. Ведь у каждого оно бьется по-разному. Результирующий экран настроек показан ниже. Применен настраиваемые элемент управления RatingBar в форме сердца.
Видео работы приложения доступно по ссылке. Приложение абсолютно бесплатное и не требует от пользователя ни одного разрешения. Доступно с Android Market.
Надеюсь выложенные исходные тексты шаблона живых обоев на min3d помогут начинающим ознакомиться с 3D графикой на Андроиде и применить полученные знания для написания впечатляющих обоев с низким энергопотреблением.
— Andengine;
— LibGDX.
К несчастью, первый не умеет загружать модели и ограничивается двумя измерениями, во втором же работа с моделями выполнена в расширениях и написана на Java, что приводит к перерасходу памяти и медленной работе.
Unity3D не рассматривался из-за его платности, да и не заточен он для живых обоев.
Linderdaum выполнен полностью в нативном коде, но не содержит обертки для живых обоев и страшно медлителен, на Desire в простой сцене количество кадров в секунду колебалось от 3 до 8.
Просмотр маленьких проектов обнаружил min3d и Rajawali, причем первый написан для OpenGL 1.1 и уже год не поддерживается, а второй, хотя и работает с более продвинутой версией API, не содержит многих полезных функций из первого. Кроме того первый не умеет работать с живыми обоями.
Просмотрев примеры, я все же остановился на min3d. Использование шейдеров не столь актуально для простой сцены, а примеры написаны просто и доступно. Осталось добавить в него поддержку живых обоев и внести все исправления собранные сообществом со времени его последнего обновления.
В принципе, надстроек над OpenGL для живых обоев написано достаточно много, я использовал код от Robert Green с именем пакета net.rbgrn.opengl, которые реализует сервис и все необходимые классы. Для работе с живыми обоями, из min3d пришлось убрать жесткую привязку к активити, после чего стало возможно собрать примеры.
Так как автор давно бросил свое детище, а попытки с ним связаться окончились ничем, на Google Code был создан проект min3d-live-wallpaper-fork, куда и были сохранены результаты проделанной работы.
Хотелось бы сказать несколько слов о модели функционирования живых обоев в Android. Живые обои это сервис выполняемый системой в основном потоке лаунчера, поэтому если они тормозят – интерфейс будет тормозить вместе с ним. При активных жывых обоя, когда пользователь заходит в окно предварительного просмотра создается еще один экземпляр сервиса. Поэтому, если разработчик наставил статических переменных и очень плотно работал с памятью, его ожидают интересные сюрпризы. Избежать их можно тщательно следя за завершением активных потоков, очисткой растровых изображений использования менеджера ресурсов без их статической инициализации.
Так как min3d изначально затачивался для работы с активити, пришлось дописать текстурный менеджер так, чтобы отмена загрузки текстуры в предварительном просмотре не удалял текстуру из фона рабочего стола. Кроме того изначально текстуры можно было добавлять только в блоке инициализации сцены, для динамической загрузки был добавлен не слишком изящный код ожидающий команду загрузки и загружащий ее в цикле перерисовки экрана.
Собственно на этом подготовительные работы заканчиваются, ставим задачу и пытаемся ее решить. Базовый концепт прост до примитивности – вывести на экран две смоделированных сердца, заставить их биться, добавить симпатичный фон и возможно простенькие частицы.
Модель я рисовал в Blender, так как сердце симметрично, была смоделирована его четверть и при помощи модификатора зеркало получена готовая модель. Так как, что пользователь будет видеть только одну сторону сердца, оборотная сторона была отрезана, сцена освещается цветными источниками света, поэтому сердце не текстурировалось. Результирующий файл составил 17 килобайт, что отлично подходит для медлительного Java парсера моделей.
![image](https://habrastorage.org/getpro/habr/post_images/863/76d/67d/86376d67d18b07c3829bf5716be2eb3a.png)
Не долго думая на фон была установлена текстура с рендером из того же Blender. Из-за ограниченности авторской фантазии было решено вывести на фон изящные бокалы. Нарисовав в Inkspace огибающую для бокала я импортировал в Blender файл svg формата, преобразовал кривую в ломаную и применив модификатор Screw получил весьма симпатичный бокал. Клонировав объект и настроив материалы был получен простенький фон:
![](https://habrastorage.org/storage2/ee4/29b/cac/ee429bcac4a49961e6ea643984a75b91.png)
Пришло время реализовывать все это в коде. Как уже было сказано в начале, все функции связанные с живыми обоями были вынесены в отдельные классы. Нам осталось только расширить уже готовую модель. Пишем класс описывающий сервис живых обоев.
public class TwoHeartsWallpaper extends WallpaperTemplate {
@Override
public Engine onCreateEngine() {
TwoHeartsRenderer mRenderer = new TwoHeartsRenderer(this);
return new WallpaperEngine(getBaseContext(), mRenderer.renderer);
}
public void onDestroy() {
super.onDestroy();
}
}
Здесь мы наследуемся от класса WallpaperTemplate и инициализируем класс рендера TwoHeartsRenderer. Именнно в этом классе мы будем рисовать сцену. Заготовка этого класса наследуется от CommonRender и содержит два основных перегруженных метода
initScene() – инициализация сцена
updateScene() – обновление сцены
Эти два метода аналогичны методам из min3d, поэтому уже сейчас можно собрать один из готовых примеров и получить свои живые обои. Я же решу поставленную задачу с двумя сердцами.
Первое что нам понадобится это выгруженные в понятный min3d формат модели. Движек поддерживает md2, obj, 3ds. Из этих форматов я остановился на obj, так как он полностью текстовый – разобраться в нем проще всего. Кроме того, Blender содержит готовый экспортер, работающий с материалами. Выгрузив модель, ее необходимо скопировать в папку ресурсов raw приложения для Android. Точку в имени файла необходимо заменить на знак подчеркивания, чтобы соответствовать стандартам именования ресурсов. Теперь загрузим ее в код. Для хранения данных о объектах в min3d существует формат Object3dContainer, а для загрузки моделей парсер Iparser. Загрузка модели заняла всего несколько строк.
IParser parser = Parser.createParser(Parser.Type.OBJ, mContext.getResources(), "com.two_hearts:raw/heart_cut_obj", false);
parser.parse();
Object3dContainer mModelHeartHe = parser.getParsedObject();
mModelHeartHe.scale().x = mModelHeartHe.scale().y = mModelHeartHe.scale().z = mModelHeartHeScaleCurrent;
mModelHeartHe.position().z = 4;
mModelHeartHe.lightingEnabled(true);
Первые две строки инициализируют парсер и устанавливают формат модели, путь в ресурсах и параметр, определяющий необходимость генрирования MipMap карт для текстур. Далее мы загружаем модель в переменную mModelHeartHe и задаем ее размер и позицию. Последняя строка включает работу с освещением.
Модель загружена, для ее добавления в сцену достаточно выполнить команду
_scene.addChild(mModelHeartHe);
Аналогичным образом в сцену добавляются все объекты, при использовании прозрачных текстур необходимо соблюдать очередность добавления от дальнего к ближнему объекту. Дописывать сортировку Z-буффера я не стал, так это сильно ударило бы по производительности.
Как я уже говорил, все операции по модификации сцены выполняются в методе updateScene(). Для вращения модели по оси Z достаточно просто вставить строку
mModelHeartHe.rotation().z++;
После того, как были реализованы два бьющихся сердца с фоном сцена все еще выглядила примитивно, поэтому было принято решение поизобретать велосипед и написать простейшую систему частиц. Из больших сердец должны были разлетаться много маленьких. Класс Particle, получился несколько безобразным, поэтому здесь я его не привожу. Параметры для частицы задаются в виде скорости, приращения, гравитации, трения и цветового сдвига. Более сложные модели увеличили бы нагрузку на процессор при их обновлении, а более простые не позволили бы реализовать поставленную задачу. Для теста в состав min3d были включены классы ParticleBox.java, ParticleCommon.java, ParticleCube.java и ParticleSprite.java, позволяющие использовать в качестве частицы куб, модель или спрайт. Естественно, самые быстрые частицы состоят из спрайтов.
Спрайт частицы частицы был получен из того же сердца, сглаженного и отрендеренного в Blender с бликом. Получилось вот так
![image](https://habrastorage.org/getpro/habr/post_images/393/7e4/2f3/3937e42f3780c9e7e8d9f5c75198c874.png)
Было создано два эмиттера частиц с центрами внутри сердец. Так как сортировки Z-буффера нет, используется простейший способ избежать проблем с прозрачностью. Каждаю последующая частица выводится чуть выше предыдущей. Это конечно не академическое решение, но зато работает быстро.
После запуска эмиттеров сцена выглядит примерно так
![image](https://habrastorage.org/getpro/habr/post_images/93b/bc7/50a/93bbc750a17bb330adaba8e192adf273.png)
![image](https://habrastorage.org/getpro/habr/post_images/9b1/352/ec3/9b1352ec3721cbb1ac95750f00642560.png)
Осталось добавить интерактивнось. Для живых обоев доступно сообщение о нажатии, делим экран на 4 зоны, по клику в зонах сердец запускаем поток, ускоряющий биение на одну секунду.
Для полноценной программы осталось стилизовать меню настроек. В нем пользователь может настроить количество вылетающих частиц или выключить их полностью, а также, указать насколько сильно ускорять биение сердец. Ведь у каждого оно бьется по-разному. Результирующий экран настроек показан ниже. Применен настраиваемые элемент управления RatingBar в форме сердца.
Видео работы приложения доступно по ссылке. Приложение абсолютно бесплатное и не требует от пользователя ни одного разрешения. Доступно с Android Market.
Надеюсь выложенные исходные тексты шаблона живых обоев на min3d помогут начинающим ознакомиться с 3D графикой на Андроиде и применить полученные знания для написания впечатляющих обоев с низким энергопотреблением.