Quake: настоящий 3D — BSP, PVS и запечённый свет
1/z прячем под целочисленную работу (раз в 16 пикселей). Честный 3D, оплаченный вперёд.
Настоящий 3D против 2.5D Doom
Doom и Quake оба стоят на BSP — но в разном числе измерений, и это меняет всё.
| — | Doom (1993) | Quake (1996) |
|---|---|---|
| BSP | 2D: секущие линии в плане | 3D: секущие плоскости в объёме |
| Геометрия | сектора с высотой пола/потолка | произвольные выпуклые браши |
| Комната над комнатой | нельзя | можно |
| Обзор | нет наклона камеры (автоприцел по вертикали) | свободный обзор вверх/вниз + перемещение в 3D |
| Текстуры | вертикальный стретч, без перспективной коррекции | перспективно-корректные UV |
| Свет | яркость на сектор | запечённые lightmap’ы (текстура света) |
Структура данных та же — дерево двоичного разбиения пространства — но в 3D она режет объём произвольными плоскостями на выпуклые ячейки (листья дерева). Одна комната обычно дробится на несколько выпуклых ячеек. BSP здесь работает на три фронта: порядок отрисовки, коллизия и хранилище видимости (PVS).
PVS — предвычисленная видимость
Главная идея Quake: видимость не считают в кадре, её компилируют заранее. QBSP между смежными ячейками автоматически расставляет порталы — «окна», через которые из одной ячейки видно другую. Эти порталы живут только во время компиляции. Инструмент VIS по ним прогоняет алгоритм видимости из области (Сет Теллер, 1992; Кармак реализовал в 1996) и для каждой ячейки сохраняет битовый вектор: какие ячейки потенциально видны хоть из какой-то её точки. Это и есть PVS — Potentially Visible Set (плюс аналог PAS для звука).
В рантайме движок берёт лист, где сейчас камера, читает его PVS-битмаску и рисует только отмеченные ячейки. Вместо обхода всей карты — один lookup: из битвектора длиной в число листьев карты (сотни–тысячи) у тесной ячейки взведены единицы–десятки бит. Дальше идёт обычный frustum cull (что в поле зрения) и BSP-порядок.
Запечённый свет: lightmap’ы
Инструмент LIGHT в офлайне кастует лучи от всех источников карты и записывает результат в lightmap — отдельную низкоразрешающую текстуру света (≈ один сэмпл на крупный блок поверхности, lightmap’ы складываются в атласы вроде 128×128 на много поверхностей). В рантайме пиксель = базовая_текстура × lightmap. Это развязывает детализацию и освещение: геометрия и узор — в hi-res текстуре, свет — в lo-res lightmap’е.
Чтобы перемножение не повторять каждый кадр, Quake кэширует результат в surface cache: один раз собрал освещённую поверхность — переиспользуешь, пока она в кадре. Флаг -extra у LIGHT берёт по 4 сэмпла на тексель и усредняет — мягче края теней. Цена запекания — свет статичен: подвинуть лампу или стену нельзя (несколько динамических огней — вспышки выстрелов — накладывались отдельным дешёвым слоем).
Перспективно-корректные текстуры и трюк с делением
Честный 3D требует перспективной коррекции: экранно-линейная интерполяция u,v вдоль уходящего вглубь полигона искривляет текстуру. Корректно — интерполировать u/z и 1/z (они линейны в экранных координатах), а потом делить:
Проблема: деление на Pentium (FDIV) — до ~39 тактов, на каждый пиксель неподъёмно. Решение Майкла Абраша: точные u,v считать раз в 16 пикселей, между ними — линейная интерполяция (ошибка на таком отрезке незаметна). И главный трюк — перекрытие: FDIV для следующего 16-пиксельного спана запускается в начале текущего; пока FPU 30+ тактов делит, целочисленные конвейеры U/V рисуют текущие 16 пикселей. Деление выходит «бесплатным» — спрятано под рисование, ~7.5 такта на пиксель.
Мост к Mode 7. Помнишь построчную перспективу SNES? Там глубина постоянна вдоль строки → деление 1/z одно на строку. У Quake полигон уходит вглубь, z меняется вдоль спана → деление нужно периодически (раз в 16 пикселей). Mode 7 — это вырожденный случай «N = вся ширина строки».
Хардкор · теория: почему PVS «потенциально» и почему это безопасноможно пропустить
Видимость из области через порталы
VIS решает не «видна ли точка из точки», а «видна ли ячейка из любой точки другой ячейки». Луч зрения должен пройти через последовательность порталов; задача сводится к тому, существует ли прямая, протыкающая все порталы цепочки (через сепарирующие/клиппирующие плоскости между рёбрами порталов). Если хоть одна такая прямая есть — целевая ячейка попадает в PVS.
Консервативность
PVS консервативен: он может пометить лишнее (ячейку, которая из текущей точки/угла на самом деле не видна) — это лишь лишние отрисовки. Но он никогда не пропустит реально видимую ячейку — иначе в мире появились бы дыры. Точная попиксельная видимость зависела бы от позиции и угла камеры и стоила бы попиксельных вычислений в кадре — ровно того, что precompute и устраняет. Поэтому хранят грубое-но-безопасное приближение на область, а не точное на точку.
Перспективная коррекция: откуда берётся кривизна
Экранная координата ∝ x/z. Если интерполировать u линейно по экрану, неявно предполагаешь z постоянным — на уходящем вглубь полигоне это даёт «плавающую» текстуру. Линейны по экрану именно u/z и 1/z; их и тянут, а деление в конце возвращает истинное u. Длина отрезка между точными делениями — компромисс «ошибка vs стоимость»: 16 пикселей у Quake выбраны так, что ошибка невидима, а FDIV как раз успевает за рисованием спана.
Хардкор · инженерия: компилятор карты и рантайм-конвейерможно пропустить
- Три инструмента, по очереди:
QBSP(BSP-дерево + порталы.prt) →VIS(PVS из порталов; на картах 1996-го мог считаться часами) →LIGHT(трассировка света в lightmap’ы). Карта — это «скомпилированный бинарь» уровня. - Рантайм-каскад отсечений: PVS (грубо, предвычислено) → frustum cull (по полю зрения, в кадре) → BSP-порядок (back-to-front / front-to-back со списком рёбер). Каждый слой убирает свою долю.
- Surface cache: освещённая поверхность (texture × lightmap) собирается один раз и кэшируется; перерисовки берут готовое. Память против вычислений.
- Внутренний цикл текстурирования Абраша: FDIV следующего спана перекрывает целочисленное рисование текущего на сдвоенных пайпах Pentium → ~7.5 такта/пиксель (это для 16-пиксельных спанов ASM-пути; дефолтный C-путь шиппинг-сборки делит каждые 8 пикселей). Это удвоило fps относительно наивной версии.
- Софт-рендер сначала. Quake вышел с программным растеризатором; GLQuake/VQuake (аппаратное ускорение) — позже. Аппаратный GPU убрал ручной трюк с FDIV, но PVS/BSP/lightmap’ы остались (в GL surface cache уже не нужен — текстура и lightmap комбинируются на лету).
Системы / БД: PVS — это материализованное представление / предвычисленный индекс: дорогой ответ считаем офлайн, рантайм = lookup. BSP — пространственный индекс, родня k-d tree, BVH, R-tree: режем пространство, чтобы не сканировать всё. Консервативная видимость = «лучше лишнее, чем пропуск» (как в alias-анализе компилятора).
ML / AI: «запеки офлайн — отдавай дёшево» — это предвычисленные эмбеддинги / feature store (тяжёлые признаки считаем заранее, инференс = выборка) и ANN-индексы (HNSW/IVF) — пространственный индекс над эмбеддингами, тот же BSP-над-миром: дробим пространство, чтобы не делать полный скан. KV-cache = surface cache для трансформера: посчитанное состояние префикса переиспользуем. Деление-раз-в-16-пикселей = чанкинг/градиентное накопление: дорогую операцию амортизируем по оси.
Перф: lightmap = мемоизация дорогого вычисления в текстуру; surface cache = переиспользование между кадрами. «Не считай в горячем цикле то, что не меняется».
Принцип: реши заранее всё, что в рантайме неизменно; рантайм пусть лишь выбирает из предсчитанного и амортизирует то, что обязан считать.
~). r_speeds 1 — счётчики нарисованных поверхностей/рёбер. Теперь r_novis 1 — выключаешь PVS: движок начинает рисовать всё в frustum, счётчики взлетают, fps проседает; r_novis 0 — вернулось. Затем r_fullbright 1 (нужен developer 1/читы) — выключаешь lightmap’ы: атмосферный свет исчезает, мир становится плоским.🕹 В какие игры поиграть — и что заметить
От 2.5D-предшественника к настоящему 3D и к тому, что на нём выросло. По каждому: что внутри и во что сыграть/что ввести в консоль.
2D-BSP, сектора с высотами, нет наклона камеры и комнат-над-комнатами. Тот же приём предвычисления, но в плоскости — отличный контраст к Quake.
🎮 Сыграй: в Doom попробуй найти комнату прямо над другой — её нет; стрельба по вертикали идёт автоприцелом. Это потолок 2.5D. Разбор — в уроке Doom и BSP.
Из ячейки камеры рисуется только её PVS. Это можно выключить и увидеть цену.
🎮 Сыграй: в QuakeSpasm введи r_speeds 1, затем r_novis 1 — счётчик поверхностей подскакивает в разы, fps падает: движок рисует весь потенциально-кадровый мир без отсечения по видимости. r_novis 0 — PVS снова режет. Ты буквально щёлкаешь предвычисленную видимость.
Произвольная геометрия и полный обзор — то, чего Doom не мог.
🎮 Сыграй: в Quake найди балкон над залом (комната над комнатой — невозможна в Doom) и посмотри строго вверх и вниз. Свободный наклон камеры + перспективно-корректные стены под любым углом = настоящий 3D, не псевдо.
Вся «атмосфера» уровней — статические lightmap’ы поверх текстур.
🎮 Сыграй: с developer 1 введи r_fullbright 1 — тени и градиенты света исчезают, мир становится равномерно ярким и плоским. Включил-выключил — увидел, сколько настроения несёт один запечённый слой света.
GoldSrc (Half-Life) и Quake II стоят на том же BSP+PVS+lightmap, добавив цветной свет и больше динамики. Архитектура видимости и запечённого света дожила до конца 90-х почти без изменений.
🎮 Посмотри: в Half-Life то же дерево BSP и PVS; developer 1 и аналог r_speeds покажут ту же механику отсечения. Сравни цветной свет HL с монохромными lightmap’ами Quake.
1/z раз на строку (постоянная глубина). Quake делит на пиксель/спан, потому что глубина меняется вдоль полигона.Если PVS уже говорит, что видно, зачем ещё frustum culling и BSP-обход?
Почему PVS «потенциально» видимое, а не «точно»?
Doom тоже на BSP — в чём принципиальная разница с Quake?
Зачем запекать свет, если Doom уже менял яркость секторов «динамически»?
Почему перспективную коррекцию делать раз в 16 пикселей, а не на каждый или раз на спан?
- Michael Abrash, «Graphics Programming Black Book» — главы о Quake: surface cache, перспективное текстурирование, модель освещения (бесплатно онлайн).
- Fabien Sanglard, «Game Engine Black Book: Quake» + разбор PVS/порталов и исходников QBSP/VIS.
- Seth Teller, «Visibility Computations in Densely Occluded Polyhedral Environments» (1992) — теория порталов/PVS, которую реализовал Кармак.
- id Software — исходники Quake (GitHub): QBSP, VIS, LIGHT, BSP-обход.
- Модуль 3 (
03-3d-revolution-1993-1999.md), разделы Quake и Lightmaps.