Буфер глубины и z-buffer
Когда объекты перекрываются, видеокарта хранит для каждого пикселя его глубину и показывает лишь самый близкий фрагмент.
Буфер глубины (z-buffer) — массив, где для каждого пикселя хранится глубина ближайшего нарисованного фрагмента; новый фрагмент проходит, только если он ближе.
Зачем это знать
Без буфера глубины порядок рисования определял бы видимость: что нарисовано позже — то поверх, даже если оно дальше. Z-buffer избавляет от ручной сортировки треугольников: каждый фрагмент сам «знает» свою глубину и честно конкурирует за пиксель.
Как работает тест глубины
Буфер глубины инициализируют максимумом (бесконечно далеко). Для каждого фрагмента сравнивают его глубину с тем, что уже лежит в буфере: если ближе — пишем цвет и обновляем глубину; если дальше — отбрасываем. Смоделируем на одном пикселе, в который приходят три фрагмента.
FAR = float("inf")
depth = FAR # буфер глубины пикселя
color = None # буфер цвета пикселя
fragments = [("красный", 0.8), ("зелёный", 0.3), ("синий", 0.5)]
for name, z in fragments:
if z < depth: # ближе того, что уже есть?
depth = z
color = name
print(f"{name} (z={z}) ПРОШЁЛ -> пиксель {name}")
else:
print(f"{name} (z={z}) отброшен (дальше {depth})")
print("Итог пикселя:", color, "на глубине", depth)
Вывод:
красный (z=0.8) ПРОШЁЛ -> пиксель красный зелёный (z=0.3) ПРОШЁЛ -> пиксель зелёный синий (z=0.5) отброшен (дальше 0.3) Итог пикселя: зелёный на глубине 0.3
Несмотря на то что синий пришёл последним, победил ближайший — зелёный (z=0.3). Порядок рисования не важен.
Отсечение задних граней
Отдельная оптимизация — backface culling: треугольники, повёрнутые «спиной» к камере (внутренняя сторона), не рисуют вообще. Ориентацию определяют по порядку обхода вершин (по часовой/против) после проекции — фактически по знаку площади или векторному произведению. Это убирает примерно половину треугольников замкнутого объекта.
Z-fighting
Если две поверхности очень близки по глубине, ограниченная точность буфера не может надёжно решить, кто ближе — пиксели мерцают между ними (z-fighting). Буфер глубины нелинейный: он точнее у ближней плоскости и грубее у дальней, поэтому маленький near и огромный far усугубляют проблему.
Точность глубины (схематично):
near |#########| много точности
|#### |
|## |
far |# | мало точности -> z-fighting
Как работает под капотом
Тест глубины выполняется аппаратно после фрагментного шейдера, но GPU умеет делать early-Z — отбрасывать фрагмент ещё до шейдера, если он заведомо закрыт. Это огромная экономия: нет смысла считать дорогой шейдер для пикселя, который всё равно перекроется. Прозрачные объекты ломают early-Z, поэтому их рисуют отдельно (следующий урок).
Частые ошибки
- Огромный диапазон near/far → потеря точности и z-fighting.
- Рисовать прозрачные объекты с записью в z-buffer — они перекроют то, что должно просвечивать.
- Отключить backface culling без нужды — лишняя работа GPU.
Итоги
- Z-buffer хранит глубину ближайшего фрагмента на пиксель; ближе — побеждает.
- Порядок рисования непрозрачных объектов не важен благодаря тесту глубины.
- Backface culling отбрасывает грани, повёрнутые от камеры.
- Близкие по глубине поверхности дают z-fighting; виноваты точность и большой far/near.