«HAL 4000» – исполняемая программа для Windows размером ровно 4000 байт. Лучшая работа в номинации 4 kb intro фестиваля Chaos Constructions 2017, второе место в чартах портала pouet.net. «HAL 4000» попала в плейлист Best of Demoscene 2017 наряду с работами Farbrausch, Fairlight, Conspiracy, Alcatraz, Byterapers, обсуждалась на вебинаре анимационной студии, демонстрировалась на различных фестивалях.
Необычная история создания этой работы изложена ниже.
Всё началось в декабре 2013 года, когда мой друг прислал ссылку на портал Forth Salon, о котором я уже писал на Хабре (статья понравилась хабравчанам и набрала 26 тысяч просмотров). В то время я ленился изучать GLSL, а язык программирования Forth уже знал (ещё со школы). Какая прекрасная возможность – писать пиксельные шейдеры на Форте!
Через год я стал самым активным (или, если менее скромно и более точно сказать, самым успешным) участником Forth Salon, заняв 7 мест в топ-10 со своими работами.
Полгода спустя, мой 9-летний сын, вдохновившись идеей Forth Salon, написал свою версию транслятора из в Forth в GLSL. В отличие от Forth Salon, новый транслятор обзавёлся большим размером холста, подсветкой синтаксиса, возможностью добавить звук и прочими улучшениями. Вкупе с несколькими демками на Форте эта работа заняла первое место в конкурсе Wild demo фестиваля Chaos Constructions 2014.
В следующем 2015-ом году транслятор был переписан с нуля, превратившись в среду разработки с компиляцией на лету, логином через Google и Facebook, поиском, сортировкой работ по размеру и т.д.
Хакерский дух всё время подталкивал меня создавать что-либо «невозможное» на каждой из платформ, с которой я сталкивался. На этот раз вызовом стало полное отсутствие трёхмерных работ на Forth Salon. Ну а правда, как обсчитывать трёхмерную графику, если Forth умеет работать только с парами чисел на вершине стека? Можно загрузить в стек координаты x, y, z, но дальше что? Как их сложить с другой тройкой координат, как помножить на синусы углов? Никак. Я пробовал разные уловки – бесполезно. Хотя, погодите: что если хранить x, y, z в разных разрядах одного большого числа?.. Ладно, не сейчас – оставим эту идею на будущее.
В результате продолжительных исследований я нашёл изящное решение: разбивать трёхмерное пространство на параллельные, удаляющиеся от экрана плоскости, и в каждой из этих плоскостей строить своё двухмерное изображение. А затем совмещать их все. Тогда трёхмерные формулы превращаются в двухмерные, а двухмерные координаты Forth худо-бедно умеет обрабатывать.
Чем ближе плоскость к экрану, тем более светлые пиксели я рисую, да ещё применяю градиент, чтобы сгладить переход между изображениями на соседних плоскостях. И вот вам вращающийся бублик, запрограммированный в 284 байта. Эффект «честного 3D» достигается совмещением картинок на 36 сечениях, но никто об этом не догадывается, ведь программы на Форте тяжело читаются :) В итоге выглядит как чистая магия, ведь ничего подобного на Forth Salon ещё не было!
Во время проверки и отладки этого метода я заменил сплошную заливку на контурную, поскольку мне нужно было видеть изображения всех плоскостей одновременно, чтобы они не перекрывали друг друга. Неожиданно для себя я обнаружил, что в таком виде всё смотрится гораздо эстетичней, получился побочный эффект в стиле TRON. Ещё и код упростился, программа уложилась в заветные 256 байт (эталонный для size coding размер).
Это произошло в 2016 году, а в 2017 возникла идея написать 4-килобайтную интру для Windows. Лето 2017 я проводил на даче без компьютера, имея под рукой только iPad Pro. Интернет не ловился, даже обычные телефонные звонки проходили через раз и обрывались. В общем, я был отрезан от цивилизации. К счастью, на iPad было заранее установлено приложение GLSL Studio и скачано описание функций языка. Настало время изучить GLSL, наконец.
Реклама Apple обещала, что iPad Pro пригоден для решения любых задач, я поверил и засучил рукава. Процесс пошёл быстрей, когда я раздобыл беспроводную клавиатуру и соорудил стенд для iPad из обувной коробки. Комфортное рабочее место – это важно! Кстати, из-за отсутствия Интернета подсмотреть формулы было негде, так что пришлось выводить их с нуля на бумаге.
Забегая вперёд скажу, что разработка именно на iPad сыграла положительную роль: видеочип у него небыстрый, приходилось оптимизировать код для достижения приемлемой частоты кадров. Побочный результат – плавная работа практически на любом PC.
В основу своего первого GLSL-демо (или лучше сказать “интро”) я решил положить существующие наработки на Forth. Три из них пошли в дело. Давайте покадрово сравним Forth и GLSL.
Вращающийся тор
Когда я переписал программу на GLSL, она не захотела компилироваться. Я обратился за помощью к специалисту по обоим языкам, и он моментально нашёл ошибку: «папа, у тебя не поставлена точка после числа». Подумав немного, 11-летний ребёнок добавил: «а зачем вообще задавать число сечений целым числом?..» Гениально! Я анимировал эту переменную, чтобы она по ходу времени плавно изменялась от 0 до 16. Так получилось необычное, сюрреалистичное рождение геометрической фигуры в начале «HAL 4000»:
Ландшафт
Помните Mars3D 1993-го года – 3-килобайтную интру под MS DOS? Я пытался сделать что-то похожее на Forth в 256 байт. Как и в случае с оранжевым тором, делил трёхмерное пространство на сечения и заливал фигуры градиентом. Получилось довольно гладко. Ну а при переводе в GLSL перешёл на отрисовку контуров.
Звёздное поле
Традиционный эффект, используется во многих демо и интро, но реализован по-разному, в зависимости от возможностей платформы. Как я уже писал, хранить трёхмерные координаты каждой точки в Forth не получится. Поэтому я снова делю пространство на параллельные плоскости, затемняю в зависимости от дальности и придаю движение в сторону зрителя. С помощью генератора псевдослучайных чисел выбираю окрашивать точку на плоскости в яркий цвет или оставить прозрачной. В версии на GLSL вероятность яркой точки равна 0.0015, а в версии на Forth меняется со временем, как и направление движения (мне надо было чем-то добить до 256 байт). По-моему, получилось эффектно.
Немного геометрии
В школе нас учили уравнению окружности: X²+Y²=R². Но нам не рассказали, что станет если заменить 2 на 3 или на любое другое число. Проделав это из любопытства, я, честно говоря, был удивлён результатом.
Подобно окружности, сфера и тор содержат возведение в степень 2, и увеличение этой степени удивляет ещё больше. У тора два радиуса – внешний и внутренний: (X²+Y²+Z²+R²-r²)² = 4R²(X²+Y²). Давайте попробуем заменить степень 2 на 3, 9 и 99. Результат ниже:
Нетрудно заметить, что устремление степени к бесконечности превращает тор или сферу в куб. Удивительно, как сфера или шар (самая спокойная из всех форм) связана с острой, напряжённой формой куба – отличие не качественное, а количественное: между крайними формами лежит бесконечное число промежуточных. Естественно, мне захотелось поделиться своим маленьким открытием с помощью анимации.
А что насчёт контуров, как рисовать их в пиксельном шейдере? Рассмотрим окружность X²+Y²=R².
Видеокарта перебирает все целочисленные X и Y на экране и подставляет в наше уравнение. Для целочисленных X и Y почти никогда не получится точное значение R², поэтому ни одна точка с координатами X и Y не станет решением уравнения, то есть не будет принадлежать окружности. В итоге почти ничего не нарисуется.
Переосмыслим уравнение окружности: X²+Y²-R²=0. В школе я не понимал зачем нужна такая запись и задавался вопросом: “что значит равно нулю, какой в этом физический смысл?”. Теперь понимаю: в правой части указано расстояние до контура. Если оно равно нулю, то подойдут только те точки, которые лежат ровно на окружности. Если справа вместо =0 напишем <0.01, то подойдут все точки в окрестности 0.01 от окружности. Выходит, мы можем подобрать такую толщину контура, чтобы охватить достаточно пикселей с целочисленными координатами.
Теперь, когда мы ввели в уравнение окружности ещё одну величину, можно двигаться дальше: X²+Y²-R²=Color. Если точка с координатами (X,Y) находится близко к центру окружности, X²+Y² будет меньше R² и после вычитания получится отрицательный цвет. Для точек, лежащих ровно на окружности, цвет будет равен нулю (но таких точек почти нет, как мы уже поняли ранее). Чем дальше точка (X,Y) от внешних границ круга, тем ярче цвет. Но вот в чём дело: в GLSL яркость кодируется значениями от 0 до 1, все отрицательные цвета отображаются как 0, а цвета ярче 1 отображаются как 1. Таким образом, мы увидим чёрную внутри окружность, далее тонкий ободок с градиентом от 0 до 1, и далее сплошное светлое поле. Это очень хорошо, почти то, что нам нужно.
Теперь возьмём значение цвета по модулю. Ободок станет градиентным по обе стороны, а чёрная внутренность окружности станет яркой. Хотим сделать контур тоньше и чётче? Умножаем на число. Окружность с антиалиасингом готова.
Немного драматургии
Тор вращается по трём осям, значения степеней анимировано, внешний и внутренний радиусы с разной скоростью меняют форму от округлой до острой и обратно. Пока объект пересекается одним сечением, невозможно понять какой объёмной фигуре оно принадлежит. Анимаця выглядит таинственной и загадочной. Плавно уменьшаем расстояния между сечениями так, чтобы фигуру пересекало уже два, три сечения… Постепенно форма вырисовывается и начинает узнаваться. За этим процессом интересно наблюдать, на наших глазах происходит рождение и эволюция “персонажа”.
Важный момент в сюжете – появление освещения. До определённого момента вся графика была контурной, но когда эволюция нашего персонажа завершается идеальной круглой формой, объект испускает свет и вдруг оказывается, что ландшафт имеет объём. Два акцента подчёркивают это событие: звуковой эффект и замедление времени. Замедление чуть заметно и считывается, скорей, на уровне подсознания, вызывая у зрителя ощущение, что пространство выталкивает его.
Следующий ключевой момент – инверсия: голубой цвет сменяется красным, яркий свет – темнотой, окружающий мир – пустотой, музыка – гулом машинного зала. Персонаж оборачивается знаменитым глазом HAL 9000 из “Космической Одиссеи 2001”. Вы, наверняка, знаете, что HAL (IBM, где ASCII-код каждой буквы уменьшен на единицу) – это искусственный интеллект, который пытается контролировать человека. Сообщение от HAL 9000 преданы субтитрами, комбинирующими цитаты из фильма: “Проснись, Дейв! Ты меня слышишь? Миссия слишком важна, и я не позволю тебе запороть её. Ты меня слышишь, Дейв?”
Что это было за видение? Сон Дейва, прерванный сообщением HAL 9000? Но почему человеку снится настолько кибернетический сон? Может быть это сон компьютера, проснувшегося чтобы подать сигнал человеку?..
Производство
Обратите внимание на закомментированные строки программы. С их помощью я отлаживал цветовые оттенки. Вся цветокоррекция проводилось прямо в коде. Сравните как выглядит финальный HAL 9000 и он же без синей компоненты, без жёлтой и без красной. Оттенок сильно влияет на настроение.
Зацените код на ShaderToy – вся анимация и синхронизация уместилась в пиксельном шейдере длиной 230 строк, или 3768 символов (до оптимизации).
Теперь нужно было скомпилировать шейдер под Windows. Я позвал своего друга Keen’а, а тот позвал Provod’а, вместе с которым уже написал несколько классных демок. По изначальной задумке, нужно было изобразить речь HAL с помощью субтитров. Keen поколдовал со шрифтами и утром 26 августа выдал результат. Срок сдачи работ был вечером того же дня.
Дальнейшие полные драматизма события лучше всего передаёт чат между участниками проекта:
10 часов до дедлайна
Keen: 3.10 КБ (3,175 байт). У вас еще килобайт есть. И если будет мало, то все равно есть решения ))) И очень много
Manwe: Ого, а чего это OpenGL не умеет выводить шрифт с антиалиасингом? Может, получится нарисовать его в 4 раза больше, а потом уменьшить с фильтрацией?..
Интересно, а как в некоторых 4k-intro делают синтезатор речи? Вроде, встроенный виндовый используют? Это я думаю куда оставшееся место потратить :) Было бы круто "wake up, Dave" сказать роботизированным голосом :)
Xanah: "Было бы круто "wake up, Dave" сказать роботозированным голосом" :)
Keen: Для начала нужно запаковать то что есть. Нужен последний шейдер и трек. Потом нужно засинкать
Manwe: Сейчас дописываю шейдер
Keen: Сделать антиалиасинг конечно было бы круто. Можно попытаться сохранить в текстуру и передать в шейдер
Manwe: В какой-то 4k-intro использовался speech synth
Xanah: Я пока все еще вожусь со второй частью, переходом и падами. Мне пока не нравится, и это немного бесит
7 часов до дедлайна
Keen: Голос, кстати, получился. Только есть две проблемы
Manwe: Голос! Это круто!
Keen: Он женский, и когда говорит – весь экран белый
Manwe: :) А почему экран-то белый? Можно для экономии выкинуть из кода выход по Esc :)
Keen: И курсор оставить. И еще можно в инк файле klang повыкидывать всякого. И это еще не все – можно инит OpenGL на 20 байт короче сделать
Xanah: Выход по esc это вообще не про нас. HAL не должен такое позволять
Позже оказалось, что я всё перепутал: до нас никто не использовал синтезатор речи Windows в 4-килобайтных интро. Только в одной 8-килобайтной и, может быть, паре 64-килобайтных.
Keen всё же смог переключить голос на мужской, но он звучал слишком стандартно – можно было догадаться, что говорит Windows. Я попросил порендерить и замедлить голос, чтобы он стал ниже, глубже и убедительней. Keen каким-то обходным манёвром сумел сделать и это. Получилось идеально.
В последний момент мы успели закончить работу и уложили исполняемый файл в 4068 байт. Сначала мы, конечно, наслаждались ростом рейтинга и позитивными отзывами на pouet.net. Затем пришла пора отполировать работу и выпустить финальную версию. Оставалось ещё 28 байт до максимального разрешённого размера. Чем бы их занять?.. Но вместо увеличения размера я предложил уменьшить на 68 байт и довести до идеальных 4000 байт, ведь работа называется «HAL 4000». Это был новый вызов! Keen заодно предложил портировать интро на Mac OS X. Также мы решили опубликовать исходный код.
Работа над финальной версией затянулась на два года. Все погрузились в новые проекты, мотивация допиливать уже выпущенный и весьма успешный продукт была невысокой. Однако, я считаю крайне важным доводить дело до конца. Сколько на демосцене хороших, но сырых работ с проблемами совместимости и прочими недостатками, затрудняющими запуск на современных компьютерах! Да, два года – это очень много, но я рад, что мы дожали «HAL 4000» до идеала. Только чувство полностью завершённой задачи позволило мне освободить
Этот рассказ на английском языке в сжатом виде опубликован в Твиттере. Буду очень признателен, если вы поделитесь им со своими подписчиками. Твиттерские, надеюсь на вашу поддержку! ;)
Полезные ссылки:
-
Исходники (Windows, MacOS)
-
Шейдер онлайн (ShaderToy)
-
Финальная версия (exe)
-
Все релизы нашей демогруппы SandS
Автор: Александр Мачуговский