3D скролл: реализация
Терминология
Небольшой словарик используемых в ста─
тье терминов.
Текстураилислой текстуры- некий бит─
мап, который отображается на экран как на─
клонная плоскость с использованием перспе─
ктивно-корректного алгоритма накладывания
текстуры.
Байт текстуры- какой-либо байт,состав─
ляющий текстуры.Обычно в этих байтах испо─
льзуются не более 4-5 текстурных пикселей
(или битов).Такой трюк необходим для того,
чтобы ограничить на разумном уровне испо─
льзование памяти таблицами (см. ниже).
Для примера рассмотрим слой текстуры из
демы New Wave(рис. 8).Слева то,как он на
самом деле хранится в памяти, справа - с
вычтенной константой. Можно заметить, что
каждый символ имеет ширину 5 или менее
пикселей и располагается в битах 4..0 каж─
дого текстурного байта.
Экран,экранный байт- всё,что относится
к ZX-экрану 6912 байт.
Маппинг- отображение текстуры на эк─
ран.
Горизонтальная, вертикальная таблица-
способ расположения таблиц в памяти. Если
в регистре H - номер (указатель) таблицы,а
в L - индекс в таблице, то такую таблицу
назовём горизонтальной. Наоборот, если L -
номер таблицы, а H - индекс в ней, то таб─
лица вертикальная.
Аналогично можно классифицировать и
буферы с графическими данными: если для
сдвига вправо на 1 байт мы делаем INC L, а
для сдвига вниз INC H или прибавляем кон─
станту к HL - это горизонтальный буфер.
Если INC L сдвигает HL на байт вниз (или
вверх) а INC H - на байт вправо, то это
вертикальный буфер.
Основные идеи
Основная идея, в общем-то, очевидна -
далеко не каждая строчка экрана перерисо─
вывается каждый кадр.В верхней части можно
видеть некий шум и альясинг (как в игре
Doom, если отойти подальше от стены). Там
обычно все строчки перерисовываются каждый
кадр. Снизу, однако, ситуация совершенно
другая, и для скролла на 1 текстурный пик─
сель необходимо провести несколько фаз
обновления экрана (рис. 9). Весь процесс
скролла состоит в последовательном рендере
каждой фазы и сдвиге текстурного буфера на
1 пиксель после того, как все фазы выпол─
нены.Обычно этот сдвиг производится просто
изменением указателя в текстурном буфере.
Так как на каждом шаге в нижней части
экрана обновляются далеко не все строчки,
а в верхней части - строчки относительно
узки,обычно получается весь эффект пускать
в 50 FPS без двойной буферизации (т.е. на
одном экране!),успевая выполнить всю пере─
рисовку вперёд луча. Более того, попытка
использовать два экрана приведёт,очевидно,
к тому, что в нижней части экранов удвоит─
ся число линий, требующих обновления!
Следующая по важности идея заключается
в самом процессе маппинга:как именно байты
текстуры отображаются в байты экрана. Оче─
видно, этот процесс происходит при помощи
множества таблиц - для каждого обновляюще─
гося экранного байта требуется столько та─
блиц, сколько текстурных байт в него попа─
дают (полностью или частично),при этом со─
ответствие между экранными байтами и таб─
лицами фиксированно.В верхней части экрана
несколько текстурных байт отображаются в 1
экранный байт, в нижней - наоборот, один
текстурный байт отображается в несколько
экранных(рис.10).
Все эти разнообразные таблицы генериру─
ются в процессе прекалка, при этом обычно
оказывается (либо этого специально добива─
ются), что количество различных таблиц не
превышает 256. Текстурные байты, как ука─
зывалось выше, обычно имеют 4 или 5 знача─
щих бит,следовательно,все таблицы маппинга
удобно размещаются в вертикальном виде в
блоке памяти размером соответственно 4 или
8 килобайт. Индексом (загружаемым в стар─
шую часть регистровой пары) в таких табли─
цах служит текстурный байт, к которому за─
ранее (при отрисовке в слой текстуры, на─
пример), можно прибавить смещение к блоку
таблиц. Номер таблицы загружается в млад─
шую часть регистровой пары.
Итак, всего вышесказанного достаточно,
чтобы понять суть эффекта.
Реализация
Я, конечно же, не стану загромождать
эту статью бесконечными портянками полного
Z80-кода эффекта, равно как и сорцами пре─
калка - оставлю это в качестве упражнения
для достаточно заинтересованного читателя.
Тем не менее,ниже приведены ключевые фраг─
менты Z80-кода.
Если памяти в избытке (например, у вас
128-килобайтовый ZX), то можно просто сге─
нерить одну большую портянку кода для каж─
дой фазы, которая каждый экранный байт об─
рабатывает соответствующим образом: загру─
жает нужные номера таблиц, шагает по текс─
туре, объединяет значения из таблиц и т.д.
Суммарный объём такого кода (для всех фаз)
оказывается в районе нескольких десятков
килобайт.
Пусть текстурный буфер горизонтален, мы
его читаем при помощи POP , а на экран
указывает DE . Тогда код может выглядеть
следующим образом:
;Шаг на следующую текстурную линию
LD HL,const
ADD HL,SP
RES 4,H;не допускаем выход за
;пределы буфера
LD SP,HL
;один байт текстуры распределяем на
;несколько экранных байтов
POP BC;берём сразу 2 текстурных байта
LD L,table_number1
LD H,C;текстурный байт - индекс
;в таблице
LDI;из таблицы прямо в экран
LD L,table_number2
LDI
LD H,B;следующий текстурный байт
;несколько текстурных байтов попадают в
;один экранный
POP BC
LD L,table_numberЗ
LD H,C
LD A,(HL);кусочек - из одной таблицы
LD L,table_numberЧ
LD H,B
OR (HL);кусочек - из другой
;и объединяем
LD (DE),A;результат на экран
INC E;следующий экранный байт
Скорее всего,такой код почти оптимален.
Однако, если поставить перед собой задачу
реализовать такой же эффект в пределах
48 килобайт, то основной проблемой станет
неприемлемый размер этого кода.Для радика─
льного сокращения кода я использовал сле─
дующий трюк, подсказанныйAlone Coder'ом.
Вместо того, чтобы в прекалке генерить
целиковую простыню, которая целиком ренде─
рит одну фазу, можно поискать закономерно─
сти и похожие кусочки в коде - например,
можно выделить кусочек, который продвигает
текстурный указатель, кусочек,который мап─
пит несколько текстурных байтов в один эк─
ранный и т.д.
Всего таких кусков получается несколько
килобайт. Для управления последовательнос─
тью вызова таких кусков и снабжения их
данными (например,номерами таблиц) исполь─
зуется,очевидно,стек. Переход на следующий
кусок -RET , чтение необходимых данных -
POP .
Далее идёт пример из48К демы New Wave.
В этом примереHL указывает на вертикаль─
ный текстурный буфер,BC на экран.
POP BC;загружаем новую позицию
;в экране
LD A,L
POP HL
ADD A,L
LD L,A;пропускаем 1 или более строк L
;в текстурном буфере
;(всего в нём 256 строк)
;заодно грузим новую
;горизонтальную позицию H в нём
RET;идём на следующий кусок
POP DE;берём номер таблицы в E
LD D,(HL);подставляем индекс в этой
;таблице из текстурного буфера
LD A,(DE);лукап в этой таблице
LD (BC),A;и на экран
INC C;следующий байт на экране
INC H;след. байт в текстурном буфере
RET;следующий кусок кода
POP DE;всё то же самое,но комбинируем
LD D,(HL);в одном экранном байте
INC H ;несколько текстурных,
LD A,(DE);пропущенных через несколько
POP DE ;разных таблиц
LD D,(HL)
INC H
EX DE,HL
OR (HL)
EX DE,HL
LD (BC),A
INC C
RET
Такой код, конечно, не так быстр и не
так элегантен (например,EX DE,HL два раза
- это же звиздец!), но зато сносно помеща─
ется и работает в 48K .
Прекалк
Прекалк уже был упомянут несколько раз
и не зря:ведь это именно то,что превращает
некий рандомный Z80-код с лукапами через
непонятные таблицы в рабочий визуальный
эффект.
Данная процедура довольно затратна по
меркам Z80: предположительно она длилась
бы многие десятки секунд (если не минут)
на ZX. Мой прекалк был написан и работал
на пц.
Примерный план того, что происходит в
прекалке:
Задаёмся исходными данными: наклоном
текстурной плоскости, положением, шириной,
количеством фаз и т.д.
При помощи рейкастинга определяем мап─
пинг текстурных пикселей в экранные.Тут же
можно проверить, например,что видимых тек─
стурных линий меньше, чем 256 минус высота
шрифта - чтобы не видеть процесс печати
новых символов где-то далеко на экране.
Объединяем пиксели в байты и составляем
таблицы маппинга,выкидываем повторяющиеся.
Проверяем,что всего таблиц не более 256.
Наконец, генерируем код вывода фаз, или
(для случая48K ) блоки кода и управляющую
таблицу.
Заключение
Я полагаю, что всей вышеизложенной ин─
формации вполне достаточно для того, чтобы
воспроизвести описанный эффект. Также воз─
можны следующие улучшения:
- текстурная поверхность может быть вол─
нистой,изогнутой по вертикали и горизонта─
ли. Изогнутость может привести к необходи─
мости удаления невидимых частей этой пове─
рхности.
- весь прекалк можно выполнить на Z80,
например, упихав весь эффект в 1 кБ.
- можно раскрасить каждую строку текста
в свой цвет.
- ну, и всё остальное, что взбредёт в
голову :)
Наконец, спасибки:
-Alone Coderза трюк с фрагментами кода
и управлением при помощи данных на стеке.
Ну, и за бессменную работу над Info Guide.
-Bolek,ещё разAlone CoderиEllvisза
ценную историческую инфу, касающуюся похо─
жих 3D скроллов.
-Stein(автору star wars scroll в C64
демах Trick and Treat и RGB C64 demo )- за
вдохновение.
Автор :lvd^mhm, lvd.mhm@gmail.com
Other articles: