Тени: 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 усредняет соседние сэмплы, давая мягкие края.