Идеей для создания приложения послужило видео, в свою очередь вдохновленное дизайном Deus Ex: Human Revolution, а именно его элементами с осколками стекла. За пару дней созрело конечное видение сцены и в итоге за выходные была создана первая версия готового приложения.
Построение сцены
Сцена предельно простая. Она состоит из собственно плавно вращающихся осколков стекла, зависших в воздухе. Осколки разделены на 2 группы — находящиеся вблизи и вдалеке. Объекты вдалеке размыты для придания сцене дополнительного объема. Также есть размытый задний фон произвольного цвета. Камера движется влево-вправо согласно движения пальца по экрану, и таким образом осколки двигаются.
Технически это реализовано так: Осколки стекла размещены по цилиндру вокруг камеры. Осколки, расположенные дальше определенного расстояния, отрисовываются в отдельную текстуру и размываются. Вместе с ними же рисуется и фон. Рисуются сперва размытые объекты вдали и поверх них уже рисуются осколки, находящиеся ближе к камере.
Вот таким нехитрым способом мы и реализовали упрощенный эффект depth-of-field (DOF), которого вполне достаточно для даной сцены. Полноценная же реализация DOF несколько сложнее и более ресурсоемкая. Для этого потребовалось бы рендерить в отдельные текстуры карту глубины сцены, готовую сцену, и ее же еще раз но с размытием. И затем уже рисовать на экран одновременно размытую и четкую сцену, смешивая их согласно карты глубины и параметров фокуса камеры.
Реализация
Так как все объекты в сцене прозрачные, то весь рендеринг производится с различными blending mode. При этом запись в depth buffer не ведется для того, чтобы прозрачные стекла не отсекали объекты за ними. Так как самих стекол немного, то это не вызывает слишком большой повторной отрисовки пикселей. Для создания бликов и отражений объекты осколков рисуются с cubemap размером 128х128.
Порядок отрисовки заднего плана:
1. Очитска FBO с glClearColor нужным цветом.
2. Поверх рисуется маска на весь размер фрейм-буфера. Таким образом получаем декоративные цветные размытые пятна для фона вместо сплошного цвета.
3. Затем отрисовываются стекла для заднего плана. Разрешение 256х256, изображение довольно сильно пикселировано.
4. Размытие всего заднего плана. Низкое разрешение заднего фона практически не заметно.
Отрисовка основной сцены и компоновка двух планов:
1. Очистка экрана.
2. Отрисовка заднего плана.
3. Рендеринг стекол переднего плана.
Эта отрисовка производится без записи в depth buffer, так как все объекты прозрачные.
Размытие объектов на заднем плане
Размытие реализовано последовательной отрисовкой изображения между двумя фрейм-буферами специальным шейдером. Шейдер может делать горизонтальное либо вертикальное размытие, это задается параметрами. Этот способ размытия называется ping-pong rendering. Суть его заключается в том, что сперва текстура из фрейм-буфера А отрисовывается с горизонтальным размытием в фрейм-буфер B, а затем наоборот из В в А, но с вертикальным размытием. Эту процедуру можно повторять необходимое количество итераций для достижения необходимого качества размытия исходного изображения. Пример реализации этого эффекта постпроцессинга был взят давно из какого-то примера bloom, ссылку к сожалению не могу найти.
Примечательно, что современные телефоны и планшеты (и даже весьма старенькие устройства, тоже) могут успевать проводить даже не одну, а несколько итераций размытия достаточно быстро. На практике оказалось, что Nexus 10 выдает стабильные 50-40 fps даже при 6-8 проходах размытия текстуры 256х256, причем одним проходом является полное — горизонтальное + вертикальное размытие.
Подбирая достаточно компромиссное соотношение разрешения текстуры, количества проходов и качества размытия, остановились на трех итерациях и разрешении 256х256.
Mali
В предыдущей статье я закинул камень в огород nVidia. Это не потому, что я просто так недолюбливаю nVidia — я точно также недолюбливаю любых других производителей железа, которые предоставляют глючные драйвера к своим GPU. Вот, например, при разработке описываемых живых обоев столкнулись с проблемой на Nexus 10. Проблема заключается в некорректном рендеринге в текстуру, причем проявляется это только при изменении ориентации устройства. Каким образом ориентация планшета может влиять на рендеринг в текстуру, для нас остается загадкой, но это факт.
Сперва, для того чтобы убедиться что я просто упустил какой-то нюанс при инициализации контекста, написал вопрос на Stack Overflow: stackoverflow.com/questions/17403197/nexus-10-render-to-external-rendertarget-works-only-in-landscape И вот тут стоит похвалить сотрудников ARM за работу их тех. поддержки. Через пару-тройку дней я получил письмо от инженера ARM в котором он предложил дать запрос об этом баге на форум Mali Developer Center. Я подготовил простенькое тестовое приложение и описал шаги для воспроизведения ошибки: forums.arm.com/index.php?/topic/16894-nexus-10-render-to-external-rendertarget-works-only-in-landscape/page__gopid__41612. И через всего лишь 4(!) дня получил ответ о том, что действительно есть баг в текущей версии видео-драйвера для Nexus 10. Самое интересное, что ARM предложил workaround для решения моей проблемы, который чудесным образом помог — надо просто вызывать glViewport() после glBindFramebuffer(). За такую работу тех. поддержки ARM им памятник при жизни надо поставить — сотрудник ARM не поленился найти мой e-mail (а он на Stack Overflow не указан), и инженеры тех. поддержки ARM нашли и решили проблему быстрее чем я даже ожидал.
Всех интересующихся качеством Android на Nexus 10 прошу голосовать за соответствующий баг в трекере Гугла: code.google.com/p/android/issues/detail?id=57391
Результат
Скачать программку можно с Google Play по ссылке: play.google.com/store/apps/details?id=org.androidworks.livewallpaperglass
Описанный метод упрощенного эффекта DOF можно применить не только для сцены с одинаковыми объектами, как в нашем приложении, но и в любых других случаях, где можно отделить основную сцену от фона.
Автор: diamond3