EN
creator cover aleha_84
aleha_84
Creating cozy pixelart seamless scenes
aleha_84
23
subscriber
goals
3 of $ 502 money raised
for good deeds \ на хорошие дела
Available to everyone
Jul 30 00:26

Статья №1 Подходы к формированию кадров в процедурной графике.

Я много раз уже рассказывал, что для своих сцен я создал собственный движок для отрисовки и анимирования сцен. Там нет никакой магии, это обычные html5 canvas технологии. При этом я не имею художественного образования и анимации тоже нигде не учился. Поэтому все дальнейшие статьи будут опираться только на мой личный опыт, практическую реализацию и в контексте созданного программного обеспечения. 
Обычно у такого JS приложения, есть 1 <canvas> элемент на странице, которым мы пользуемся для рисования всего что нам нужно. Канвасов может быть и больше, их можно располагать слоями друг над другом, для рендеринг не зависимых друг от друга элементов. Рисование на канвасе происходит через его контекст.
Сейчас я хотел бы рассмотреть способы формирования кадров.
На лету. 
Этот подход я использовал в самом начале своего пути. Заключается он в том, чтобы обновлять изображение в режиме реального времени, по мере изменения модели данных, которая за него отвечает. Если говорить о выполнении этого кода в контексте простого однопоточного JS приложения, то появляется очень существенное бутылочное горлышко в виде крайне малого количества возможного времени на перерисовку изображения. При анимации частотой 60 кадров в секунду на формирование одного кадра у нас есть только 16 миллисекунд. Как думаете, много можно успеть сделать?
Выделенное время не принадлежит нам полностью. Процесс состоит из трёх частей:
1. Стереть текущий кадр (выбранную область)
2. Формирование нового кадра.
3. Нарисовать новый кадр.
Мы можем контролировать только пункт 2. Первый пункт можно только оптимизировать, а последний полностью зависит от движка JS. Хотя 1 и 3 пункты не значительны по времени, но их нужно учитывать. Так же я не говорю тут о возможности подключения многопоточности через webworker-ы, возможно я расскажу о них когда-то отдельно.
Пункт 2 и 3 могут быть объединены, если мы используем контекст на прямую и рисуем сразу в целевом канвасе. Я обычно стараюсь сначала нарисовать всё на созданном на лету в памяти канвасе и только уже по завершении всех операций рендерить его в целевую картину. Так или иначе производительность всегда упирается в количество вызовов ctx.fillRect(x,y,w,h), чем их больше, тем больше вероятность не успеть в выделенное нам время.
В каких ситуациях может пригодиться рендеринг на лету?
Не предсказуемое изменение состояние объекта. Т.е. те ситуации в которых необходимо обновлять изображение по не предвиденному заранее алгоритму с внесением поправок или переменных. Самое простое, это изменение текстуры объекта в зависимости от его внутреннего состояния или внешнего воздействия, но переключение с одной картинки на другу, это не тяжелая операция. А вот создание эффектов, реагирующих на пользователя, уже может быть интереснее. Рассыпающиеся искры по поверхности рядом стоящих объектов с учетом поиска коллизий или красивая неповторяющаяся вспышка в месте клика пользователем. Хотя всегда можно пойти на трюки оптимизации и решить всё другим путём.
Что будет если не уложиться в отведенные время пункта 2? Анимация начнёт лагать и замедляться. Т.е. рендерить и просчитывать траектории десятков тысяч частиц не очень хорошая идея, без должной оптимизации.
Формирование кадров заранее.
Этот подход я использую повсеместно при создании моих сцен сейчас. Суть проста, рендеринг последовательности кадров происходит в памяти при старте приложения или сцены. Здесь теперь нет ограничения на использования времени на формирование одного кадра. Формируется массив кадров нужной длины и в цикле происходит просчет поведения модели и формирование изображений. 
Очевидным неудобством этого бывает то, что при большой сложности и объеме формирование последовательности кадров займет прилично времени и подвесит приложение. В сложных ситуациях у меня бывает это может занимать до нескольких секунд. Подобный подход нельзя использовать в приложениях где есть пользователь, так его можно потерять. Но если рендеринг происходит для последующей выгрузки в файл, то нет проблем.
Реальные ситуации могут включать в себя оба способа формирования кадров, даже объединяющие их, когда сложность алгоритма позволяет вместить задуманное. Например можно по событию формировать кодом небольшую последовательность кадров с учетом контекста. Хотя чаще всего заканчивается всё жуткими лагами и тормозами и в ход идет оптимизация, чтобы где-то выжать доли секунды, где то что-то закешировать и так далее.
Хватит скучных букв, я создал небольшой пример.
Два квадрата как только появляются на холстве сразу начинают пускать в себя частицы по кривым траекториям.
Каждый из квадратов плавно моргает - это последовательность из 20 кадров сменяемая каждые 50 миллисекунд. Кадры были сформированы при старте приложения и используются объектом в момент своего появления.
Летящие друг в друга частицы являются объектами формирующими свои кадры на лету. Не лучший конечно по оптимальности использования пример. В момент создания частица знает только начальную и конечную точку, маршрут строится по уравнению кривой. Каждые следующий цикл вычисляется (берется из массива) очередная точка маршрута и в этом месте на канвасе размером со всю сцену рисуется точка и её хвост. Далее этот канвас рендерится в общем пространстве сцены.
На этом всё, я постарался в этой статье изложить своё видение подхода к формированию кадров в процедурной графике. Спасибо за внимание. 
Log in, to post comments

Subscription levels

Поддержка \ Support

$ 1.68 per month
Go up