Прозрачность и блендинг

Стекло, дым и вода частично пропускают то, что за ними — это достигается смешиванием цветов по альфа-каналу.

Блендинг (alpha blending) — смешивание цвета нового фрагмента с уже лежащим в пикселе по формуле, где вес задаёт альфа-канал.

Зачем это знать

Непрозрачные объекты просто перекрывают фон. Прозрачные должны его просвечивать, и здесь возникает целый класс тонкостей: формула смешивания, порядок рисования, конфликт с буфером глубины. Неправильный блендинг — это либо «чёрные края», либо «стекло», через которое ничего не видно.

Формула смешивания

Классическая формула «over»: итоговый цвет = новый_цвет×alpha + старый_цвет×(1-alpha). При alpha=1 объект полностью непрозрачен, при alpha=0 — невидим, при 0.5 — наполовину просвечивает.

def blend_over(src, dst, alpha):
    return tuple(round(s*alpha + d*(1-alpha), 2) for s, d in zip(src, dst))

red = (1.0, 0.0, 0.0)    # новый фрагмент (стекло)
blue = (0.0, 0.0, 1.0)   # фон
for a in [0.0, 0.25, 0.5, 0.75, 1.0]:
    print(f"alpha={a}: {blend_over(red, blue, a)}")

Вывод:

alpha=0.0: (0.0, 0.0, 1.0)
alpha=0.25: (0.25, 0.0, 0.75)
alpha=0.5: (0.5, 0.0, 0.5)
alpha=0.75: (0.75, 0.0, 0.25)
alpha=1.0: (1.0, 0.0, 0.0)

При alpha=0 виден только фон (синий), при alpha=1 — только новый цвет (красный), между — плавная смесь. Это и есть «стекло».

Порядок имеет значение

Смешивание зависит от того, что уже лежит в пикселе, поэтому для прозрачных объектов порядок критичен. Их рисуют от дальних к ближним (back-to-front), иначе смешивание получится с неправильным «задником».

def blend_over(src, dst, alpha):
    return tuple(round(s*alpha + d*(1-alpha), 2) for s, d in zip(src, dst))

# два полупрозрачных слоя над фоном, разный порядок
bg = (0.0, 0.0, 0.0)
A = ((1.0, 0.0, 0.0), 0.5)  # красный
B = ((0.0, 1.0, 0.0), 0.5)  # зелёный

# порядок A потом B
r1 = blend_over(A[0], bg, A[1]); r1 = blend_over(B[0], r1, B[1])
# порядок B потом A
r2 = blend_over(B[0], bg, B[1]); r2 = blend_over(A[0], r2, A[1])
print("A потом B:", r1)
print("B потом A:", r2)

Вывод:

A потом B: (0.25, 0.5, 0.0)
B потом A: (0.5, 0.25, 0.0)

Те же два полупрозрачных слоя дают разный цвет при разном порядке — вот почему прозрачность сортируют.

Конфликт с z-buffer

Если прозрачный фрагмент запишет свою глубину в z-buffer, он перекроет всё, что за ним, и просвечивания не выйдет. Поэтому прозрачные объекты рисуют после непрозрачных, обычно с тестом глубины, но без записи в неё (depth write off).

Как работает под капотом

Есть разные режимы блендинга: «over» (стекло), аддитивный (свет, огонь — цвета складываются и светятся), мультипликативный (затемнение). Аддитивный блендинг (src + dst) приятен тем, что не требует строгой сортировки — сложение коммутативно. Premultiplied alpha (заранее домноженный цвет на альфу) избавляет от тёмной каймы на краях.

Частые ошибки

  • Рисовать прозрачное с записью глубины — задние объекты исчезнут.
  • Не сортировать back-to-front при «over»-блендинге — неверные цвета наложения.
  • Забыть про premultiplied alpha — тёмные ореолы вокруг полупрозрачных краёв.

Итоги

  • Alpha blending: цвет = src·alpha + dst·(1-alpha).
  • Прозрачные объекты рисуют после непрозрачных, сортируя от дальних к ближним.
  • Для прозрачных отключают запись в z-buffer (но тест оставляют).
  • Аддитивный блендинг (свет/огонь) коммутативен и не требует сортировки.
Проверьте себя
1. Как выглядит формула alpha-блендинга 'over'?
Asrc + dst
Bsrc·alpha + dst·(1-alpha)
Csrc · dst
Dmax(src, dst)
2. Почему прозрачные объекты сортируют от дальних к ближним?
AДля скорости
BБлендинг зависит от того, что уже в пикселе, поэтому порядок меняет цвет
CИначе они исчезнут
DЧтобы включить mipmap
3. Что делают с записью в z-buffer для прозрачных объектов?
AУсиливают её
BОтключают запись (но тест глубины оставляют)
CУдваивают глубину
DНичего не меняют