Ещё слово о процедурной графике
Не так давно я публиковал здесь небольшой обзор по работам в жанре процедурной графики и, в частности, призывал поучаствовать в конкурсе. Упомянутый мной конкурс на фестивале Chaos Constructions состоялся — было представлено семь работ размером до 1кб, о которых, особенно о двух собственного изготовления, я и хочу рассказать.
Первая моя работа называется Way и написана для платформы Sony Playstation 1 (PSX) на ассемблере MIPS R3000.
Хотя RISC процессоры обычно не лучший выбор для написания компактного кода, но в случае с PSX это компенсируется способностью GPU аппаратно рисовать примитивы.
По сути, мы просто перечисляем примитивы в специальном формате и потом отправляем этот список (display list) в GPU, предварительно установив графический режим 640x480.
К примеру, вот так описывается небо — четырёхугольник с заливкой градиентом (Гуро):
Горизонт и другие линии намеренно идут непараллельно краям экрана, чтобы получить некий эффект мультяшности.
Вот исходник.
Совершенно аналогичным образом устроена и работа "Invitation to CC'2024" (928 байт) — с некоторым закосом под супрематизм, при этом буквы формируются «вырезанием» одних четырёхугольников из других (рисованием их в цвет фона).
Несколько сложнее обстоят дела с другой моей работой — Face (646 байт), написанной для платформы Vectrex.
Особенностью игрового компьютера Vectrex является векторный дисплей — растра нет, лучом нужно управлять вручную (с некоторыми оговорками). Я уже подробно рассказывал об этой платформе, поэтому здесь опишу суть происходящего не слишком углубляясь.
Лицо состоит из нескольких элементов — брови и границ глаза (прямые линии), зрачка глаза (кривая) и контура лица (две кривых).
С прямыми всё просто — есть подпрограмма BIOS Draw_Line_d. А вот с кривыми — сложно.
Суть подхода в том, что мы вручную запускаем интегрирование — луч начинает перемещаться сверху вниз от текущего своего положения до заданного нами значения Y. В это время мы записываем в канал X разные значения и таким образом заставляем луч смещаться влево (если значения отрицательные) и вправо (если положительные). После записи очередного значения мы или сразу записываем следующее или некоторые время ничего не делаем, предоставляя лучу продолжить движение в прежнем направлении. Это «ничегониделанье» первоначально выглядело как куча NOP'ов после каждого изменения X, причём я приклеил к экрану кусок пергамента с рисунком и подбирал значения в VIDE эмуляторе.
В 1кб при таком подходе не уложишься, поэтому все длинные последовательности nop'ов я свернул в циклы. Причём, сами инструкции подготавливающие и реализующие цикл конечно тоже имеют некую длительность выполнения, что тоже пришлось учитывать, посчитав все такты (ну, примерно):
Вообще, первая мысль которая должна возникать при взгляде на этот код — что там куча повторяющихся кусков и все задержки можно свести в таблички и доставать их оттуда. Увы, любой лишний такт приведёт к продлению рисуемого вектора, поэтому простое решение здесь не сработает. Теоретически можно написать генератор кода — т.е. чтобы программа по данным из таблички воссоздавала нужные последовательности инструкций. К сожалению, у 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, особенно топившего за этот конкурс, весьма порадовали представленные работы — все они (включая дисквалифицированную) весьма достойны.
Первая моя работа называется 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, особенно топившего за этот конкурс, весьма порадовали представленные работы — все они (включая дисквалифицированную) весьма достойны.
2 комментария
Единственное, есть странный момент в правилах [СС2024]
Не совсем понятно, чем вам не угодили Фэнтази Консоли, если в 1k на GLSL шейдерах можно сделать всё тоже что и на ФК, только круче (shadertoy не даст соврать).
Получается что тут что-то лишнее: или GLSL, или приписка запрещающая ФК.
—
Конечно, СУТЬ разницы между ФК и шейдерами в том, что шейдеры не были выдуманы на ровном месте — есть железки. Но с формальной точки зрения граница проходит в стандартности поддержки платформой. Ну т.е. если бы винды или линукс имели поддержку инструкций TIC-80, ситуация бы изменилась.
Аналогично, про работы на javascript — если целевая платформа браузер, вопросов нет. Если платформа PC/Windows, браузер не является частью ОС (тут конечно можно долго дискутировать, являются ли все программы в поставке ОС её частью, но моё такое мнение).
[...]
С виртуальными консолями вообще прикол — раньше, когда люди писали демку и использовали движок (скажем, werkkzeug какой-нибудь), то им надо было включать этот движок в исполняемый файл. Т.е. размер считался суммарно. И теперь вдруг (следите за руками!) мы называем движок виртуальной платформой и его размер перестаёт считаться, больше не надо включать его в состав работы! :)
----