Тени: shadow mapping

Тень — это место, которого не видит источник света; чтобы её нарисовать, сцену сначала «фотографируют» со стороны света.

Shadow mapping — техника теней: сначала рендерят глубину сцены из позиции источника света (теневая карта), затем при рисовании проверяют, видна ли точка свету.

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

Тени привязывают объекты к земле и дают сцене глубину. Самый распространённый способ их получить в реальном времени — shadow mapping. Понимание идеи объясняет, откуда берутся характерные артефакты теней (зазубрины, «акне», отрыв тени) и как с ними борются.

Идея в два прохода

Проход 1 (из позиции света):
   рисуем сцену, сохраняем ТОЛЬКО глубину -> shadow map
   (для каждого направления: как далеко ближайшая поверхность)

Проход 2 (из позиции камеры):
   для каждого пикселя вычисляем его расстояние до света
   и сравниваем с тем, что записано в shadow map:
     дальше записанного -> точка в тени
     равно/ближе        -> точка освещена

Тест в тени или нет

Суть — сравнение двух глубин. Если расстояние от точки до света больше, чем ближайшая глубина из теневой карты в этом направлении, значит, что-то загораживает свет — точка в тени. Смоделируем.

# shadow map: ближайшая к свету глубина по направлениям (упрощённо)
shadow_map = {"dir0": 5.0, "dir1": 3.0, "dir2": 8.0}

def in_shadow(point_depth, map_depth, bias=0.05):
    # точка в тени, если она дальше ближайшей поверхности
    return point_depth > map_depth + bias

# точка на расстоянии 7 в направлении dir1 (ближайшая там 3.0)
print("dir1, глубина точки 7.0:", in_shadow(7.0, shadow_map["dir1"]))
# точка на расстоянии 5 в направлении dir0 (ближайшая 5.0) — сама поверхность
print("dir0, глубина точки 5.0:", in_shadow(5.0, shadow_map["dir0"]))

Вывод:

dir1, глубина точки 7.0: True
dir0, глубина точки 5.0: False

Точка на 7.0 загорожена поверхностью на 3.0 — она в тени. Точка ровно на ближайшей глубине — освещена (это сама освещённая поверхность).

Shadow acne и bias

Из-за конечного разрешения теневой карты поверхность местами «затеняет сама себя» — появляются полосы (shadow acne). Лечится небольшим смещением bias: точку считают в тени, только если она заметно дальше записанной глубины. Слишком большой bias порождает обратную проблему — тень «отрывается» от объекта (peter panning).

Без bias: самозатенение      С bias: чисто
  /\/\/\/\  полосы           ________  ровно
 поверхность                   поверхность

Мягкие края: PCF

Жёсткая теневая карта даёт ступенчатые края. PCF (percentage-closer filtering) сглаживает их: вокруг точки берут несколько соседних сэмплов теневой карты и усредняют долю «в тени», получая полутень.

def pcf(samples_in_shadow, total):
    # доля сэмплов в тени -> мягкая граница
    return round(samples_in_shadow / total, 2)

print("3 из 9 в тени:", pcf(3, 9), "-> частичная тень")
print("9 из 9 в тени:", pcf(9, 9), "-> полная тень")
print("0 из 9 в тени:", pcf(0, 9), "-> полный свет")

Вывод:

3 из 9 в тени: 0.33 -> частичная тень
9 из 9 в тени: 1.0 -> полная тень
0 из 9 в тени: 0.0 -> полный свет

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

Для направленного света (солнце) теневую карту рендерят ортографически; для точечного — кубическую (во все стороны). У больших сцен применяют каскадные теневые карты (CSM): несколько карт разного масштаба — детальная вблизи камеры, грубая вдали. Это компромисс между качеством и памятью.

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

  • Нулевой bias → shadow acne (полосы самозатенения).
  • Слишком большой bias → отрыв тени от объекта (peter panning).
  • Низкое разрешение теневой карты → зазубренные края теней.

Итоги

  • Shadow mapping: проход 1 — глубина из позиции света, проход 2 — сравнение глубин.
  • Точка в тени, если она дальше ближайшей поверхности по направлению к свету.
  • Bias лечит самозатенение, но избыток отрывает тень.
  • PCF усредняет соседние сэмплы, давая мягкие края.
Проверьте себя
1. Что рендерят в первом проходе shadow mapping?
AЦвет сцены
BГлубину сцены из позиции источника света
CТени напрямую
DТекстуры
2. Как определить, что точка в тени?
AОна ярче соседей
BЕё расстояние до света больше записанной в теневой карте глубины
CОна дальше камеры
DУ неё alpha=0
3. Зачем нужен bias в shadow mapping?
AЧтобы ускорить рендер
BЧтобы убрать самозатенение (shadow acne) из-за конечного разрешения карты
CЧтобы покрасить тень
DЧтобы включить PCF