Не так давно я публиковал здесь небольшой обзор по работам в жанре процедурной графики и, в частности, призывал поучаствовать в конкурсе. Упомянутый мной конкурс на фестивале Chaos Constructions состоялся - было представлено семь работ размером до 1кб, о которых, особенно о двух собственного изготовления, я и хочу рассказать.
Первая моя работа называется Way и написана для платформы Sony Playstation 1 (PSX) на ассемблере MIPS R3000.
Хотя RISC процессоры обычно не лучший выбор для написания компактного кода, но в случае с PSX это компенсируется способностью GPU аппаратно рисовать примитивы.
По сути, мы просто перечисляем примитивы в специальном формате и потом отправляем этот список (display list) в GPU, предварительно установив графический режим 640x480.
К примеру, вот так описывается небо - четырёхугольник с заливкой градиентом (Гуро):
sky
db soil, soil>>8, soil>>16, $8 ; link to next list element, size of current element
dw $38aa5b00 ; $38 - gradated 4p poly. CCBBGGRR (C - command, b,g,r - color0) $38c4deeb
dw $00000000 ; y0 x0 0 0
dw $30bb5b00 ; color 1
dw $0000027f ; y1 x1 639 0
dw $30f3dec6 ; color 2
dw $01300000 ; y2 x2 0 304
dw $30f3dec6 ; color 3
dw $0141027f ; y3 x3 639 321
Горизонт и другие линии намеренно идут непараллельно краям экрана, чтобы получить некий эффект мультяшности.
Вот исходник.
Совершенно аналогичным образом устроена и работа "Invitation to CC'2024" (928 байт) - с некоторым закосом под супрематизм, при этом буквы формируются "вырезанием" одних четырёхугольников из других (рисованием их в цвет фона).
Несколько сложнее обстоят дела с другой моей работой - Face (646 байт), написанной для платформы Vectrex.
Особенностью игрового компьютера Vectrex является векторный дисплей - растра нет, лучом нужно управлять вручную (с некоторыми оговорками). Я уже подробно рассказывал об этой платформе, поэтому здесь опишу суть происходящего не слишком углубляясь.
Лицо состоит из нескольких элементов - брови и границ глаза (прямые линии), зрачка глаза (кривая) и контура лица (две кривых).
С прямыми всё просто - есть подпрограмма BIOS Draw_Line_d. А вот с кривыми - сложно.
Суть подхода в том, что мы вручную запускаем интегрирование - луч начинает перемещаться сверху вниз от текущего своего положения до заданного нами значения Y. В это время мы записываем в канал X разные значения и таким образом заставляем луч смещаться влево (если значения отрицательные) и вправо (если положительные). После записи очередного значения мы или сразу записываем следующее или некоторые время ничего не делаем, предоставляя лучу продолжить движение в прежнем направлении. Это "ничегониделанье" первоначально выглядело как куча NOP'ов после каждого изменения X, причём я приклеил к экрану кусок пергамента с рисунком и подбирал значения в VIDE эмуляторе.
...
lda #30
sta <VIA_port_a ; put X to DAC
nop ; 2 cycles for each nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
...
В 1кб при таком подходе не уложишься, поэтому все длинные последовательности nop'ов я свернул в циклы. Причём, сами инструкции подготавливающие и реализующие цикл конечно тоже имеют некую длительность выполнения, что тоже пришлось учитывать, посчитав все такты (ну, примерно):
...
lda #30
sta <VIA_port_a ; put X to DAC
; 10
ldb #2 ; 2 cycles
d6a: decb ; 2 cycles
bpl d6a ; 3 cycles
...
Вообще, первая мысль которая должна возникать при взгляде на этот код - что там куча повторяющихся кусков и все задержки можно свести в таблички и доставать их оттуда. Увы, любой лишний такт приведёт к продлению рисуемого вектора, поэтому простое решение здесь не сработает. Теоретически можно написать генератор кода - т.е. чтобы программа по данным из таблички воссоздавала нужные последовательности инструкций. К сожалению, у Vectrex-а всего лишь 1кб ОЗУ - упихать наверное можно, но игра в данном случае не стоит свеч (в 1кб и так всё влезло).
Отдельная проблема - наличие в силуэте лица элементов, близких к горизонтальным (возле губ). Дело в том, что луч движется сверху вниз с постоянной скоростью - его нельзя притормозить. Крутизна изгиба определяется исключительно величиной записываемого в X значения. Но больше чем +/- 127 туда, по понятным причинам, записать нельзя.
Можно было бы сделать эти два элемента просто отдельными линиями, но это привело бы либо к видимым нестыковкам концов линии и прилегающей кривой, либо к ярким точкам в местах соединения. В результате я сделал силуэт из четырёх кривых, каждая из которых начинается там, где заканчивается вторая. Тот факт, что интегрирование запускается заново, позволяет лучу с самого начала сегмента двигаться влево (и вправо), таким образом получаем близкие к горизонтальным линии.
Зрачок - просто кусочек кривой.
Летящие справа светящиеся объекты отображаются вызовом подпрограммы BIOS Dot_List, которая рисует векторы с нулевой длиной по списку координат. Подпрограмма вызывается трижды со смещением по горизонтали, при этом после каждого вызова меняется яркость (чтобы создать эффект motion blur).
Текст также выводится подпрограммой BIOS Print_List_hw, что, при таком количестве текста, является весьма неэффективным методом. Однако, в данном случае он оправдан - во-первых с целью экономии байт (вызов стандартной процедуры короче написания своей), во-вторых всё вместе (текст, прямые, кривые) укладывается в 30 тыс тактов процессора, что необходимо чтобы избежать мерцания и других нежелательных эффектов.
Хотя изначально эта работа задумывалась именно как процедурная графика, в процессе вышеописанных развлечений мне пришла в голову мысль, что можно сделать лицо слегка шевелящимся, просто изменяя в некоторых местах значения на которые отклоняется луч и задержки после записи этих значений. Так появилась ещё интро Aniface (820 байт),
Видео не передаёт теплоты и ламовости векторного CRT монитора (да и фото - лишь отдалённо). Увы, тут надо весьма серьёзную видеокамеру (нужен широкий динамический диапазон и высокая светочувствительность) и много времени на подбор параметров съёмки.
Напоследок покажу все работы (других авторов), представленные на данный конкурс фестиваля Chaos Constructions:
Дисквалифицированная работа Rebranding by artёmka // wbcbz7 - работа написана для TIC-80, но по правилам конкурса работы для виртуальных платформ не принимаются
Шестое место - Ancient Aztec Matrix 128b by g0blinish (125 байт) для Commodore 64 (процессор 6510)
Пятое место - моя, уже упомянутая выше, работа Way by Frog //ROi
Четвёртое место - Reptile 1k by g0blinish для ZX Spectrum (процессор z80)
Третье место - моя, уже упомянутая выше, работа Face by Frog //ROi
Второе место - Teonon by dart / fnm (1022 байта) - единственная из всех, написанная для современной платформы (PC/Windows)
Победитель - работа RTZX by RCL of Virtual Vision Group (983 байта).
Как пишет в пояснении к работе сам автор, им реализован честный raytracer для ZX Spectrum 48K (процессор z80). На входе положение камеры и четыре примитива. Работа была написана под впечатлением реализации raytracer'а на Бейсике, в котором рендеринг шёл 17 часов. Конкурсная работа рендерится 43 секунды.
Места определялись голосованием зрителей - интересно, что RTZX победила, т.к. люди, очевидно, смогли оценить сложность реализации raytracer'а на z80 в таком объёме кода.
Все работы можно скачать здесь или здесь.
В целом меня, как одного из организаторов Chaos Constructions, особенно топившего за этот конкурс, весьма порадовали представленные работы - все они (включая дисквалифицированную) весьма достойны.