Художники нарисовали яркую графику, программисты встроили ее в игру, аниматоры добавили движения — казалось бы, все, готово. Но нет, менеджерам не нравится:
Смотришь игровые ресурсы — нет в них ничего такого, все спрайты обрезаны. Читаешь код — формулы правильные, точности шейдера хватает. Но результат все равно получился неважный. Где ошибка?
Небольшой опрос для тех, кто уже знает откуда берутся артефакты. Что делать в такой ситуации?
Какой вариант ответа правильный, почему именно он и как побороть артефакты графики читайте под катом.
На мобильных платформах используется билинейная фильтрация текстур, чтобы спрайты могли двигаться плавно и на разных разрешениях не расплываться на пиксели. В терминах OpenGL ES это параметр GL_LINEAR:
Пример увеличения в 4 раза (по центру GL_NEAREST, справа GL_LINEAR).
Пример уменьшения в 2 раза (по центру GL_NEAREST, справа GL_LINEAR).
В игре видны белые артефакты у веток деревьев, хотя в редакторе ничего такого нет. По описанию все как здесь
Когда текстура нарисована с масштабом отличным от 1.0 или с дробными координатами, на границе с прозрачностью возникают цветовые артефакты. Дело в том, что OpenGL ES для обычных RGBA-текстур сначала независимо интерполирует каналы R,G,B,A, и лишь потом этот результат смешивается с экраном. Если для канала с альфой (A) интерполирование выглядит естественным, то для цветовых каналов R,G,B ранее невидимые пиксели начинают влиять на видимых соседей на границе объекта.
Как одно из решений проблемы предлагается использовать текстуры с предумноженной альфой (premultiplied alpha), тогда интерполирование будет происходит без артефактов. Именно режим premultiplied alpha полезен при отрисовке графики в таргет, в частности при самостоятельной реализации сглаживания 3D-моделей средствами OpenGL ES 2.0.
Альтернативное решение — добавлять обводки в прозрачных областях. Для этого прозрачные пиксели заимствуют цвет от непрозрачного соседа или усредняют цвета от нескольких непрозрачных соседей. Такой расчет удобно проводить фронтом волны от границы прозрачного с непрозрачным.
Пример 2-х пиксельной обводки в прозрачной области (крайняя справа)
Кроме того, можно собрать графические наборы под все разрешения и рисовать спрайты только в целых координатах. На retina-экранах движение будет достаточно плавным. Только вот графики будет очень много.
Стоит лишь задуматься, как происходит рисование текстуры в нецелых координатах в единичном масштабе, как сразу возникает проблема алиасинга границы. Дело в том, что железо рассчитывает в пиксельном шейдере физические пиксели, и для нашей дробной границы происходит смещение текстурных координат s,t за пределы [0..1]. В режиме GL_CLAMP_TO_EDGE при выходе за границы текстуры происходит дублирование рамки. Для текстур, которые стыкуются, это нормально, а вот для различных спрайтов возникают утолщения.
Чтобы не рябили края спрайта достаточно сделать однопиксельную (для масштаба 1) или 2-х пиксельную (для масштаба 0.5) прозрачную рамку и рисовать ее как часть спрайта. Иными словами, команда trim в графическом редакторе не только удаляет прозрачность, но и добавляет проблему алиасинга. Поэтому правильнее будет делать trim, а вслед за ним увеличивать полотно относительно центра на 2 пикселя (обычная графика) или 4 пикселя (retina графика с возможностью downscale).
Пример алиасинга границы (сверху) и плавного движения (снизу)
Для 3D, кстати, идея overdraw тоже работает, но реализация требует предварительных расчетов и формирования дополнительных треугольников с прозрачностью.
При помещении текстуры в атлас теряется свойство режима GL_CLAMP_TO_EDGE, ведь теперь он работает на границах всего атласа. А раз нет дублирования границ у элементов атласа, то и стыки могут сломаться.
Просветы в меде при помещении текстур в атлас
Тут на помощь приходит дублирование внешней границы стыкующихся элементов в самом атласе. Размер рамки зависит от масштаба отрисовки, обычно хватает 1-2 пикселей. При масштабе >= 1 достаточно однопиксельной рамки. Лишь в случае уменьшения требуется 2 и более пикселей.
Чтобы игра могла работать на устройствах с небольшим размером оперативной памяти приходится сжимать атласы в один из доступных форматов: PVRTC, ETC1, DXT1, DXT5, ETC2 и т.д.
Особенность этих аппаратных форматов — разделение всей текстуры на блоки размером 4x4 и дальнейшее сжатие с потерями. Помимо блочности, для каждого блока доступно как правило не более 2 базовых цветов. В итоге все 16 пикселей блока получаются выбором из 4 возможных значений, рассчитанных каким-то образом на основе базовых цветов.
Для форматов с независимыми блоками (ETC1/2, DXT1/5) достаточно расширить каждый элемент, который помещается в обычный атлас, до прямоугольника с размером кратным 4 пикселям. В таком случае соседние элементы не будут влиять друг на друга.
Пример обособленности элементов атласа в блоках 4x4
Для формата PVRTC, где почти на каждый пиксель оказывают влияние цвета 4 смежных блоков, приходится разводить элементы атласа на расстояние в 4 пикселя. Это достигается за счет формирования 2-х пиксельной защитной области по контуру каждого элемента атласа, причем полезная область в этом случае центрируется.
Для полигональных атласов помогает соблюдение дистанции между соседними элементами.
Рассмотренные методы объединяет специальная обработка графических данных: как отдельных текстур, так и элементов атласа. Такая обработка хорошо автоматизируется и, как правило, не отвлекает сотрудников от творческого процесса.
Поэтому в опросе наиболее общий правильный ответ — 6, «Так и должно было получиться, ведь мы не подготовили графические данные». Замечу, что это не умаляет полезности других способов.
Теперь самое время задуматься и спросить: «что умеет делать используемый сборщик ресурсов и предусмотрена ли подготовка графических данных процессами?»
P.S. Приветствуются замечания к рассмотренным методам, а также информация о других эффективных способах борьбы с артефактами в 2D-графике.
«Надо убрать серые пятна и белые линии. Тут пульсирующая кнопка дергается, там прогресс-бар лесенкой идет».
Смотришь игровые ресурсы — нет в них ничего такого, все спрайты обрезаны. Читаешь код — формулы правильные, точности шейдера хватает. Но результат все равно получился неважный. Где ошибка?
Небольшой опрос для тех, кто уже знает откуда берутся артефакты. Что делать в такой ситуации?
- Нужна мощная видеокарта и свежие драйверы;
- Стоит сделать скачиваемые наборы графики для всех возможных разрешений экрана;
- У квадратных текстур с размерами степени двойки нет таких проблем;
- Это все из-за сжатия графики (PVRTC/DXT5/ETC1/...);
- В графическом редакторе придется слегка размазать края;
- Так и должно было получиться, ведь мы не подготовили графические данные;
- Поможет только антиалиасинг;
- Нужны текстуры и таргеты в режиме premultiplied alpha.
Какой вариант ответа правильный, почему именно он и как побороть артефакты графики читайте под катом.
Билинейная фильтрация текстур
На мобильных платформах используется билинейная фильтрация текстур, чтобы спрайты могли двигаться плавно и на разных разрешениях не расплываться на пиксели. В терминах OpenGL ES это параметр GL_LINEAR:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Пример увеличения в 4 раза (по центру GL_NEAREST, справа GL_LINEAR).
Пример уменьшения в 2 раза (по центру GL_NEAREST, справа GL_LINEAR).
В игре видны белые артефакты у веток деревьев, хотя в редакторе ничего такого нет. По описанию все как здесь
Когда текстура нарисована с масштабом отличным от 1.0 или с дробными координатами, на границе с прозрачностью возникают цветовые артефакты. Дело в том, что OpenGL ES для обычных RGBA-текстур сначала независимо интерполирует каналы R,G,B,A, и лишь потом этот результат смешивается с экраном. Если для канала с альфой (A) интерполирование выглядит естественным, то для цветовых каналов R,G,B ранее невидимые пиксели начинают влиять на видимых соседей на границе объекта.
Как одно из решений проблемы предлагается использовать текстуры с предумноженной альфой (premultiplied alpha), тогда интерполирование будет происходит без артефактов. Именно режим premultiplied alpha полезен при отрисовке графики в таргет, в частности при самостоятельной реализации сглаживания 3D-моделей средствами OpenGL ES 2.0.
Альтернативное решение — добавлять обводки в прозрачных областях. Для этого прозрачные пиксели заимствуют цвет от непрозрачного соседа или усредняют цвета от нескольких непрозрачных соседей. Такой расчет удобно проводить фронтом волны от границы прозрачного с непрозрачным.
Пример 2-х пиксельной обводки в прозрачной области (крайняя справа)
Кроме того, можно собрать графические наборы под все разрешения и рисовать спрайты только в целых координатах. На retina-экранах движение будет достаточно плавным. Только вот графики будет очень много.
Пиксельные шейдеры
Стоит лишь задуматься, как происходит рисование текстуры в нецелых координатах в единичном масштабе, как сразу возникает проблема алиасинга границы. Дело в том, что железо рассчитывает в пиксельном шейдере физические пиксели, и для нашей дробной границы происходит смещение текстурных координат s,t за пределы [0..1]. В режиме GL_CLAMP_TO_EDGE при выходе за границы текстуры происходит дублирование рамки. Для текстур, которые стыкуются, это нормально, а вот для различных спрайтов возникают утолщения.
Чтобы не рябили края спрайта достаточно сделать однопиксельную (для масштаба 1) или 2-х пиксельную (для масштаба 0.5) прозрачную рамку и рисовать ее как часть спрайта. Иными словами, команда trim в графическом редакторе не только удаляет прозрачность, но и добавляет проблему алиасинга. Поэтому правильнее будет делать trim, а вслед за ним увеличивать полотно относительно центра на 2 пикселя (обычная графика) или 4 пикселя (retina графика с возможностью downscale).
Пример алиасинга границы (сверху) и плавного движения (снизу)
Для 3D, кстати, идея overdraw тоже работает, но реализация требует предварительных расчетов и формирования дополнительных треугольников с прозрачностью.
Атласы
При помещении текстуры в атлас теряется свойство режима GL_CLAMP_TO_EDGE, ведь теперь он работает на границах всего атласа. А раз нет дублирования границ у элементов атласа, то и стыки могут сломаться.
Просветы в меде при помещении текстур в атлас
Тут на помощь приходит дублирование внешней границы стыкующихся элементов в самом атласе. Размер рамки зависит от масштаба отрисовки, обычно хватает 1-2 пикселей. При масштабе >= 1 достаточно однопиксельной рамки. Лишь в случае уменьшения требуется 2 и более пикселей.
Сжатые атласы
Чтобы игра могла работать на устройствах с небольшим размером оперативной памяти приходится сжимать атласы в один из доступных форматов: PVRTC, ETC1, DXT1, DXT5, ETC2 и т.д.
Особенность этих аппаратных форматов — разделение всей текстуры на блоки размером 4x4 и дальнейшее сжатие с потерями. Помимо блочности, для каждого блока доступно как правило не более 2 базовых цветов. В итоге все 16 пикселей блока получаются выбором из 4 возможных значений, рассчитанных каким-то образом на основе базовых цветов.
Для форматов с независимыми блоками (ETC1/2, DXT1/5) достаточно расширить каждый элемент, который помещается в обычный атлас, до прямоугольника с размером кратным 4 пикселям. В таком случае соседние элементы не будут влиять друг на друга.
Пример обособленности элементов атласа в блоках 4x4
Для формата PVRTC, где почти на каждый пиксель оказывают влияние цвета 4 смежных блоков, приходится разводить элементы атласа на расстояние в 4 пикселя. Это достигается за счет формирования 2-х пиксельной защитной области по контуру каждого элемента атласа, причем полезная область в этом случае центрируется.
Для полигональных атласов помогает соблюдение дистанции между соседними элементами.
Итоги
Рассмотренные методы объединяет специальная обработка графических данных: как отдельных текстур, так и элементов атласа. Такая обработка хорошо автоматизируется и, как правило, не отвлекает сотрудников от творческого процесса.
Поэтому в опросе наиболее общий правильный ответ — 6, «Так и должно было получиться, ведь мы не подготовили графические данные». Замечу, что это не умаляет полезности других способов.
Теперь самое время задуматься и спросить: «что умеет делать используемый сборщик ресурсов и предусмотрена ли подготовка графических данных процессами?»
P.S. Приветствуются замечания к рассмотренным методам, а также информация о других эффективных способах борьбы с артефактами в 2D-графике.