Если вы используете в своей игре спрайты с прозрачностью (а обычно так и бывает, как минимум для UI), то вам, вероятно, стоит уделить внимание к полностью прозрачным пикселям текстур (или «текселам»).
Даже если значение альфа-канала равно 0, с пикселем всё равно связано значение цвета. Этот цвет ни на что не влияет, так ведь? В конце концов, пиксель полностью прозрачен, кому есть дело до его цвета…
Так вот, на самом деле этот цвет важен, если этого не понимать, то можно получить артефакты, которые заметны во многих играх. Чаще всего искажения очень малы и их не заметно, но иногда они действительно бросаются в глаза.
Пример искажений
Пора привести пример из реальной жизни! Вот XMB моей PS3 (главное меню), с демо-версиями нескольких игр.
Сначала выбрана Limbo, потом я просто нажимаю «вверх», чтобы переместиться к The Unfinished Sawn
(кстати, обе игры отличные).
Начало.
Нажимаю «вниз». Limbo спускается вниз.
Фон становится белым.
Артефакты.
Видите, что произошло с областью логотипа Limbo?
Фон сменился на белый фон The Unfinished Swan и в результате «абсолютно белый» логотип Limbo отрисован поверх фона, который тоже полностью белый. Эта область должна быть полностью белой, тогда откуда взялись эти странные серые пиксели?
Вероятнее всего, искажение возникло из-за того, что текстура Limbo использует для полностью прозрачных пикселей неправильные цвета RGB.
Фильтрация текстур
Артефакты на самом деле возникают из-за того, как видеопроцессор фильтрует текстуру при рендеринге спрайта на экране. Давайте рассмотрим его работу на простом примере.
Вот небольшая пиксельная текстура с красным крестом размером 12x12:
А вот его увеличенное изображение, шахматная клетка просто показывает, что это полностью прозрачная область со значением альфа-канала 0.
Можно использовать этот спрайт как значок для отображения в UI здоровья или как текстуру для игровой модели аптечки. (Хотя нет! На самом деле этого делать не стоит!)
Давайте создадим три версии этого спрайта, просто изменив значение цвета пикселей с нулевой альфа-прозрачностью.
Прозрачная область: зелёная. Как выглядит изображение:
Прозрачная область: синяя. Как выглядит изображение:
Прозрачная область: красная. Как выглядит изображение:
(Вы можете скачать файлы и проверить значения RGB прозрачных пикселей)
Эти три спрайта выглядят на экране совершенно одинаково, правда? Это логично: мы всего лишь изменили значение цвета прозрачных пикселей, которые всё равно будут невидимыми.
Но давайте посмотрим, что происходит, когда эти спрайты находятся в движении. Вот увеличенное изображение, чтобы лучше видеть экранные пиксели:
Мы видим тут искажения! Коричневый оттенок у первого спрайта и фиолетовый у второго. У третьего всё правильно, именно так он должен выглядеть.
Давайте рассмотрим синюю версию:
Как мы видим, проблема возникает, когда положение текстуры не соответствует попиксельно экранным пикселям. Это можно объяснить билинейной фильтрацией, которую видеопроцессор выполняет при рендеринге спрайта на экране: при сэмплировании текстуры видеопроцессор усредняет значения цвета ближайших соседних пикселей с запрошенными координатами в вертикальном и горизонтальном направлениях.
Рассмотрим случай, когда положение спрайта не совпадает ровно на половину пикселя:
Каждый экранный пиксель сэмплирует спрайтовую текстуру ровно между двумя текселами. Именно это происходит с пикселем, который видно на изображении: он выбирает спрайтовую текстуру посередине между сплошным красным текселом и прозрачным синим текселом. Средний цвет получится таким: А это частично прозрачный фиолетовый, примерно такой: -
Этот цвет, возвращённый сэмплером текстур, теперь будет примешан к альфа-каналу результата рендеринга (сплошному белому цвету).
Уравнение смешивания имеет вид: Поэтому конечный цвет пикселя на экране будет примерно таким: -
Это нас не устраивает. Правильный результат (который мы получили, когда прозрачные пиксели были красными) будет таким:
— это билинейно интерполированное значение, которое затем смешивается и получается Пиксель на экране выглядит так: -
Как же нам избежать этих неприятных артефактов?
Как избежать этой проблемы
Если вы художник: пусть всё просочится!
Если вы отвечаете за создание графических ресурсов, то обезопасьте свою работу и не доверяйте программистам и движку.
Велика вероятность, что на каком-то этапе конвейера цвета прозрачных пикселей «просочатся» на окружающую их графику. Мы уже видели, как это бывает при билинейной фильтрации текстур, но так может произойти и при генерировании MIP-текстур…
Можно бороться с таким просачиванием цветов… дополнительным просачиванием!
Исходный спрайт
Только RGB
Под этим я подразумеваю то, что перед экспортом графики на диск нужно сначала сделать так, чтобы все непрозрачные пиксели «просочились» в значения RGB соседних прозрачных пикселей (это также называется заливкой (flood-filling) или контурной заливкой (edge-padding)). В этом случае когда прозрачные пиксели в процессе выполнения игры просочатся к своим непрозрачным соседям, то они хотя бы протекут с правильным цветом.
На изображениях выше показан пример из реального мира: атлас спрайтов растительности, извлечённый из GTA V, с альфа-каналом и без него.
Заметьте границу вокруг пикселей с ненулевой прозрачностью: прозрачные пиксели заимствуют цвета своих ближайших видимых соседей. Rockstar не случайно проделала всю эту работу.
В этом процессе нам могут помочь инструменты: у Photoshop есть плагин Solidify,
для Gimp тоже существует плагин…
Также будьте внимательны при экспорте значений RGB прозрачных пикселей, например, при сохранении PNG: многие программы по умолчанию отбрасывают данные RGB прозрачных пикселей и заменяют их при экспорте сплошным цветом (белым или чёрным), чтобы улучшить сжатие.
Если вы программист: используйте Premultiplied Alpha!
Если вы программист, то уже знаете, что не стоит слепо доверять графическим ресурсам, созданным художниками. К счастью, у программистов есть больше возможностей для борьбы с этой проблемой.
Можно использовать инструмент для автоматизации просачивания цвета, о котором мы говорили ранее. Его следует использовать при импорте ресурсов. Но у нас есть гораздо лучшее и более надёжное решение: premultiplied alpha.
Я не буду подробно рассказывать о нём, потому что другие люди написали хорошие описания, например здесь и здесь.
Также крайне рекомендую посты Тома Форсайта (Tom Forsyth): 1 и 2 на эту тему.
Идея очень проста: вместо хранения текстуры как нужно хранить её как Компоненты RGB просто умножаются на значение альфа-прозрачности пикселя. Исходный цвет по-прежнему можно легко получить, разделив значение на альфа-прозрачность.
Так можно превратить спрайт:
Оригинал
Premultiplied Alpha
Также необходимо изменить уравнение смешивания, потому что наша текстура теперь содержит результат первого умножения, и её не нужно снова умножать на значение альфа-прозрачности: В OpenGL это выражается во внесении следующих изменений в функцию смешивания:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
? glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)
Вернёмся к нашему спрайту с красным крестом, смещённому на полпикселя. В случае режима Premultiplied Alpha билинейный интерполятор будет искать среднее между Это правильно и довольно неплохо решает все наши проблемы! Конечный результат получился в точности таким же, какой мы ожидаем при «традиционном смешивании», за исключением того, что мы избавились от всех артефактов. Вы заметите, что при работе с premultiplied alpha полностью прозрачный пиксель всегда имеет значение RGB чёрного цвета, поэтому нам не нужно волноваться о том, что же содержится на самом деле в прозрачных областях спрайта. Premultiplied alpha также позволяет избежать головной боли при генерировании цепочек MIP-текстур и наслоении нескольких просвечивающих спрайтов одного над другим.
Подводим итог
Итак, вернёмся к первоначальной теме: была ли проблема с логотипом Limbo действительно вызвана «мусором» в RGB прозрачных пикселей?
Есть только один способ узнать это, поэтому я извлёк файл PNG из пакета демо
/PS3_GAME/ICON0.PNG
.На первый взгляд, изображение выглядит отлично, но давайте удалим альфа-канал, чтобы визуализировать полные значения RGB:
Оригинал
Только RGB
Так и есть: вместо сплошного белого цвета в буквах «B» и «O» присутствуют неправильные значения RGB, которые «просачиваются» и вызывают виденный нами раньше графический баг.
Проблема с этими артефактами в том, что их сложно обнаружить. Я не замечал ничего странного с логотипом Limbo, пока он не рендерился на белом фоне. Не все знают об этой проблеме, поэтому ознакомление с темой будет полезно.
Если вы художник, то вы — первая линия защиты, уделяйте внимание цветам, которые находятся внутри прозрачных пикселей. Если вы программист, то задумайтесь об использовании premultiplied alpha.
Комментарии (83)
MrShoor
11.05.2017 22:17+5Я по этому поводу даже когда-то тулзу написал. Строю по прозрачным областям DistanceField и заливаю ближайшим цветом из непрозрачной области.
Вот например была такая текстура:
Заголовок спойлераVEG
12.05.2017 16:22По идее же можно заполнить таким образом только полностью прозрачные пиксели, которые соседствуют с не полностью прозрачными. Тогда такие текстуры будут гораздо лучше сжиматься :)
MrShoor
12.05.2017 19:46Ну тулза вроде это и делает. Насчет сжиматься не уверен (если имеется ввиду размер файла), но мипы по ним строить будет можно без опаски.
VEG
12.05.2017 23:23У вас в примере все прозрачные пиксели заполнены разными цветами. Но по идее достаточно сгенерировать подобные цвета только для тонкой линии вокруг не полностью прозрачных пикселей, а остальное залить одним цветом (прозрачным белым или чёрным, например).
homm
12.05.2017 23:46Это плохая идея. При разных операциях при формировании конечного пикселя может учитываться разное количество исходных. При ресайзе будут учитываться 2?scale?W исходных пикселей, где scale — коэффициент уменьшения, а W — окно фильтра. При Гауссовом размытии до трех сигма в каждую сторону.
lgorSL
12.05.2017 01:01Если пиксели строго прозрачные или строго непрозрачные (как в примере с плюсом), можно особо не заморачиваться: достаточно сделать всё прозрачное "прозрачным чёрным" и выбрать подходящую glBlendFunc. (В некоторых графических редакторах "прозрачный чёрный" сам по себе появляется безо всяких усилий со стороны редактирующего).
В случае с полупрозрачными пикселями на краях так не прокатит, придётся делать как в статье.Rampages
15.05.2017 06:59А можно поподробнее, т.е. не совсем понял бывает прозрачный черный и прозрачный белый(не черный)? Просто не очень хорошо разбираюсь в цветовых пространствах.
mayorovp
15.05.2017 08:56Прозрачный черный: 0x00000000 (или 0xff000000, в зависимости от значения, вкладываемого в альфа-канал)
Прозрачный белый: 0x00ffffff (или 0xffffffff, в зависимости от значения, вкладываемого в альфа-канал)
Это особенность не столько цветового пространства, сколько формата представления цвета.
Bel_Riose
12.05.2017 08:47Вот автор пишет формулу для смешивания(линейной интерполяции) RGB цветов,
но ведь вроде для корректного смешивания надо возвести в квадрат, смешать, и извлечь корень?
https://www.youtube.com/watch?v=LKnqECcg6Gw
http://stsievert.com/blog/2015/04/23/image-sqrt/AngReload
12.05.2017 10:01Это нужно только для «гамма-скорректированных» изображений.
В играх текстуры вполне могут быть сразу в линейном sRGB. Не делать же гамма-коррекцию для каждой операции, когда разумнее загрузить в линейном виде, сделать фильтрацию, и уже на выводе финального изображения откалибровать для монитора.
Современные игры учитывают гамму, претензии скорее нужно направлять к софту: графическим редакторам, браузерам.
https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch24.htmlMrShoor
12.05.2017 10:12Не делать же гамма-коррекцию для каждой операции, когда разумнее загрузить в линейном виде, сделать фильтрацию, и уже на выводе финального изображения откалибровать для монитора.
Делать. Точность в sRGB будет выше. Иначе зачем бы заморачиваться с sRGB вообще. Сегодня такое конвертирование крайне дешевое, потому что делается аппаратно на видеокарте отдельными блоками.AngReload
12.05.2017 11:35+2Меня превратно поняли. Мне проще в псевдокоде объяснится:
Вместо:
Загрузить две картинки Возвести значения цветовых каналов изображений в квадрат Наложить изображения Найти корень Опять возвести в квадрат Сделать тонирование Опять найти корень Вывести финальное изображение
Так:
Загрузить изображения Возвести значения в корень Наложить изображения Тонировать Найти корень Вывести результат
То есть, я хочу сказать, что гамма-коррекция не относится к интерполяции или другой обработке, и не следует её пихать в каждый фильтр, а делать непосредственно при операциях ввода-вывода.MrShoor
13.05.2017 03:25и не следует её пихать в каждый фильтр, а делать непосредственно при операциях ввода-вывода.
Если промежуточные вычисления хранятся в большей размености, то я с вами соглашусь. Если промежуточные вычисления хранятся в byte, то придется каждый раз при отправке в byte и извлечении обратно конвертировать.
alexeymalov
12.05.2017 08:57В википедии есть статья на эту тему
https://en.m.wikipedia.org/wiki/Alpha_compositing
msts2017
12.05.2017 11:24-3Удивляет что в редакторах правильно все отображает (масштабирование, то, се) а в движках вылезает.
Aquahawk
12.05.2017 14:11Наконец-то кто-то это написал нормально. Мы именно так и решали это на нескольких проектах, но так руки и не доходили это нормально написать, теперь есть хорошая дока на которую всех сбрасывать можно. Из дополнений только то, что в WebGl например сжатие текстур поддерживается только DXT3 и DXT5, а они не поддерживают премультиплаед альфу, а экономия видеопамяти в 4 раза стоит того. В итоге приходится делать экструзию(то что в статье предлагается делать через soldify-b) граничных пикселей на этапе экспорта ресуров. В нашем случае пришлось реализовать это самим руками, не так сложно как кажется.
SerVB
12.05.2017 14:23Добрый день!
Почему то сразу вспомнил сайт UFO. Метод "вставки чёрных кадров" ( https://www.testufo.com/#test=blackframes ) — это то же, что описано в статье?
pewpew
12.05.2017 15:23-5Получается, это баг видеокарт. И вы предлагаете его костылить подготовкой изображения. При софтовом рендеринге его легко можно избежать.
Tutanhomon
12.05.2017 15:28+1Это принцип работы видеокарт и мониторов (в связке), и предлагается его понимать и уметь правильно с ним работать
pewpew
12.05.2017 15:36-2Из статьи я понял что при отображении изображений, если доверять рендеринг чёрному ящику с невнятными алгоритмами отрисовки субпикселей, могут возникнуть баги. Путей избежать баги два. Первый — написать правильное отображение субпикселей, которое умеет использовать маску, или в случае чёрного ящика с багом, обходить его подготовкой изображений (в вашем случае).
Не могу согласиться с вами и назвать баг принципом работы. Это недоработка, которая тянется из игнорирования альфа-канала при субпиксельном рендеринге.Tutanhomon
12.05.2017 16:36+1Из статьи вы должны были понять, что прозрачный пиксель — тоже пиксель, который не рисуется, но используется в вычислених при сглаживании. Сглаживание — не баг видеокарты/рендеринга, а один из аспектов работы.
pewpew
12.05.2017 16:57-3Что мешает использовать маску прозрачности, вычисляя прозрачные пиксели?
khim
12.05.2017 21:23+1Тот факт, что эпоха GIF'ов с одним прозрачным цветом в палитре давно ушла в прошлое и сегодня прозрачный цвет — это цвет с уровнем прозрачности 100% (или, что то же самое, уровнем непрозрасности (opacity) — 0%). И вот если вводить правила, пристойно работающие для прозрачности 90%, 99%, 99.9%, то на 100% они иногда будут давать неожиданные, для неподготовленного человека, результаты.
pewpew
12.05.2017 21:27-3В каком месте я утверждаю, что пиксели должны иметь дискретную прозрачность? Я предлагаю брать ближние пиксели для вычислений из фона, а не из скрытого альфа-слоем основного слоя.
homm
12.05.2017 17:24Получается, это баг видеокарт.
Что вы называете видеокартой? Видеокарта — это набор железа. Еще есть драйвера, графические API, ваш код шейдеров. Все это заставляет видеокарту работать так или иначе. Видеокарта делает то, что ей скажут. Статья о том, как заставить работать правильно.
pewpew
12.05.2017 18:58-5Видеокарта — это железо. Понять не могу, как так можно запрограммировать баг, описанный в статье.
Ведь понятно, что усреднять пиксели надо принимая во внимание все слои. В статье же описывается, как не принимать во внимание слой фона, на котором рисуется картинка со сглаживанием и субпиксельным отображением. Но в чём проблема принимать во внимание все слои? Отпадёт необходимость городить кучу описанных костылей.
В указанном в статье примере субпиксели какого-то лешего учитывают соседние пиксели только основного слоя, которые должны быть прозрачны под альфа-слоем. Хотя соседние пиксели надо брать из сочетания фона и основного слоя (в примере фон белый, учитывая что прозрачность полная, то совершенно не важно что там в основном слое).
mayorovp
12.05.2017 20:33При чем тут вообще субпиксели? И как вы собираетесь брать сочетание фона и основного слоя — если именно это сочетание и строится неправильно?
pewpew
12.05.2017 20:42-3Переходные пиксели по-вашему как называются?
Ошибка в построении вызвана тем, что граничащие пиксели берутся из слоя, который не видно вместо того чтобы браться из фона. Основной слой становится прозрачным через маску альфа-слоя.
Не понимаю, как вы не понимаете, что баг легко поправить, просто учитывая фон, а не городить огород.mayorovp
12.05.2017 20:44Предложите формулу, раз это так легко сделать.
pewpew
12.05.2017 20:55Формула та же, ничего не меняется. Просто учитывается фон.
Берём красный крест 255,0,0 с синим ключевым цветом 0,0,255 (что не важно, учитывая альфа канал, который превращает его в цвет фона)
Подсчёт пикселей на 50% перекрытии на белом фоне 255,255,255:
255,0,0 + 255,255,255 => 255,128,128, то есть розовый. Никакого синего или фиолетового.mayorovp
12.05.2017 21:26Это для однородного фона так все просто. А если на фоне уже что-то нарисовано?
pewpew
12.05.2017 21:30А какая разница? Синий цвет, который скрыт маской не участвует в вычислениях. Откройте любой редактор, поддерживающий слои и субпиксельные сдвиги, тот же photoshop. Почему там не наблюдается подобных эффектов?
mayorovp
12.05.2017 21:32Разница в том, что прежде чем "просто учитывать цвет фона в формуле", надо понять какой из пикселей фона нужно брать. Вы это забыли сказать.
pewpew
12.05.2017 21:35Точно так же. При перекрытии 30% на 70%, беру 30% левого пикселя фона, 70% правого и смешиваю. Результат считаю пикселем фона. Неоткуда там взяться синему «ключевому» цвету.
mayorovp
12.05.2017 21:36Что такое "левый пиксель фона" и "правый пиксель фона"?
pewpew
12.05.2017 21:38
На этом изображении указанный пиксель перекрывает 2 пикселя фона. Один неизбежно становится левым, второй правым.mayorovp
12.05.2017 21:40Подождите, но вы что и на чем рисуете? Мне почему-то на этой картинке виден один пиксель фона, перекрытый двумя пикселями изображения...
pewpew
12.05.2017 21:43На картинке уже результат. На изображении, которое мы передвигаем пиксель изображения один и граничит с двумя пикселями фона.
mayorovp
12.05.2017 21:45То есть вы предлагаете фактически сначала отрисовать фон на изображении — а потом изображение уже без альфа-канала — на фоне?
При таком способе отрисовки любое изображение будет немного размывать фон — а это неправильно.
pewpew
12.05.2017 21:46То есть правильно смотреть на ключевой синий цвет?
mayorovp
12.05.2017 22:03Правильно делать как написано в этом посте. Смотреть на синий ключевой цвет вам никто и не предлагает.
pewpew
12.05.2017 22:08Как мы видим, проблема возникает, когда положение текстуры не соответствует попиксельно экранным пикселям. Это можно объяснить билинейной фильтрацией, которую видеопроцессор выполняет при рендеринге спрайта на экране: при сэмплировании текстуры видеопроцессор усредняет значения цвета ближайших соседних пикселей с запрошенными координатами в вертикальном и горизонтальном направлениях.
В статье предполагается, что сэмплирование происходит перед наложением на фон. Поэтому фон считается неопределённым и возникает артифакт. Я же предлагаю объединить эти процессы. При семплировании брать ближайшие пиксели уже с фона. Откройте photoshop или gimp. Там нет таких проблем. Прозрачный фон считается прозрачным.mayorovp
12.05.2017 22:10Нет, в статье предлагается два способа решения проблемы, один из которых — костыль, а второй нормальный.
Вы предлагаете более сложный способ.
homm
12.05.2017 22:13Да никто вам не мешает объеденить две операции, если у вас две операции. Просто в статье речь об одной операции, той, которая создает пролемы. В статье общий способ, вы даже сказали что формула та же. Вы же говорите «я знаю, как можно проще» и предлагаете частный.
khim
12.05.2017 21:34+3Формула та же, ничего не меняется. Просто учитывается фон.
Нифига ж себе «просто». А ничего что понятие «фона» в общем случае неопределено?
Рассмотрите простой пример: возьмите два ваших креста и расположите их друг над другом. Сдвиньте один из них на 1/3 пикселя, а второй — на 2/3. И? Что тут будет с фоном?
Подсчёт пикселей на 50% перекрытии на белом фоне 255,255,255:
Если бы в природе был только белый фон и не было бы никакого другого — то никому нафиг бы не сдалась вся эта конструкция. Но что делать, если у вас кроме обьекта и белого фона есть ещё что-нибудь? Другие обьекты, в частности, полупрозрачные.
Вы уж, пожалуйста, изобретите что-нибудь работающее не только для одного частного случая, а?pewpew
12.05.2017 21:41Расположите объекты наложения слоями и подсчитывайте по-очереди, считая фоном результат подсчёта. Фон не определён в общем случае. Но во время построения изображения фоном будет самый нижний слой.
khim
13.05.2017 00:51Расположите объекты наложения слоями и подсчитывайте по-очереди, считая фоном результат подсчёта.
А если обьекты того, отказываются «слоями» располагаться? Мы тут про игру, в общем-то говорим.
Но во время построения изображения фоном будет самый нижний слой.
Это если он существует. А если у вас обьекты расположены так, что нельзя сказать какой из них «ниже»?pewpew
13.05.2017 01:05Я уже уловил, за что меня минусуют. Я не уловил контекста статьи. И того что всем непременно надо сэмплировать текстуры до их рендеринга, иначе я несу ересь по мнению большинства.
И всё же что вы говорите — это те самые условия, которых я предлагаю избегать.
Ещё во времена экранного режима 0x13h, когда вся видеопамять умещалась в 64к не было необходимости делать всё как у всех, было интересно экспериментировать, можно было с интересом написать свой хитрый движок рендеринга и даже свою поделку (игрой назвать сложно) на этом движке. И вот тогда мои слова про то, что подготовку текстур можно делать непосредственно во время рендеринга сцены, вместе с сэмплированием и фильтрациями были бы уместны.
Но теперь многие функции положены на видеокарты, что стало стандартом и проще не изобретать велосипед, а идти по проверенному пути, в котором одни грабли обходятся заранее подготовленными костылями и все так делают, о чём собственно статья. Да, в ней всё верно написано. Но с учётом того частного случая, который стал повсеместно употребляться. Статья как раз о том, как готовить текстуры, шлифуя костыль до блеска.
FishDude
12.05.2017 17:34С этим эффектом столкнулись люди давным-давно-предавно, ещё во времена, когда текстуры были маленькие — и тогда баги были хорошо заметны. Суть: альфа-канал растягивается отдельно, rgb-слои отдельно, и следовательно, из-под альфы по границе обязательно вылазит фон спрайта; а если не дай боже фон был залит каким-то ключевым цветом (ярко-зеленым например), текстуры будут выглядеть просто чудовищно.
Кстати, а ведь маленькие текстуры до сих пор используются. Где? Да везде! Mip-mapping. )) А когда поверх текстуры еще и DXT-сжатие прошлось, баги множатся и расцветают во всей красе.
Вот как мы боролись с этим в Half-Life; по сути, точно такое же заполнение, как рекомендуют здесь в комментариях, но с упрощенным алгоритмом, без distance field.msts2017
15.05.2017 09:23-2Суть: альфа-канал растягивается отдельно, rgb-слои отдельно, и следовательно, из-под альфы по границе обязательно вылазит фон спрайта;
собственно такой должна быть статья плюс картинки, а то воды налили
hdfan2
Напоролся на именно этот баг (особенность?) в cairo (графическая библиотека, которая используется, например, в Firefox). Вот, например, svg-шная картинка:
Там на чёрном фоне переход полностью прозрачного цвета в белый. Казалось бы, должен быть градиент между чёрным и белым. А вот фиг! Прозрачный задан как rgba(255,0,0,1), и в результате посередине явственно виден красный. Я уже сделал патч и описание, хотел отправить разрабам cairo… А потом посмотрел ту же картинку в Хроме. и увидел то же самое. И в последнем Эксплорере. Так что это уже не баг, а фича, и чинить это, к сожалению, никто не будет.
TheShock
У вас в вашей свг такой градиент (посмотрите код ее). Естественно перед будет с красного прозрачного в белый непрозрачный на черном фоне. Поведение на 100% закономерное. Если хотите чтобы не было красного — замените 255 на 0.
hdfan2
Неважно, какие там RGB, если прозрачность 100%. При переходе с полностью прозрачного на белый (или любой другой) цвет должен быть только этот конечный цвет, и никакой другой. Об этом, собственно, и статья, и в этом я совершенно согласен с её автором.
TheShock
Кто вам такое сказал? А если я хочу выйти из прозрачного красного и в прозрачный белый? Я вот так часто в Юнити в частичках делаю. Там даже настраивается альфа-канал — отдельно, а все остальное — отдельно.
Вы мыслите поверхностно (вот у меня есть задача и пусть именно под эту задачу все подстраиваются). А задачи бывают совершенно разные и текущее решение — позволяет решать их все, а не только некоторое подмножество.
Статья, конечно, близка, но не об этом.
hdfan2
Прозрачный красный — это как? Полностью прозрачный он один, у него нет цвета. Если же имеется в виду «из прозрачного через полупрозрачный красный и в белый», то запросто:
Keyten
Стало интересно, накодил сравнение: https://jsfiddle.net/cjysxbc2/.
Прозрачный красный — это абстрактное понятие. Вас же не смущает мнимая единица, которая в квадрате внезапно превращается в -1? Почему же вас смущает прозрачный красный, который при изменении прозрачности внезапно становится красным, а не белым / чёрным?
К слову, какой цвет вы предлагаете на роль «настоящего прозрачного»? Чёрный прозрачный или белый прозрачный? Или, может быть, серый прозрачный?
Fesor
это красный + альфаканал. Ваш кэп.
CrazyNiger
А как по вашему должен высчитываться цвет по середине, только на основании второго цвета? Или вы предлагаете отдельно считать случай, когда первый цвет полностью прозрачен и когда он не полностью прозрачен? Интерполяция же идет по всем четырем каналам одинаково, так что «баг» в вашем случае, не баг, а вполне ожидаемое поведение.
hdfan2
В статье есть чёткий и понятный ответ, к которому я когда-то самостоятельно пришёл (см. пункт «Если вы программист: используйте Premultiplied Alpha!»).
TheShock
Это вообще бредовый совет. Вот у меня есть красный цвет. Я ходу сделать красный с полупрозрачность 25%. Так вот — по формуле, данной в статье у меня вместо «rgba(255,255,255,0.25)» будет «rgba(51,51,51,0.2)». То есть теперь этот цвет будет мало того, что прозрачным, так еще и чернить и он будет некорректно орисовываться поверх белых поверхностей! Premultimlied alpha можно использовать только если рисовать более светлое поверх более темного, а не всегда. Посмотрите сами на его картинку-пример — эта картинка испорчена!
До:
После:
А поверх белого фона это будет еще более заметно.
homm
Это неправда. Premultiplied Alpha нужна для вычислений, а не для вывода на экран. Для вывода на альфу нужно обратно разделить. Когда вы после вычислений получите rgba(51,51,51,0.2) и разделите на альфу, вы получите обратно rgba(255,255,255,0.2).
TheShock
Это должен быть выбор в момент наложения, а не единственный способ в момент сохранения. Какой смысл терять 80% информации при записи в файл?
homm
Зачем такой выбор, какой в нем смысл? О потере какой информации идет речь и при чем тут запись в файл, если мы говорим об обработке и выводе на экран?
Давайте вернемся к вашему исходному утверждению: «теперь этот цвет будет мало того, что прозрачным, так еще и чернить и он будет некорректно орисовываться поверх белых поверхностей». В браузерах кроме Сафари background-градиенты строятся именно с помощью приумноженного альфаканала. Сделав градиент, например, от красного непрозрачного, к зеленому прозрачному, вы можете убедиться, что никакого черного там нет.
TheShock
Процитирую вам из топика:
О выводе не говорится. Говорится о хранении. Если хранить картинки в таком виде, то в большом количестве целевых устройств будет именно черный цвет. Именно этот эффект мы видим на картинке «после».
MrShoor
Для premultiplied обычно меняют режим блендинга.
Обычный блендинг это:
dectColor * (1 — srcAlpha) + srcColor * srcAlpha
То есть чтобы смешать 2 картинки видеокарта потом все равно делает это умножение на альфу (srcColor * srcAlpha). Поэтому режим смешивания для premultiplied делают таким, чтобы этого умножения не было. Если использовать OpenGL, то вместо glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) просто делают glBlendFunc(GL_SRC_ONE, GL_ONE_MINUS_SRC_ALPHA).
Так что чернить оно не будет.
Но есть другой неприятный момент, смещение цвета при семплинге. Я об этом написал чуть ниже в комментарии. Он имеет место быть для картинок, которые сильно растягиваются, например текстуры GUI. На выходе можем получить не совсем то, что задумывал дизайнер.
homm
Хранить можно и в памати. Но даже если для оптимизации загрузки хранить в файлах в таком же виде, то ничего страшного не случится: вы же будете знать, что это формат приумноженной альфы и будете выводить его правильно. Конечно, другие просмоторщики не будут этого знать и будут открывать так как у автора на рисунке, с черным. Если это например ресурсы игры, то это не страшно.
MrShoor
Premultiply нужно осторожно делать, ибо он меняет изображение, и может все испортить. Покажу на примере:
Возмем 2 RGBA пикселя:
{1, 0, 0, 0.1} {0, 0, 1, 0.9}
Допустим мы семплим ровно между пикселей, это будет что-то типа lerp(c1, c2, 0.5), и пиксель после семплинга будет:
{0.5, 0, 0.5, 0.5}
А теперь делаем Premultiply для этих пикселей:
{0.1, 0, 0, 0.1} {0, 0, 0.9, 0.9}
Аналогично семплим, и получаем другой результат:
{0.05, 0, 0.45, 0.5}
Уже как бы видно, что результат другой, если разделим на 0.5 то получим:
{0.1, 0, 0.9, 0.5}, согласитесь, это значительно отличается от {0.5, 0, 0.5, 0.5}
homm
Соглашусь, значительно отличается. Не понимаю, что тут испорчено, это верный результат.
MrShoor
Если у вас текстура градиента скажем 2*2 пикселя, которую вы растягиваете на 200 * 200, то результат может быть сильно не такой, какой задумывал художник.
Предлагаете потом художнику доказывать, что он не прав, и у нас в рендере результат верный?
homm
Понял о чем вы. Растягивать текстуру 2*2 — это грубо говоря наложить градиент. И дизайнеру может хотеться, чтобы градиент шел из красного непрозрачного в зеленый прозрачный. При умножении такого трудно добиться. Но это не значит, что такое поведение без умножения — один из вариантов нормы. Нет, правильный скейлинг и субпиксельный рендеринг может быть только с умножением. А то, что вам иногда нужно другое поведение — это изобразительный прием. Изобразительные приемы бывают разные и вы можете реализовать их через шейдеры. К скейлингу это не будет иметь отношения.
mayorovp
Другими словами, Premultiply нужно делать там, где художник не проверял результат интерполяции между соседними пикселями?
homm
Картинка тут конечно зря приведена, она только запутывает
khim
В какой момент и почему вдруг становится «неважно какие там RGB»?
hdfan2
Повторюсь: в статье есть чёткий и понятный ответ (см. пункт «Если вы программист: используйте Premultiplied Alpha!»).
Kealon
причина понятна, а вот вывод сомнительный
эту проблему должен решать программист — а именно обойти\заблокировать фильтрацию, а не художник
Beowulf
Напоминает недавний спор про гомеопатические препараты. Вещества в нем нет, но на самом деле как бы есть, но очень мало…
Хочется просто сказать: «Ты хочешь 0, но принципиально пишешь 255, а потом винишь компьютер, что он не угадал твоё желание — не надо так!»