В игровой индустрии работа над визуальным качеством эффектов ведется на стыке искусства и технологий. Особенно это касается мобильных приложений.
В этой статье мы расскажем об использовании чисел с плавающей точкой при отладке шейдеров для мобильных устройств на ПК. Мы уверены, что опыт краснодарской студии Plarium будет полезен для вас вне зависимости от того, какой движок вы используете.
Как мы выявили проблему
В процессе создания эффектов мы постоянно сталкивались с тем, что некоторые из них со временем рассыпаются на пиксели.
Мы исследовали эту проблему и определили причины ее возникновения: оказалось, далеко не все гаджеты подвержены артефактам. В основном это касается старых мобильных устройств или моделей со слабыми характеристиками. Причем некоторые с виду одинаковые эффекты то работали как следует, то теряли плавность движений и приобретали пикселизацию.
В ходе анализа технической документации видеопроцессоров устройств, находящихся в группе риска, мы выяснили, что не все гаджеты имеют полную поддержку 32-битных чисел с плавающей точкой. Именно это вызывает проблемы с артефактами и изображением.
А как там в двоичной системе?
Вспомним, как в двоичной системе выглядят числа с плавающей точкой.
Можно представить такое число как контейнер для хранения фиксированного количества значащих цифр и запятой в любом месте этого числа. Очевидно, что большие числа имеют меньшую точность.
Например, у нас есть значащее число — 123456789. Если поставим запятую после первого знака (1,23456789), то сможем изменять это число с точностью до 0,00000001. Но если запятую поставить перед последним знаком (12345678,9), то точность будет уже 0,1.
Если мы используем 32-битные числа, то любое такое число точно передает 7 знаков. Это позволяет приложению работать около 70 часов без видимых проблем. Речь идет о времени, когда пользователь видит именно запущенную игру, а не на паузе или в свернутом состоянии. Достаточно большой запас, не правда ли?
Но дело в том, что многие мобильные устройства поддерживают только 16-битные числа с плавающей точкой в пиксельном (фрагментном) шейдере. В таких числах всего 4 точных знака, что приводит к заметной потере точности при анимации текстур уже через 10–15 минут.
В поисках решения:
- Мы разделили причины потери точности на три группы:
- При передаче — значение конвертируется из 32 бит в 16 бит.
- При вычислении — попытка операций с числами разного порядка.
- При переполнении — любое число с плавающей точкой имеет свое максимальное и минимальное значение.
Решить проблему на каждом конкретном устройстве нереально, поэтому мы сосредоточились на том, чтобы визуализировать ошибку на ПК при создании эффекта и заранее принять меры по ее устранению.
На ПК нет возможности напрямую эмулировать режим работы с числами 16 бит. Драйвер видеокарты автоматически преобразует все 16-битные инструкции в 32 бита, и визуально отследить потерю точности можно только спустя продолжительное время. Поэтому для визуализации артефактов от потери точности мы написали собственный менеджер времени и расширения для шейдеров.
Менеджер времени
Наше «отладочное» время стартует с опережением на несколько часов. Таким образом художник сразу может увидеть проблемы с эффектом. Также менеджер автоматически сбрасывает время каждый раз, когда приложение находится на паузе или свернуто. Именно в этот момент скачок эффектов наименее заметен игроку.
С появлением в новой версии Unity системы управляемого процесса рендеринга кадра (Scriptable Rendering Pipeline) мы смогли отказаться от отдельного скрипта управления временем и делать все непосредственно при формировании кадра.
Расширения для шейдеров
Идея состояла в том, чтобы перегружать 32-битное число с плавающей точкой так же, как перегружается 16-битное. Нужно было сдвинуть значащую часть на некоторое число бит. Но битовые операции недоступны для Shader Model 2.0, и вдобавок использование более высокой модели шейдеров приводит к тому, что движок перестает предупреждать художников по эффектам о слишком сложных вычислениях для мобильных устройств и бюджет эффекта превышается.
Мы решили сделать максимально точный имитирующий алгоритм, который позволит получить необходимый результат.
- Добавляем некоторое большое число к исходному. Само это большое число определяется количеством знаков основного числа.
- Вычитаем его. Нужно обмануть компилятор, который автоматически сократит операции. Для этого мы вносим незначительную погрешность в число после увеличения.
Таким образом, немного изменяя шейдер, мы получаем визуально идентичное изображение для слабых устройств. Это позволяет нашим художникам контролировать процесс разработки эффектов и сокращает количество ошибок в работе.
Подсказка: frac в помощь
В работе над эффектами стремитесь к тому, чтобы все числа, ответственные за плавность эффекта и требующие точности, находились в диапазоне от 0 до 1. Используйте для этого операцию отсечения целой части — frac(x).
По сути, frac — это приведение числа с плавающей точкой к целому с последующим вычитанием этого целого числа из первоначального. То есть frac = x – floor(x).
Поскольку в этой операции используется изначальное число в исходном виде, потеря точности может происходить и на этапе передачи этого числа внутри шейдера. Поэтому мы рекомендуем использовать время, которое не увеличивается до больших значений. Хорошей практикой будет принудительное обнуление времени в моменты загрузки или в моменты, когда интерфейс игры перекрывает игровой уровень.
Проделанная работа позволила значительно сократить количество визуальных ошибок. Наши художники получили возможность контролировать процесс создания эффектов без длительной и сложной проверки на мобильных устройствах.
Автор: Plarium