Тот кто когда-нибудь задумывался о том как работает графическая часть 2д ретро ускорителя, примерно представляет как именно она рисует эти пресловутые Tiles, которые к слову из определения не обязаны быть прямоугольными. Тайлинг это про замощение плиткой. Да чаще всего разработчики железного апи понимают это и методы соответственно называют drawRect а не drawTile. Любой прямоугольник действительно может быть тайлом, но обратное не верно! И тут назревает вопрос: Почему 2D ускорители упорно ускоряют только rect… Простой ответ на этот вопрос потому что все остальное слишком сложно для простой железки. Но тут я бы и поспорил. Можно предложить как минимум одно простое, но очень функциональное расширение этой базовой абстракции, следующее.
Я давно не писал на Хабр. Потому что все время хотелось сделать идеально… есть у меня такая болезнь. Сам Хабр тем временем ощутимо снизил планку, а я все не пишу да не пишу. Поэтому сегодняшняя статья родившаяся без подготовки из просто беседы с другом. Однако есть такие друзья с которыми хочется беседовать глубоко и обстоятельно.
Пусть она станет, быть может, моим возвратом к регулярности. Если такое плюс-минус легкое чтиво, с накиданными в паинте на скорую руку, но минимально достаточными для пояснения картинками, не перегруженное деталями повествование, однако все еще с концептуальным акцентом, и ламповость как когда-то, зайдет дорогому хаброчитателю.
Иными словами, сегодня я ищу компромис между качеством контента и временем, которое могу на него уделить. Надеюсь на понимание.
Итак начнем…
Рассмотрим для образца общую функцию копирования произвольной прямоугольной области. Видим что это просто двойной цикл по строкам и по элементам строк:
Заметим, что этот блок кода обычно тривиально повторяют в х4 комбинациях направлений обхода обоих циклов для реализации Отражений:
Еще х2 комбинаций дает перестановка порядка на dst[x][y] отражая по оси y=x.
Попутно эти 8 вариантов отражений являются всеми возможными поворотами кратными 90гр со всеми их зеркальными отражениями слева направо.
А теперь посмотрим на то, как еще минимальными добавлениями можно было бы сильно видоизменить данный базовый элемент.
Возможно никому не приходило это в голову, но почему тайл не может быть например параллелепипедом? Забегая вперед скажу, что это расширение носит минимальные накладные расходы, потому что для реализации этого необходимо добавить всего пару инкрементов на строку:
В результате комбинации с x/y перестановкой мы получим параллелепипед парой сторон всегда ориентированных по одной ортогонали, а две других будут под углом. Что же нам дает эта дешево полученная мелочь? Ну во первых возможность использовать три фиксированные проекции:
Если приращение сделать не константой, а дробным числом, угол сдвига сможет стать свободным. Что прекрасно может быть использовано как для эффектов покачиваний тайла в сайд скроллере:
Так и для построения стен в пространстве изометрических проекций:
И помним, что говоря дробные это не означает непременную поддержку float! О чем современный избалованный программист порой начинает забывать. float это дробные с Плавающей точкой, а тут она совершенно не нужна. “Поддержкой” же дробных с фиксированной точкой (fixed point), обладает Аппаратно по сути любой процессор имеющий сдвоенные регистры, где верхний из пары может читаться как отдельное значение. Более того в этом случае НИ одной лишней инструкции не добавиться. (Ну за исключением того что задаваться наклон будет в 256-ых долях) Так что это все бесплатно, берите ребята!
Еще один аналогичный шаг который попроситься по индукции у внимательного читателя, это повторение этого функционала ко второй границе перебора внутреннего цикла. Мы ведь можем отдельно развязать приращение end. И тогда в общем случае мы сможем рисовать любые лежащие основанием на одной из ортогоналей трапеции (как частный случай треугольники):
И на хрена же они нам сдались? Подумаете вы, но тут же сами догадаетесь, что всего за два таких вызова отрисовки, вы элементарно текстурируете плоскость из ортогонально ориентированных шестигранников или диагонально ориентированных ромбов, из предварительно подготовленного под это тайлсета. Довольно часто встречающиеся в игрострое кейсы замощений, между прочим.
Было бы здорово иметь для них аппаратную поддержку на ускорителе:
Дальнейшее описанное является скорее бонусным функционалом, т.к. реализовать его целенаправленно можно лучше. Однако, если говорить в том числе только о заливке примитива только цветом (или плоским паттерном), для каких то простых дополнений имеющегося игрового пространства…
Той же парой вызовов отрисовки можно составить любой не ограниченный частностями треугольник (он просто сечется на два ортогональной линией по средней точке, это очень похоже на его классический алгоритм отрисовки) И из этого уже можно строить построения в перспективе. Кстати из самих трапеций например элементарно дешево получается вертикально фиксированная 3D проекция DOOM1 вида:
При желании можно пробовать рисовать заливкой даже что-то более менее крупно-воксельное. В зависимости от степеней свободы проекции затрачивая больше или меньше вызовов отрисовки. Первые две перспективные проекции ниже являются фиксированными однако каждая из них имеет 4 симметричных представления — итого 8 углов отображения. Третья является общим случаем и фиксирована только вертикально. Вращаясь вокруг вертикальной оси она будет попеременно переходить то в первую, то во вторую:
Разбиения фигур на трапеции лучше реализовывать по возможности на более вытянутые вдоль ортогонали разреза куски, потому что дополнительный функционал вызова хоть и не дорог, но связан с итерациями внешнего только цикла, которых желательно что бы было меньше. На самом деле это справедливо даже для рисования базового алгоритма прямоугольника — горизонтальный рисовать эффективнее вертикального.
Выполнение на бонусном функционале перспективного текстурирования в этих вызовах, возможно, но требует реализации дробного скейлинга и связано с внесением дополнительных коэффициентов расхождения при src, и коэффициентов прореживания, которые пуще того необходимо будет раз это 3D довольно криво рассчитывать в динамике, что не целесообразно для данной простой архитектуры.
Ну и заканчивая реванш перед Хабром за свой собственный интерес, я все же не удержусь кратко пояснить откуда берется вся эта магия коэффициентов приращения алгебраически. Формула прямой y=kx+b которую учат в школах, передает нам здесь привет сразу дважды. Все коэффициенты пригодились на своих местах:
Почему же мне не встречались аппаратные 2д ускорители с такой реализацией тайлов, я могу лишь гадать:
- Понятие Тайл было столь жестко зафиксированным на практике шаблоном, что никто и не думал, что можно было бы сделать чуть больше… получив много больше.
- Или никто не хотел потратить и пары тактов на такие функции, потому что не анализировал изложенные широко простирающиеся следствия, или спроса на них тогдашние разработчики с незамысловатыми играми не создавали. Тем не менее был же mode7 (читовый алгоритм реализации перспективного текстурирования, через вывод кусками прямоугольных спрайтов на железе с поддержкой дробного скейлинга)
- Или я недостаточно олд, и мне надо олдеть дальше...
shiru8bit
Четырёхугольные спрайты-полигоны были реализованы в 3DO, Sega Saturn, в некоторых аркадных автоматах и в видеокарте NVidia NV1.
Mode7 никакого отношения к перспективе сам по себе не имел, по сути это всего лишь один составной спрайт (тайловая карта), который можно вращать и масштабировать. Его нельзя было наклонять или двигать отдельные углы. Это достигалось уже программно, изменением параметров вращения и масштаба на каждой строке растра таким образом, чтобы в эту строку попала нужным образом повёрнутая и растянутая строка исходного спрайта — как бы построчная растеризация полигона.
impfromliga Автор
Да я писал его на высоком уровне по приколу. Но на сколько я узнавал исторически. На консолях это был специальный режим рисования спрайта, который аппаратно брал на себя ускорение конкретно афинных преобразований. Этого хватало только для поворота, масштаба и сдвига. Потому оси X и Y вращать было можно, — это есть в афинных. А вот Z нету, и ни как виртаульно она не добавлялась. Впрочем прямого управления ею нет в большинстве 3д игр. Перспектива же добавлялась хаком нарезания полос, но не обязательно только по строкам растра, иногда даже по несколько строк одним ректом, в зависимости от требований скорость/качество. Чем ближе была полоса, тем больше зум. А в консолях шел этот режим за номером семь. Я вспомнил его здесь как пасхалочку для знатоков.
Нашел сейчас его у себя в загашниках, но версию с классическим MODE7 для поворота/наклона/смещения положенного в перспективу пола деплоить лень (она не тема этой статьи и для интересующих это гуглиться) Зато могу задеплоить бенчмарк, который как раз не тривиально использует наклон:
codepen.io/impfromliga/full/qBNomVO
Скрол меняет угол наклона в поверхность.
Дополнительно колесо мыши меняет «скважность» полос для теста скорость/качество
Очень наглядно видно насколько большими полосами можно было относительно качественно эту перспективу хакать.
shiru8bit
Всё же всего на одной консоли, SNES. Схожий функционал в GBA и Sega CD имеет свои номера (2 на GBA и никакого на Sega CD). Mode 7 просто стало именем нарицательным для перспективной проекции а-ля трасса в F-Zero, это название использовалось в рекламных материалах (на коробке с игрой) и потому ушло в народ, не особо разбирающийся в технических деталях.
impfromliga Автор
Да я понимаю что нарицательным, сам его так же использую, потому что keyword гуглиться. Просто лично его не застал, не достаточно олд я…
Хотя при анализе приема, для чего писался бенч, — Возможности этого режима позволят рендерить не только пол/небо, но даже что-то вроде DOOM1 like рейкастинга, теоретически. Только кастить надо будет интеллектуально, что бы получать углы стен.
— Кстати нужно заметить что HTML5 Canvas окружение без webGL является по сути тем же окружением. Встречал на этом алгоритмы развертки сферической панорамы в проекцию 3д камеры. Что бы сделать как в yandex панорамах. Как один из fallback'ов для неподдерживающих ничегошеньки устройств. Причем на CSS2 то же есть реализации, но суть не меняется — Суметь нарезать кривое на плоское
shiru8bit
Я могу говорить только за реальный Mode 7 и SNES, т.к. знаю, как они работают и что с ними делать. За абстрактный программный Mode 7-подобный рендер сказать ничего не могу, т.к. его можно сделать каким угодно. Оригинальный же Mode 7 позволил бы рендерить стены а-ля Wolf 3D только если положить телевизор на бок, т.к. мы можем менять параметры рендера каждую строку (через HDMA), но никак не можем менять их каждый столбец.
impfromliga Автор
Ахахах, сразу видно единомышленника…
— Когда я размышлял как успеть выдать рейкастинг с микроконтроллера софтверно синтезирующего VGA сигнал, при том что памяти на экранный буфер у него нет, но пока луч возваращается в стройчной развертке у нас есть 20% времени… И хотя нужно гораздо больше, можно подготавливать строку отложенно, пока предыдущая подготовленная пуляет копии.
Но телевизор на бок все таки прийдется положить…
Человечество просчиталось… развертку надо было делать сначало по строкам.
Так и горизонтальное разрешение было бы выше — что полезно и для шрифта.
Хотя сейчас можно найти дисплеи с возможностью поворота изображения на бок.