← Модуль 2/SNES Mode 7
EN
Модуль 2 · Console Wars

SNES Mode 7: псевдо-3D аффинным преобразованием

Один фоновый слой, который умеет только поворачиваться и масштабироваться, превращается в гоночную трассу до горизонта. Не 3D — построчный хак: перспективное деление 1/z, которое в настоящем 3D считают на пиксель, здесь считают раз на строку.
🏠 лаба~16 мин
Суть за 20 секунд
Mode 7 — единственный режим PPU, где к фоновому слою (тайлмап 1024×1024, 8 бит/пиксель) применяется аффинное преобразование: матрица 2×2 + точка-центр. Для каждого экранного пикселя (Sx,Sy) железо считает обратным отображением, из какого текселя карты брать цвет. Сам по себе это плоский поворот/зум. Иллюзию «уходящей в горизонт трассы» даёт построчный хак: HDMA меняет матрицу на каждой строке развёртки, и каждая строка получает свой масштаб ∝ 1/p (p — пикселей ниже горизонта). «3D-объекты» — это обычные спрайты, приклеенные поверх. Дешёвая иллюзия, отлитая в кремнии.

Механизм: аффинная матрица и обратное отображение

Обычный фон рисуется тайл-в-тайл. Mode 7 вместо этого берёт квадрат карты 1024×1024 и натягивает его на экран произвольным аффинным преобразованием — поворот, масштаб, сдвиг, скос (всё, кроме изгиба: прямые остаются прямыми). Ключевой трюк железа — оно идёт не «куда лёг бы каждый тексель карты» (прямое отображение оставило бы дыры и наложения), а наоборот: проходит по экранным пикселям слева-направо, сверху-вниз и для каждого спрашивает «из какого текселя я?». Это backward mapping — ни дыр, ни перекрытий.

Для экранного пикселя (Sx,Sy) тексель (Tx,Ty) считается через матрицу [A B; C D] вокруг точки-центра (Mx,My):

(TxTy) = (ABCD) (Sx−MxSy−My) + (MxMy)

(плюс скролл M7HOFS/M7VOFS, который сдвигает картинку после преобразования). Все коэффициенты — знаковый fixed-point 8.8: 8 бит целой части, 8 дробной, деление на 256. Регистры так и называются: M7A…M7D — матрица, M7X/M7Y — центр.

Геометрический смысл столбцов матрицы прямой: (A,C) — на сколько текселей шагнуть по карте, когда сдвигаешься на 1 пиксель вправо по экрану; (B,D) — шаг по карте на 1 пиксель вниз. Отсюда всё:

карта 1024×1024 вид backward сэмпл экран 256×224 Идём по пикселям экрана → для каждого считаем тексель карты. Поворот слоя = поворот «окна-параллелограмма» по карте.

Откуда берётся перспектива: построчное масштабирование

Сама матрица плоская — она поворачивает и тянет, но не создаёт глубины. Перспектива «в горизонт» — это разная матрица на каждой строке развёртки, заливаемая через HDMA. Почему именно построчно? Потому что горизонтальная строка плоского пола лежит на постоянной глубине z. Перспективное деление 1/z вдоль строки не меняется → достаточно посчитать масштаб один раз на строку.

Камера на высоте h над полом, горизонт проецируется в фиксированную строку экрана. Для строки на p пикселей ниже горизонта глубина пола и масштаб (текселей на пиксель):

z(p)= h·fp масштаб(p)= hp

(f — фокусное в пикселях). У горизонта p→0 → масштаб → ∞ (бесконечно сжато, далеко); внизу экрана p велико → масштаб мал (близко, крупно). Эту 1/p-зависимость HDMA и зашивает: на каждой строке свои M7A=M7D=масштаб(p).

Числовой пример. Пусть h = 256 текселей. Строка у горизонта p=8 → масштаб 32 текселя/пиксель; p=64 → 4; нижняя строка p=128 → 2. Один и тот же ориентир в 256 текселей у горизонта займёт 256/32 = 8 px, а под колёсами — 256/2 = 128 px. Вот градиент глубины: дальнее сжато в 16 раз сильнее ближнего — потому что масштаб(8)/масштаб(128) = 128/8 = 16.

камера горизонт экран пол (плоскость) дальние строки → пол далеко (шаг мелкий, ∝1/p) ближние → пол близко (шаг крупный)

🕹 В какие игры поиграть — и что заметить

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

F-Zero 1990 · чистый Mode 7, 60 fps

Стартовый тайтл SNES, без сопроцессора. Один плоский слой-трасса + построчный HDMA-масштаб = дорога до горизонта на 60 кадрах. Машина — это спрайт по центру, который не поворачивается вовсе: руль крутит весь мир под ней (матрица поворота слоя). Над горизонтом — отдельный растровый небосвод, под ним — тот самый трансформируемый слой. Однопользовательский: полноэкранная построчная матрица съедает почти весь бюджет кадра.

🎮 Сыграй: в F-Zero войди в долгий поворот и смотри на машину — она стоит, а мир вращается вокруг неё. Поймай взглядом линию горизонта: всё выше неё — статичное небо, всё ниже — единственный аффинный слой.

Super Mario Kart / Pilotwings DSP-1 на картридже

Перспективы мало — нужно ещё быстро считать коэффициенты матрицы на каждый кадр (проекция, тангаж). 65816 в этом слаб, поэтому в картридж вложили математический сопроцессор DSP-1. Mario Kart (1992) к тому же двухэкранный: каждая половина — свой независимый Mode-7-слой, потерю площади компенсируют картой/зеркалом заднего вида. Вырос из прототипа двухпользовательского F-Zero.

🎮 Сыграй: в Super Mario Kart на двоих заметь, что верх и низ экрана — две разные Mode-7-плоскости. Свались с трассы — увидишь, как «земля» наклоняется и масштабируется фоном поверх спрайтов (тот самый трюк «фон перед спрайтом»).

Final Fantasy VI 1994 · карта мира + дирижабль

Mode-7-пол для обзорной карты; города и партия — обычные спрайты, скомпонованные сверху. Наклон/вираж дирижабля = поворот плоскости под неподвижным спрайтом корабля.

🎮 Сыграй: сядь в дирижабль в FF6 и разворачивайся — карта мира крутится под тобой (Mode 7), но города и сам корабль остаются плоскими спрайтами, которые лишь масштабируются. Этот разрыв «трансформируемый пол vs плоские наклейки» и выдаёт 2.5D, а не 3D.

Super Castlevania IV вращение/зум как витрина

Комнаты, которые целиком вращаются, и зумы — демонстрация чистого Mode-7-поворота одного слоя. Глубины нет: за объект не зайти, всё лежит в одной плоскости, которую крутят.

🎮 Сыграй: в Castlevania IV дойди до вращающейся комнаты — заметь, что нельзя оказаться «за» чем-либо: это один плоский слой, который спинает вокруг центра (M7X,M7Y).

Axelay (фейк) vs Genesis (нет Mode 7) контраст

Вертикальные этапы Axelay имитируют «выпуклую планету» построчным растровым скроллом (line-scroll, «rolling-pin»), а не Mode 7: поверхность изгибается, но не вращается. У Sega Genesis аппаратного Mode 7 не было вовсе — эффект подделывали в софте, дорого для CPU и хуже на вид (Tales of Phantasia на SNES, на Genesis — единичные программные трюки).

🎮 Сыграй: сравни изогнутый горизонт Axelay (line-scroll) с настоящим Mode-7-полом F-Zero. Тест: line-scroll гнёт строки, но не умеет повернуть сцену; аффинный слой — поворачивает, но строки остаются прямыми.

Хардкор · теория: обратное отображение, fixed-point и почему перспектива «бесплатна» построчноможно пропустить

Почему именно backward mapping

Прямое отображение (тексель → экранный пиксель) при увеличении оставляет дыры (соседние тексели разлетаются), при уменьшении — перекрытия (несколько текселей в один пиксель, кто последний — тот и виден, мерцание). Обратное (пиксель → тексель) гарантирует ровно одно чтение на выходной пиксель: покрытие плотное и однозначное. Цена — нужно уметь инвертировать преобразование; для аффинного это просто обратная матрица, поэтому железо считает (Tx,Ty) по (Sx,Sy), а не наоборот.

Перспектива как вырожденный случай 1/z

Перспективно-корректное текстурирование требует деления на глубину для каждого экранного пикселя: u = (u/z)·z. В общем 3D глубина z меняется вдоль отрезка → деление на пиксель (дорого; Quake делал его раз в 16 пикселей — см. дальше). Но горизонтальная строка плоского пола под камерой лежит на постоянной z → 1/z постоянна вдоль строки → одно деление на строку. Mode 7 — это и есть честная перспектива в вырожденном случае «вся строка на одной глубине». HDMA просто подаёт посчитанный масштаб(p)=h/p построчно.

Fixed-point 8.8 и мерцание

Шаг по карте (A,C) хранится как 8.8 (точность 1/256 текселя). У горизонта масштаб огромен (десятки текселей на пиксель), поэтому крошечная дробная разница в шаге между кадрами перепрыгивает через много текселей. Без мип-уровней и фильтрации (их у Mode 7 нет, выборка — nearest) это даёт алиасинг и «рябь» вдали. Та же причина, по которой далёкая текстура без мипмапов «искрит» на любом железе.

Хардкор · инженерия: HDMA, бюджет кадра и сопроцессорможно пропустить
  • HDMA = DMA по строкам. Перед каждой строкой развёртки контроллер DMA сам пишет новые M7A…M7D из таблицы в RAM, не трогая CPU. Так задаётся «разная матрица на строку» без участия процессора в реальном времени.
  • CPU всё равно считает таблицу. «Бесплатна» только растеризация. Коэффициенты масштаб(p) на ~224 строки на каждый кадр кто-то должен посчитать — это работа CPU (или DSP-1). Отсюда 1/p-таблица часто предвычислена.
  • DSP-1 — математический сопроцессор на картридже (Pilotwings, Super Mario Kart): проекция, умножение матриц, тригонометрия. Голый 65816 это тянет только в простом случае одного плоского слоя (F-Zero) — поэтому F-Zero без чипа, а Mario Kart с чипом.
  • Один слой. Mode 7 — это BG1 и только он; параллакс-слоёв нет. Небо над горизонтом рисуют отдельно (растровый сплит цвета/смена настроек в середине кадра), а «объекты» — спрайтами поверх. Никакой настоящей окклюзии: сквозь спрайт «видно», потому что нет буфера глубины.
  • Genesis: быстрее CPU ≠ Mode 7. Это фиксированная функция в PPU SNES, а не вопрос мегагерц. VDP Genesis аффинного режима не имел; софт-эмуляция съедала кадр и выглядела хуже.
Аналогия
Mode 7 — это лист с картой под наклонным стеклом. Лист плоский; из-за угла взгляда дальний край «уходит вдаль», и чем дальше строка взгляда, тем более крупным шагом ты «читаешь» карту. Поднять над листом башню нельзя — всё, что «стоит», это отдельные наклейки (спрайты), приклеенные поверх стекла. Перспектива здесь — не объём, а построчно растущий шаг чтения одной плоской текстуры.
Почему это важно
Mode 7 — чистый урок «дешёвая иллюзия под структуру задачи вместо дорогой общей честности». Увидеть, что перспектива тут — это построчное масштабирование одной текстуры, а «3D-объекты» — приклеенные спрайты, значит научиться отличать что эффект имитирует от что он реально вычисляет. Это та же граница, по которой инженер отделяет «выглядит как X» от «является X».
🔁 За пределами игр — куда это переносится
Mode 7 — это два переносимых приёма: обратное отображение (сэмплируем источник из приёмника) и амортизация дорогой операции по оси, где она постоянна (деление 1/z — раз на строку, не на пиксель).

Графика / обработка изображений: любое текстурирование, поворот/ресайз картинки, image warping, ремаппинг линз — это backward map + интерполяция (так не возникает дыр). Перспективно-корректное текстурирование Quake — общий случай построчного деления Mode 7 (следующий урок).

ML / AI: Spatial Transformer Networks и операция grid_sample — это буквально обратное аффинное отображение карты признаков с билинейной выборкой, тот же Mode 7 в дифференцируемом виде; на нём же стоят img2img-варпы и аугментации. Глубже — принцип «вынеси инвариант из внутреннего цикла»: считать дорогое там, где оно постоянно (раз на строку), — это loop-invariant code motion, переиспользование KV-cache (один префикс на много токенов), пер-батч, а не пер-пример.

Перф / системы: предвычисленная таблица 1/p вместо деления на пиксель — классический «таблица вместо арифметики» (LUT). HDMA — это смена состояния по расписанию строк, прообраз command-buffer'а / пер-draw-call апдейтов юниформов в современном рендере.

Принцип: аппроксимация, выбранная под структуру задачи (плоскость → строка постоянной глубины), бьёт честное общее решение, когда структура есть и её видно.

🏠 Лаба — Mode 7 вживую
Интерактивная лаба без кода: крути поворот и высоту камеры, двигай горизонт, переключай «плоский аффин ↔ построчная перспектива» — и смотри, как один и тот же слой то лежит плоской вращающейся картой, то уходит трассой в горизонт. Открыть лабу →
Лучший момент: выключи перспективу — останется просто поворот плоского слоя; включи — появится горизонт. Подними «ретро-точность» fixed-point — у горизонта поползёт рябь.
🔧 Запусти и поковыряй — на домашнем компе
Во что поиграть — выше (🕹). Здесь — для тех, кто хочет залезть в регистры:
🔧 Поковырять (debug) ~30 мин, эмулятор/симулятор
Открой браузерный симулятор Mode 7 (Telinc1 / Novasquirrel с SNESdev) и покрути M7A…M7D, центр, скролл — увидишь поворот/масштаб/скос на живой текстуре. Затем в bsnes-plus / Mesen-S запусти F-Zero, открой просмотр слоёв и трассировку HDMA: увидишь, как перед каждой строкой в матрицу пишутся новые значения масштаба. Выключи BG1 — мир исчезнет, останутся спрайт-машина и небо.
🧪 Потестить (глазами QA) ~15 мин
Ищи характерные «тычки» Mode 7: рябь/искрение текстуры у горизонта (нет мипмапов), алиасинг дальних краёв, шов на линии горизонта между растровым небом и слоем, отсутствие окклюзии (спрайт не закрывается «холмом», потому что глубины нет). Типовой чеклист артефактов псевдо-3D.
Чеклист: покрутил матрицу в симуляторе; увидел HDMA-запись матрицы по строкам в эмуляторе; нашёл хотя бы один артефакт (рябь/шов/нет окклюзии).
Связи
основа
Картридж, мапперы и тайл-движки — Mode 7 это один из режимов PPU; здесь же введены тайлмапы, scanline-IRQ и HDMA, на которых стоит построчный трюк.
контраст
Doom: BSP и рейкастинг — другой «3D до 3D» той же эпохи: Doom фейкует объём вертикальными столбцами по BSP, Mode 7 — трансформируемым полом. Разные иллюзии, обе без настоящего Z.
дальше
Quake: настоящий 3D — от построчного деления к перспективе на пиксель, буферу глубины и реальным полигонам. Mode 7 — вырожденный частный случай того, что Quake делает в общем.
Вопросы пытливого ума
Если Mode 7 крутит один плоский слой, почему в F-Zero машина не поворачивается, а мир вращается вокруг неё?
Потому что «машина» — это спрайт, жёстко приклеенный к центру экрана, и его Mode-7 вообще не трогает. Руль меняет угол θ в матрице фона → поворачивается слой-трасса, а спрайт стоит. Это и дешевле (одна матрица на весь мир), и автоматически держит камеру привязанной к болиду. Тот же приём в любой Mode-7-гонке: вращается мир, не аватар.
Почему перспективное деление в Mode 7 — одно на строку, а в Quake — почти на пиксель?
Горизонтальная строка плоского пола под камерой лежит на постоянной глубине z → 1/z вдоль строки не меняется → одно деление на строку. В настоящем 3D отрезок полигона уходит вглубь, z меняется вдоль него → нужна перспективная коррекция на пиксель. Quake амортизировал её, деля раз в 16 пикселей и интерполируя между ними аффинно. Mode 7 — это предельный случай, где «раз в N пикселей» превратилось в «раз на всю строку», потому что N = вся ширина.
Genesis был мощнее SNES по тактовой CPU — почему у него нет Mode 7?
Mode 7 — это фиксированная функция в PPU SNES (встроенный аффинный растеризатор), а не вопрос мегагерц процессора. У VDP Genesis такого режима в кремнии нет; подделать аффин в софте можно (Tales of Phantasia, единичные демки), но это съедает кадр и выглядит хуже. Урок: специализированный аппаратный блок бьёт более быстрый универсальный CPU на той единственной задаче, под которую он разведён.
Зачем Mario Kart и Pilotwings чип DSP-1, если Mode 7 «бесплатный» в железе?
Бесплатна только растеризация готовой матрицы. Сами коэффициенты — проекцию, тангаж, поворот, иногда позиции «3D»-объектов — надо посчитать, а 65816 в float-подобной математике медленный. DSP-1 — математический сопроцессор на картридже, который это берёт на себя. Плоский одно­слойный случай F-Zero достаточно прост для CPU, поэтому там чипа нет; более сложная проекция Mario Kart (+ два экрана) — уже нет.
Почему текстура у горизонта в Mode 7 рябит и «искрит»?
У горизонта масштаб огромен — десятки текселей на один экранный пиксель. Выборка nearest-neighbor без мип-уровней и фильтрации (их у Mode 7 нет) + шаг в fixed-point 8.8 означают, что крошечное дробное изменение шага между кадрами перепрыгивает через много текселей → алиасинг и мерцание. На современном железе это лечат мипмапами/трилинейной фильтрацией; у SNES их попросту не было.
Что почитать