ПрограммированиеСтатьиГрафика

Premultiplied alpha

Внимание! Этот документ ещё не опубликован.

Автор:

Статья находится в работе.

Статья посвящена premultiplied alpha. Этот метод композиции изображений (блендинга) обладает рядом значимых преимуществ и почти лишён недостатков. Однако (несмотря на то, что метод был введён в 1984 году), он недостаточно хорошо известен - и осознаваем - среди графических программистов. Помочь исправить данную ситуацию и призвана эта статья.

Что такое premultiplied alpha
Свойства
  Фильтрация
  Ассоциативность
  Единое выражение для альфа- и аддитивного блендинга
Переход от обычного блендинга
  Исходные изображения
  Режим блендинга
  Изменение цвета
  Когда "обычная" альфа предпочтительнее
Гамма-корректность
Ссылки

Что такое premultiplied alpha

Premultiplied alpha (PMA) - это способ интерпретации компонент RGBA. Он естественным образом задаёт и логику операций над цветами в этом представлении. Способ был предложен в 1984 г. в статье "Compositing Digital Images", T. Porter & T. Duff (да, это тот самый Том Дафф, автор Duff's device, изобретённого, кстати, примерно в то же время - в 1983 г.). Можно отметить, что статья уделяла не много внимания преимуществам premultiplied alpha, и рассматривала её в основном как оптимизацию.

Обычно в компьютерной графике цвет задаётся 3-мя компонентами RGB (интерпретация самих RGB - отдельная большая тема, которой эта статья касается лишь ограниченно).

Для задания полупрозрачности добавляется 4-я компонента, А. Она также называется альфой (α), и задаёт степень непрозрачности (англ. opacity) цвета.

Наиболее распространённый способ интерпретации цвета с полупрозрачностью, у которого, в виду его повсеместности, и названия толком нет (в английском встречается термин straight alpha; в статье далее по тексту он называется "обычным") состоит в следующем. RGB означают цвет, как и раньше, а альфа задаёт степень непрозрачности. Этот способ довольно интуитивен, но подобрать ему физическую модель несколько нетривиально. Можно представить стеклянную пластинку, на которую напылён тонкий слой краски заданного оттенка. RGB соответствует цвету краски, альфа - толщине (точнее непрозрачности) слоя.

Premultiplied alpha использует несколько более непосредственную модель. RGB обозначают вклад данного цвета, т. е., грубо говоря, количество энергии (точнее мощности в данном спектральном диапазоне (R, G или B)), которое данный объект добавляет изображению. Альфа задаёт степень непрозрачности, т. е. долю света заслонённых объектов, которую объект поглощает (так же, как и в предыдущем способе).

Соответственно, если у нас есть цвет, компоненты которого в "обычном" представлении заданы RGBA, то соответствующий ему цвет в premultiplied alpha, rgba, будет иметь вид
[r, g, b, a]=[R·A, G·A, B·A, A]

Примечание: значения компонент здесь принимаются вещественными, линейными, и (для alpha) - в диапазоне [0; 1]. Вопросам представления (кодирования) цвета посвящён раздел Гамма-корректность.

Видим, что RGB были домножены на альфу, чем и вызвано название ("premultiplied alpha" = "пре-домноженная альфа").

Рассмотрим, как в этих представлениях происходит композиция (блендинг) , т. е. получение из фонового (dst) цвета и цвета объекта (src), окончательного цвета (final)?

"Обычный" блендинг:
Cf=Cs·As+Cd·(1−As)
Af=As+Ad·(1−As)

Premultiplied alpha:
Cf=Cs+Cd·(1−As)
Af=As+Ad·(1−As)

Здесь и далее в статье C обозначает цветовые компоненты (R, G и B), если формулы для них совпадают по форме.

Свойства

Рассмотрим свойства premultiplied alpha блендинга, и то как они отличают его от "обычного" блендинга.

Фильтрация

Часто возникающая задача - это фильтрация изображения, т. е. получение цвета по значениям нескольких соседних пикселей. Очевидными примерами являются изменение размера изображения (resize) и билинейная интерполяция текстур. Другим примером фильтрации является размытие (blur).



Рис. 1


Рис. 2

Для наглядности рассмотрим следующий пример. Нужно получить билинейно интерполированное значение цвета в позиции между 4 текселями.

Прямолинейный подход - воспользоваться формулами ниже:
Cf=C00·(1−u)·(1−v)+C01·(1−u)·v+C10·u·(1−v)+C11·u·v
Af=A00·(1−u)·(1−v)+A01·(1−u)·v+A10·u·(1−v)+A11·u·v

Здесь u, v - интерполяционные коэффициенты. В частности, для u=v=0.5 выражения превращаются в средне арифметическое.

Именно так тексели интерполирует видеокарта.

Если вспомнить определение premultiplied alpha, то видно, что для неё эти формулы дают правильный результат - энергию и затенение интерполированного участка. Их можно понимать как параметры однотексельного квадратика с центром в данной точке (столь наглядная интерпретация справедлива для билинейной интерполяции, но не обязательно для других фильтров).

Легко видеть, что для "обычного" блендинга результат, вычисленный по данным формулам, будет неправильным. Как минимум очевидно, что цвет полностью прозрачного текселя будет влиять на результирующий цвет.

Зная, что результат для premultiplied alpha правильный, можно выразить формулы для "обычного" блендинга, переводом туда и обратно:
Cf=(C00·A00·(1−u)·(1−v)+C01·A01·(1−u)·v+C10·A10·A11·u·(1−v)+C11·u·v)/(A00·(1−u)·(1−v)+A01·(1−u)·v+A10·u·(1−v)+A11·u·v)
Af=A00·(1−u)·(1−v)+A01·(1−u)·v+A10·u·(1−v)+A11·u·v

Формулы довольно громоздкие, и более того, не соответствуют тому, как работает видеокарта.

На практике использование фильтров с "обычным" блендингом зачастую приводит к визуальным ошибкам, вроде чёрных контуров вокруг спрайтов. С этим обычно борются добавлением вокруг изображения бордюра цвета ближайшего пикселя, но этот хак лишь частично исправляет ситуацию. Кроме того он приводит к дополнительным проблемам: некоторые редакторы любят выставлять цвет прозрачных пикселей в чёрный; и также чёрный цвет для таких пикселей устанавливают некоторые форматы, например DXT1.

Ассоциативность

Что такое ассоциативность? Оператор (назовём его #) ассоциативен, если (a # b) # c = a # (b # c). Примеры ассоциативных операций: сложение вещественных чисел, умножение матриц (эта операция ассоциативна хотя и не коммутативна).

Блендинг изображения src поверх фона dst можно понимать как функцию от двух аргументов color=blend(src,dst), что можно также записывать в инфиксной форме: color=src BLEND dst. Соответственно блендинг ассоциативен, если (a BLEND b) BLEND c = a BLEND (b BLEND c) другими словами (в функциональной форме): blend(blend(a,b),c)=blend(a,blend(b,c)).

Чем нам ценна ассоциативность блендинга? Нередко возникают задачи, в которых есть желание сблендить несколько изображений в промежуточный буфер, а уже его - на экран. Очевидные примеры: кеширование GUI, некоторые методы отрисовки полупрозрачной геометрии (напр. depth peeling). При этом, обычно, хочется, чтобы результат был тем же, что и у непосредственного блендинга на экран.

Легко проверить прямым вычислением, что premultiplied alpha blending ассоциативен, а "обычный" блендинг - нет.

Ниже приводятся выкладки:

+ premultiplied alpha
+ обычный блендинг

Попытка получить ассоциативный блендинг в рамках "обычного", не premultiplied, обычно приводит к замысловатым бленд-модам, которые всё-равно не решают проблему полностью.

Единое выражение для альфа- и аддитивного блендинга

Кроме блендинга с полупрозрачностью иногда возникает желание использовать аддитивный: цвет фрагмента прямо добавляется к цвету фона, яркость которого при этом не уменьшается (этот режим часто используют для отрисовки пламени).

При обычном блендинге это несколько проблематично: полупрозрачные треугольники обычно отсортированы по расстоянию и управлять их порядком не получается, а менять режим блендинга на ходу (потенциально - на каждом треугольнике) - тоже не хочется по соображениям быстродействия.

В рамках premultiplied alpha данная проблема не стоит: аддитивный блендинг реализуется установкой A=0.

Более того - premultiplied alpha способна выразить и промежуточные случаи, кроме этих двух: A!=0, но C>A (что в "обычном" блендинге соответствовало бы C>1). Это возможность (overbright) может оказаться полезной в для нескольких случаев.

Переход от обычного блендинга

Может возникнуть желание использовать как единственный режим с прозрачностью. Само желание может быть вполне здравым, но остаётся вопрос: как именно его применять, и как перейти от "обычного" блендинга.

Ниже приводятся некоторые пункты. Для примеров используется OpenGL 1, ради краткости (и т. к. необходимые настройки можно выразить в FFP).

Исходные изображения

Исходные изображения (ассеты, текстуры) обычно не являются PMA, и вероятно, имеет смысл их такими оставить. Графические редакторы обычно интерпретируют изображения без PMA, и его введение затруднит работу. Перевод в PMA имеет смысл производить при загрузке изображений. Если есть необходимость хранить как не-PMA, так и PMA изображения - это можно решить, например, наименованием (напр. "character.png" vs "fire.pma.png"); также как PMA может быть осмысленно хранить изображения, которые уже прошли этап(ы) обработки (напр. .dds), в отличие от тех, которые загружены непосредственно.

Режим блендинга

Для PMA режим выставляется следующим образом

glBlendEquation(GL_FUNC_ADD);
glBlendFunc(GL_ONE,GL_ONE_MINUS_SRC_ALPHA);

вместо "обычного"

+ Показать

Изменение цвета

Если в "обычном" блендинге для того, чтобы сделать объект более прозрачным мы говорили

glColor4f(1.0f,1.0f,1.0f,alpha);

то в PMA соответствующий код выглядит

glColor4f(alpha,alpha,alpha,alpha);

Для более общего случая:

glColor4f(r,g,b,a);

в PMA

glColor4f(r*a,g*a,b*a,a);

Если мы используем glColor4f(r,g,b,a); при r,g,b>a, то получаем overbright (в "обычном" блендинге эквивалент оному отсутствует).

Когда "обычная" альфа предпочтительнее

Иногда "обычная" интерпретация оказывается подходящей. Бывает, это задачи, вообще не являющиеся блендингом. Одним примером является alpha-test. Другим - рисование одной и той же текстуры со включенной/выключенной прозрачностью в зависимости от ситуации (оба примера - из http://tomforsyth1000.github.io/blog.wiki.html#%5B%5BPremultiplie… art%202%5D%5D ).

Гамма-корректность

До сих пор в тексте использовались линейные, вещественные значения для компонент (R, G, B, A).

На практике часто цвета хранятся в текстурах с 8-битным представлением каждого канала. Оно зачастую нелинейно, и использует sRGB кодирование.

Как же взаимодействует premultiplied alpha и sRGB?

Согласно спецификации OpenGL гамма-компрессия применяется для цветовых каналов, но не для альфы:

3)  Should the alpha component of sRGB texture formats be
        gamma-corrected?

        RESOLVED:  No.  Alpha is correctly understood to be a weighting
        factor that is best stored in a linear representation.  The alpha
        component should always be stored as a linear value.

        "SRGB_ALPHA" is used to indicate sRGB formats with an alpha
        component.  This naming (as opposed to something like "SRGBA")
        helps highlight the fact that the alpha component is separate
        and stored with a linear distribution of precision.

(https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB.txt )
См. также GL_SRGB8_ALPHA8 в https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glTexImage2D.xhtml . Имеет смысл следовать этому соглашению хотя бы в целях совместимости.

Т. к. домножение происходит в линейном пространстве, то получение PMA из обычной альфы домножением RGB на A/255 для sRGB приводит к некорректным значениям после декомпрессии. Более логичной выглядит формула:
Cpma=linear2srgb(srgb2linear(Cstraight)·Astraight/255)

Ссылки


https://keithp.com/~keithp/porterduff/p253-porter.pdf - T. Porter & T. Duff, "Compositing Digital Images", 1984.
http://tomforsyth1000.github.io/blog.wiki.html - посты Premultiplied alpha и Premultiplied alpha part 2 - лучшее известное мне изложение темы.
https://en.wikipedia.org/wiki/Alpha_compositing
http://www.realtimerendering.com/blog/gpus-prefer-premultiplication/
https://blogs.msdn.microsoft.com/shawnhar/2009/11/06/premultiplied-alpha/
https://microsoft.github.io/Win2D/html/PremultipliedAlpha.htm
http://www.andersriggelsen.dk/glblendfunc.php

#blending

14 сентября 2017 (Обновление: 23 сен 2017)