Речь у нас пойдёт о поэзии. Минимализм языка программирования Forth и красота образов демосцены подтолкнули программиста Бреда Нельсона к идее Forth Haiku. Подражая японским хайку, Бред писал свои первые программы из трёх строк, состояли они из пяти, семи, и снова пяти слов. Но в отличии от традиционного японского жанра, поэзия на языке Forth порождала картины не в воображении читателя, а зримо, на экране компьютера. Эта затея могла бы остаться причудой одинокого фаната компьютерного ретро (Forth прочно ассоциируется со старыми добрыми семидесятыми), если бы Бред не воплотил её на самой что ни на есть современной платформе (WebGL) и не сделал бы онлайн-редактор общедоступным.
Вот пример кода Forth Haiku и изображения, которое этот код создаёт: «Light Drop» by Brad Nelson.
: iii x y z*
Sin ; x 5 * x y
- iii exp y iii
Впереди нас ждут немало удивительных (в том числе и «живых») картин, но сперва — немного теории.
Основные понятия
Forth — стековый язык программирования. Чтобы совершить действия над несколькими числами, нужно сперва загрузить их в стек. Делается это простым перечислением чисел, разделённых пробелами. Затем указывают действие, которое нужно совершить над числами из стека. Команды также отделены пробелами и могут идти вперемешку с числами в любом нужном для вычисления порядке. Обычные арифмитические действия изымают с вершины стека операнды и помещают вместо них результат.
1 2 3 + /
Пример 1: загружаем в стек числа 1, 2 и 3, затем выполняем сложение двух верхних чисел (стек приобретает вид 1, 5) и далее делим одно число на другое. В итоге стек содержит ответ: число 0.2
1 2 + 3 /
Пример 2: иной порядок действий. Загружаем в стек числа 1 и 2, складываем. Результат — в стеке число 3. Загружаем в стек ещё одно число 3 и выполняем деление. Ответ: число 1 на вершине стека.
Шейдер — здесь речь пойдёт о пиксельных шейдерах, называемых иначе фрагментными. Это программа, исполняемая видеокартой для каждой точки текстуры, а в нашем случае — для каждой точки квадратного окошка размером 256x256. Таким образом, полотно, на котором мы рисуем свои картины, состоит из 65536-ти точек, и именно столько раз будет исполняться программа-шейдер. На выходе она должна выдать цвет точки. Казалось бы, если одну и ту же программу выполнить для всех точек, то и выглядеть эти точки должны одинаково — мы получим большой квадрат Малевича (чёрный, красный, любой другой). Всё так. Но к счастью, программа-шейдер может узнавать координаты x и y той точки, которую рисует в данный момент. А это всё меняет.
Система координат
По окончании работы наша программа-шейдер (Forth Haiku) должна оставить в стеке три числа: красную составляющую цвета, зелёную составляющую и синюю. Очень удобно, что яркость RGB-составляющих измеряется в том же масштабе, что и геометрические размеры окна. Просто положив в стек x или y (он ведь разный для каждой точки, да?), получим плавный переход цвета от чёрного к яркому через всё наше полотно.
Время
Кроме x и y, нам доступна ещё одна глобальная переменная t — время. Правда, в браузерах, лишённых поддержки WebGL, она всегда равна нулю. Отсчёт начат в незапамятные времена, к настоящему времени натикало уже немыслимое количество секунд, поэтому практическую пользу для нас представляет, пожалуй, только дробная часть переменной t и периодическая функция типа sin(t), так как она выдаёт значения в диапазоне от -1 до +1.
x
y
t
sin 1 + 2 /
Пример самой простой анимации. Кладём в стек координату x и оставляем без изменений — она будет отражать красную составляющую цвета. На второе место в стек кладём зелёную составляющую, это будет y (стало быть, зелень заколосится снизу вверх). В качестве синей составляющей кладём время t. А теперь выполняем над последним положенным числом ряд операций. Сперва берём от него синус. Затем 1 прибавляем и на 2 делим. Всё. Был у нас синус в диапазоне [-1,1], а стал [0,1] — как и положено составляющей цвета. И вот пошла приятная пульсация по синей компоненте, и вот засветились пуще прежнего точки, оттенённые красной и зелёной компонентой — красота!
Для сравнения, вот тот же самый шейдер на языке GLSL:
void main(void)
{
vec2 uv = gl_FragCoord.xy / iResolution.xy;
gl_FragColor = vec4(uv,0.5+0.5*sin(iGlobalTime),1.0);
}
Кому как, а мне код Forth кажется проще и понятней, он предлагает очень низкий порог вхождения в тему. Посмотрите, какую анимацию сделал (с нуля) мой 9-летний сын, просто понаблюдав пару дней за моими экспериментами с Forth Haiku. Мне кажется, это показатель.
Время, вперёд!
Что ж, с теорией покончено, переходим к практике. В галерее Forth Programming Salon полно станковой живописи красивых неподвижных изображений. Но нас больше интересует анимация.
Warning (техника безопасности): когда Вы начнёте ковырять чужие работы («Create a derived work») или создавать новые («Create a Haiku», «Haiku Editor»), рука Ваша потянется к кнопке «Submit». Ни в коем случае не нажимайте её, поскольку она делает не то, что Вы подумали! Посмотреть свою программу Вы можете, нажав другую кнопку — «Update». А кнопка «Submit» опубликует в общую галерею Вашу забагованную недописанную программу, да ещё и с именем пользователя «Анонимус». Такой хоккей нам не нужен, база и без того заспамлена неработающими обрывками программ и клонами (Бред Нельсон обещал почистить базу и улучшить интерфейс). Так что только "Update". Вдох-выдох, 15-минутный перерыв на гимнастику, свежим взглядом оцениваем свою программу, и только если на 146% уверены в ней — тогда даём ей название, вписываем автора и — «Submit».
Нажимайте на картинки. Оно там движется! (в браузерах Chrome, Firefox, IE11)
Мои программы, конечно, ужасно нечитаемы. Во-первых, только недавно (благодаря сынишке, который ковырял JavaScript этого Forth-транслятора) я узнал о существовании недокументированной возможности вписывать в программу комментарии (в круглых скобках, которые должны быть отделены пробелом от самого текста комментария). Во-вторых — и это главное — я пытался уменьшить размер программы. Есть такой вид спорта — оптимизация, очень популярен на демосцене. Смог в 256 байт? Молодец! Теперь давай уложи в 128. Поэтому первое, что я делаю, это переопределяю команды языка Forth.
: q dup * ;
Двоеточие — это такой оператор языка Forth (вообще-то, в среде Форт-программистов принято говорить «слово» вместо «оператор» и «словарь» вместо «набор команд и функций», но не станем усложнять). Оператор «двоеточие» сродни определению функции. Следом за двоеточием (через пробел, как обычно) идёт имя нового оператора, в данном примере это «q» (от слова «квадрат»). После имени через пробел записана небольшая программка, которая и составляет тело нашего новоиспечённого оператора «q». Последний оператор — «точка с запятой», конец определения (в действительности он вынимает из специального стека возвратов адрес возврата и переставляет туда счётчик команд, но опять же — не будем усложнять).
Часто используемые последовательности команд имеет смысл выносить в отдельные новые операторы, даже если в этом нет физического смысла. Скажем, пишу я рей-трейсинг с традиционным подходом к программированию. Вектора для меня — это неразрывно связанные наборы из трёх чисел, функция нормализации для меня — отдельная формула, имеющая конкретный физический смысл, и так далее. Но стоит мне чисто механически, не задумываясь над «глубоким внутреннем смыслом», начать оптимизировать программу на Forth, как испаряются умножения на константы, одни функции рвутся посередине и слипаются хвостами с другими функциями, а в итоге… В итоге я получаю совершенно иначе структурированную программу, имеющую свою особенную красоту. Не говоря уже о том, что она стала короче. Например:
x ' r / z * lx m
y ' r / z * ly m
Здесь x и y — глобальные константы, всё остальное определено программистом. Оператор «штрих» переносит нас в другую систему координат, оператор «r» вычисляет длину вектора, оператор z вычисляет нормаль, «lx» и «ly» выдают координаты источника света, «m» нормализует вектор (точнее, одну из его координат). Если нам угодно, умножение после «z» можем внести в тело самого оператора «z» (сэкономим пару байт). Да и деление перед «z» можно присовокупить к нему (или к предыдущему оператору «r»). При оптимизации все эти промежуточные действия уползают в словарь, а в теле программы остаётся только голая суть: x, y, t .
Это красиво. Но через неделю уже совершенно нечитаемо, конечно.
Хотите пример простенькой программы, в которой Вы сможете разобраться при помощи словаря Forth Haiku? Словарь этот намного беднее словаря классического Forth, и это сделано намеренно. Нажимайте на сам оператор и читайте о нём подробнее.
: r dup y 12 ** * t + sin swap x * cos + 1 mod ;
18 r
25 r dup
12 r /
В первой строке определяем оператор «r» (от слова «render»), который заглатывает число с вершины стека, тщательно пережёвывает, закусывая иксами и игреками, после чего выплёвывает в стек результат в диапазоне [0,1]. Оператору «r» три раза скармливаем разные числа, а на выходе получаем три компоненты RGB. В целях цветокоррекции дублируем зелёную компоненту и делим её на синюю. Теперь у нас новая синяя компонента, благодаря чему общая картинка получилась карамельной.
«1 mod» — это получение дробной части числа. Правда, работает оно не везде (Forth Haiku, на самом деле, рисуются и в случае отсутствия WebGL, на чистом JavaScript, но с рядом ограничений). Вместо «1 mod» лучше использовать «dup floor -». Но когда Вы гонитесь за размером меньше 128 байт, приходится жертвовать совместимостью в пользу краткости.
Для любителей хардкора — отрисовка спрайтов в условиях отсутствия бинарных операций и ещё немножко всякого:
Заключение
Forth Haiku очень прост и очень ограничен. При желании, выжать из него можно довольно много, но ни о какой конкуренции с GLSL речи не идёт. Не в том цель. Программа на Forth красива сама по себе. Ход мысли человека, программирующего на Forth, да ещё и программирующего пиксельные шейдеры — интересен сам по себе. Замечательный способ отвлечься и развлечься самому, прекрасное упражнение для детей. Я бы ввёл курс Forth Haiku в школе, честное слово. Хотя бы на одну четверть подтолкнуть детей think different — это здорово.
Ссылки
Проекты Бреда Нельсона, посвящённые языку Forth.
Haiku dump — «чёрный ход» в галерею работ.
Автор: Manwe_SandS