Флипбук-текстура: как анимировать спрайты в Unity, Godot и VRChat


Создайте свою флипбук-текстуру
Флипбук-текстура воспроизводит спрайт-лист кадр за кадром на одной поверхности. Узнайте, как создать её в Unity, Godot и VRChat, и перелистайте настоящий спрайт-лист в реальном времени в 3D.
Флипбук-текстура — это спрайт-лист, который шейдер воспроизводит кадр за кадром на одной поверхности. Здесь нет рига и нет таймлайна: лист хранит все кадры в фиксированной сетке, а материал перемещает небольшое окно по этой сетке с постоянной скоростью. Лист 8 x 8 содержит 64 кадра; при 12 кадрах в секунду цикл занимает около 5.3 секунды. Меш не меняется, текстура не загружается заново, а вся анимация обходится одним обращением к текстуре.
На этой странице описаны четыре числа, необходимые флипбук-шейдеру, и то, как индекс кадра отображается на UV-координаты; два способа управлять флипбук-текстурой в Unity; как настраивается флипбук Poiyomi на аватаре VRChat; аналогичный подход в Godot и Phaser; и как собрать собственный лист из GIF или из текстового описания.
Краткий ответ
Флипбук-текстура — это спрайт-лист, используемый как шейдерная анимация, индексируемый по строке и столбцу во времени. Лист занимает один слот текстуры; при каждом вычислении шейдера индекс кадра определяется по прошедшему времени и частоте кадров, затем этот индекс сводится к столбцу и строке, а UV-координаты поверхности пересчитываются в эту единственную ячейку. Frame = floor(time * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). Лист 6 x 6 при 10 FPS проходит 36 кадров за 3.6 секунды полностью на GPU, без загрузки кадров и без изменения draw call.
Как работает флипбук-текстура
Анимацию определяют четыре числа. Столбцы и строки описывают разметку листа. Количество кадров обычно равно произведению столбцов на строки, и по этому значению происходит зацикливание индекса. Частота кадров задаёт скорость продвижения индекса. Всё остальное — фильтрация, режим обёртки, UV-отступы — нужно, чтобы сэмплер не читал за пределами текущей ячейки.
Математика индекса короткая, и её стоит выписать. Для прошедшего времени t: frame = floor(t * fps) mod frameCount, column = frame mod columns, row = floor(frame / columns). Тогда UV-окно задаётся как tiling = (1 / columns, 1 / rows) и offset = (column / columns, row / rows). Именно вертикальное направление чаще всего вызывает путаницу. Большинство инструментов для создания листов записывают строку 0 вверху изображения, тогда как начало UV-координат в Unity и Godot находится внизу слева. Для листа, у которого первая строка сверху, в Unity offset.y становится 1 - (row + 1) / rows; иначе строки воспроизводятся в перевёрнутом порядке, и первый кадр анимации оказывается последней строкой листа.
Фильтрация определяет, будет ли результат чистым. Установите для листа режим обёртки Clamp. При Repeat выборка у правого края текстуры читает левый край, поэтому столбец пикселей из кадра 0 появляется внутри последнего кадра каждой строки. Отключите мипмапы для пиксель-арта с резкими краями: мип-цепочка усредняет соседние ячейки, и анимация превращается в кашу, когда поверхность находится в нескольких метрах. Если целевой движок или шейдер принудительно включает мипмапы, оставьте прозрачную кайму в один-два пикселя вокруг каждой ячейки или сместите UV-окно на половину текселя со всех четырёх сторон.
| Значение | Что оно контролирует |
|---|---|
| Столбцы | Кадры рядом по горизонтали. Задаёт горизонтальный тайлинг одной ячейки: 1 / columns. |
| Строки | Строки в листе. Задаёт вертикальный тайлинг: 1 / rows. Используйте 1 для полосы. |
| Количество кадров | Всего индексируемых кадров, обычно columns x rows. Здесь происходит зацикливание индекса. |
| FPS | Как быстро продвигается индекс. floor(time * fps) даёт текущий кадр. |
| Режим обёртки | Clamp. Repeat позволяет сэмплеру читать через край листа. |
| Режим фильтрации | Point, с отключёнными мипмапами, или смещение UV на половину текселя для каждой ячейки. |
Флипбук-текстура в Unity
Есть два пути, и они ломаются по-разному. Первый — шейдер, который сам хранит индекс строки и столбца и анимируется самостоятельно. Второй — окно Animation, где тайлинг и offset материала анимируются с помощью ключевых кадров, а обычный шейдер Lit или Unlit выполняет выборку. Первый путь лучше для эффектов, которые должны синхронизироваться с игровым временем; второй лучше, когда анимацию нужно управлять из состояния Animator и видеть её на таймлайне вместе со всем остальным в сцене.
В любом случае сначала настройте импорт спрайт-листа: Wrap Mode Clamp, Mip Maps off, Filter Mode Point и без сжатия для пиксель-арта. Эти три настройки вызывают больше ошибок флипбука, чем математика шейдера.
Путь 1: шейдер с индексом строки и столбца
В Shader Graph узел Flipbook принимает Columns, Rows и индекс кадра, управляемый временем, и выдаёт пересчитанные UV. В написанном вручную шейдере объявите _Columns, _Rows и _Fps как свойства и вычисляйте ячейку в вершинном или фрагментном шейдере. Если анимация должна синхронизироваться между объектами, управляйте индексом кадра из float в скрипте, а не сэмплируйте _Time.y независимо в каждом материале, чтобы два флипбука на одном объекте не расходились.
Путь 2: окно Animation управляет тайлингом и offset
Этот путь анимирует вектор _ST текстуры. Встроенные Standard и Unlit используют _MainTex_ST; URP Lit и Unlit используют _BaseMap_ST. Tiling остаётся фиксированным на (1, 1 / rows), а меняется только offset.y. Приведённые ниже шаги относятся к этому пути.
01
Выберите материал и откройте окно Animation. Создайте клип, затем добавьте свойство вектора _ST текстуры как дорожку — на материале оно отображается как Vector4.
02
Задайте частоту дискретизации клипа равной целевому FPS. Если частота дискретизации ниже нужного FPS, ключевые кадры схлопываются на один и тот же сэмпл, и анимация воспроизводится медленнее, чем задумано.
03
Задавайте offset.y один раз на кадр: value = 1 - (row + 1) / rows для листа с первой строкой сверху, tiling = (1, 1 / rows).
04
Установите интерполяцию каждого ключевого кадра в Constant в редакторе кривых. Linear смешивает две ячейки, и оба кадра видны одновременно как призрачное наложение.
05
Убедитесь, что последний ключ не повторяет значение первого кадра, иначе на стыке цикла анимация задерживается на один кадр.
06
Если несколько рендереров используют один материал, анимируйте MaterialPropertyBlock для каждого рендерера вместо чтения renderer.material: при первом обращении материал клонируется, и клон остаётся живым на протяжении всего времени существования объекта.
Флипбук Poiyomi для VRChat
Раздел Flipbook шейдера Poiyomi прокручивает UV-окно по листу прямо в материале, так что один слот материала может показывать анимированный спрайт на меше аватара. Он предоставляет Rows, Columns и частоту кадров, а также режим, в котором вы сами управляете индексом кадра, вместо того чтобы позволять ему работать от шейдерного времени.
Используйте полосу из одной строки, а не сетку. Полоса даёт шейдеру только одну ось для шага, что полностью убирает вертикальный шов. В случае сетки UV-окно должно в конце каждой строки переходить на строку вниз, и любая ошибка фильтрации, мип-уровня или округления на половину текселя на этой границе захватывает соседнюю строку — это видно как тонкая полоса другого кадра во время воспроизведения. Полоса также превращает индекс кадра в простое умножение: ширина кадра в UV равна 1 / frameCount, а offset — просто frame / frameCount. Для 8-кадровой анимации с ячейками 256 x 256 полоса будет 2048 x 256 и останется степенью двойки.
Альфа-канал важнее на аватаре, чем в плоской сцене. Если у спрайта резкие края, используйте Cutout или Opaque. Флипбуки в очереди Transparent не записывают глубину, и на аватаре они сортируются относительно тела и друг друга способами, которые меняются с углом камеры. По возможности оставляйте анимированную поверхность на материале, который уже есть на аватаре: каждый дополнительный слот материала ухудшает рейтинг производительности аватара. Если индекс кадра управляется из состояния Animator с синхронизированным параметром, каждый клиент видит один и тот же кадр; индекс кадра, вычисленный из шейдерного времени, рассчитывается локально и может находиться в другой точке цикла у разных зрителей.
01
Экспортируйте кадры одной горизонтальной полосой. Высота полосы равна высоте ячейки; ширина полосы равна количеству кадров, умноженному на ширину ячейки. Не оставляйте лишнюю ячейку в конце.
02
Импортируйте текстуру с Wrap Mode Clamp, Mip Maps off и включённым sRGB. Включайте Alpha Is Transparency только если полоса действительно содержит альфа-канал.
03
Создайте материал Poiyomi, назначьте его анимированному мешу и откройте раздел Flipbook.
04
Установите Rows в 1, а Columns — в количество кадров. Задайте частоту кадров под нужную длительность: 8 кадров при 8 FPS — это цикл длительностью одна секунда.
05
Выберите источник кадров. Ручной режим предоставляет индекс кадра, которым вы управляете из состояния Animator; режим, управляемый временем, зацикливается сам.
06
В разделе Rendering установите Cutout или Opaque для спрайтов с резкими краями. Оставляйте Transparent только для эффектов с мягкими краями и будьте готовы к особенностям сортировки по глубине.
07
Проверьте цикл: последний кадр не должен дублировать первый, иначе спрайт замирает на один кадр в каждом цикле.
08
Проверяйте на загруженном аватаре с зеркалом — вблизи и с другого конца инстанса. Проблемы с мипмапами и фильтрацией проявляются именно на расстоянии.
Флипбук в Godot и Phaser
В Godot 4 путь для 2D — это AnimatedSprite2D с ресурсом SpriteFrames. Импортируйте лист, откройте SpriteFrames, установите Hframes и Vframes по количеству столбцов и строк, добавьте кадры для анимации, затем задайте скорость анимации в кадрах в секунду и оставьте Loop включённым. SpriteFrames хранит анимацию как список индексов кадров, поэтому и сетка, и одиночная полоса работают без какой-либо UV-математики с вашей стороны.
Если нужна только одна анимация, обычный Sprite2D легче: задайте Hframes и Vframes один раз, затем продвигайте целочисленное свойство frame из AnimationPlayer с пошаговой интерполяцией. Не анимируйте frame с помощью tween с кривой сглаживания по умолчанию — это целочисленный индекс, и его интерполяция либо округляется до одного значения, либо мерцает между двумя ячейками.
Для флипбука на 3D-поверхности в Godot самый короткий путь — ShaderMaterial с той же математикой строк и столбцов, что и в версии для Unity: uv = UV * vec2(1.0 / cols, 1.0 / rows) + vec2(col / cols, row / rows), где строка вычисляется из индекса кадра. Начало UV в Godot — внизу слева, как и в Unity, поэтому листу с первой строкой сверху требуется такое же вертикальное отражение.
В Phaser 3 лист загружается один раз и нарезается во время загрузки. frameWidth и frameHeight задают ячейку, кадры нумеруются слева направо и сверху вниз, а анимация определяется номерами кадров, а не координатами — не нужно писать шейдер и нельзя ошибиться в арифметике строк и столбцов.
| Движок | Объект | Что вы задаёте |
|---|---|---|
| Godot 4 | AnimatedSprite2D + SpriteFrames | Hframes и Vframes под разметку листа, затем скорость анимации в FPS с включённым Loop. |
| Godot 4 | Sprite2D | Hframes и Vframes один раз, затем целочисленное свойство frame, продвигаемое AnimationPlayer с пошаговой интерполяцией. |
| Godot 4 | ShaderMaterial на 3D-меше | Columns, rows и индекс кадра в шейдере, с тем же пересчётом UV и вертикальным отражением, как в Unity. |
| Phaser 3 | this.load.spritesheet | frameWidth и frameHeight; Phaser нарезает лист слева направо, сверху вниз. |
| Phaser 3 | this.anims.create | Кадры из anims.generateFrameNumbers, а также frameRate и repeat: -1 для зацикливания. |
Перелистайте спрайт-лист в реальном времени в 3D
Игровая сцена в этом разделе — The Cyclops' Island, работающая в 3D-конструкторе игр Neta Studio. Флипбук на экране — та же операция преобразования индекса в UV, описанная выше, выполняемая в шейдере Three.js на GPU; разница в том, что она работает на настоящем меше в настоящей сцене, с камерой, которой вы управляете, и движущимся светом.
Это важнее, чем кажется. Флипбук в плоской сцене Unity обычно рассматривают прямо, и там швы и смешивание мипмапов трудно заметить. Обойдите камерой анимированную поверхность под скользящим углом — и проблемы проявляются сразу: полоса соседней ячейки на краю кадра, последний мип-уровень усредняет два кадра в размытие или альфа-край, который читается правильно спереди и исчезает сбоку. Браузерная сборка сокращает цикл между созданием листа и его просмотром на поверхности настолько, что можно итерировать без шага сборки.
Mini World — сцена поменьше, чтобы протестировать отдельный лист: меньше мешей и быстрее загрузка. Agent Sprite Forge генерирует кадры, если у вас ещё нет листа, и обе сцены используют один и тот же путь флипбука, поэтому лист, который выглядит правильно в Mini World, ведёт себя так же в The Cyclops' Island.
Создайте собственный лист
Есть три пути к пригодному листу: конвертировать существующий GIF, обрезать сетку до одной полосы или сгенерировать кадры из текстового описания. Все три приводят к одному файлу — одному PNG, где все ячейки одного размера, расположены слева направо и сверху вниз, а ширина листа кратна ширине ячейки.
Конвертация GIF — путь с наибольшим объёмом скрытой работы, потому что GIF не является последовательностью полных кадров. Кадры часто представляют собой частичные различия с предыдущим кадром, а задержки хранятся для каждого кадра в сотых долях секунды. Флипбук-шейдер принимает только одну частоту кадров, поэтому переменный тайминг перед экспортом приходится выравнивать дублированием кадров.
Обрезка сетки до полосы механична: оставьте нужную строку, обрежьте высоту до одной ячейки, а ширину оставьте равной количеству кадров, умноженному на ширину ячейки. Это правильный ход, когда лист-сетка используется как одна анимация, потому что так убирается вертикальная ось и связанный с ней шов. Генерация из описания — третий путь: Agent Sprite Forge принимает действие и количество кадров и возвращает лист с фиксированным размером ячейки, что избавляет от ручной подгонки кадров, экспортированных по одному.
01
Разверните каждый кадр GIF перед экспортом. Декодируйте каждый кадр на канвас, который переносит предыдущий кадр вперёд, иначе вы экспортируете прямоугольники различий вместо изображения.
02
Нормализуйте тайминг. Выберите самую короткую или медианную задержку GIF, затем продублируйте кадры там, где источник держал их дольше, чтобы лист воспроизводился с одной постоянной частотой FPS.
03
Расположите кадры слева направо и сверху вниз, все ячейки одинакового размера. Сделайте размеры листа точными кратными размеру ячейки: лист шириной 9.5 ячейки будет вечно выбирать не те тексели.
04
Чтобы обрезать до полосы, обрежьте высоту до одной ячейки, а ширину оставьте равной количеству кадров, умноженному на ширину ячейки. Удалите пустую хвостовую ячейку, если она была в исходной сетке.
05
Добавьте прозрачную кайму в один-два пикселя вокруг каждой ячейки, если целевой движок не позволяет отключить мипмапы. Иначе сместите UV-окно на половину текселя со всех четырёх сторон.
06
Чтобы не конвертировать, а генерировать, опишите действие, количество кадров и направление взгляда и позвольте Agent Sprite Forge вернуть лист с фиксированными ячейками, вместо того чтобы экспортировать кадры по одному и выравнивать их вручную.
07
Проверяйте, пошагово переключая индекс кадра по одному в движке. Если у любой ячейки на краю видна полоска соседней, проблема в разметке листа или UV-отступе, а не в шейдере.
Войдите в мир, которому она принадлежит
Спрайт — это персонаж, которому негде стоять. Последний шаг — тот, которого больше никто не предлагает: возьмите только что созданный лист и поместите его в мир, по которому можно ходить, — небольшой остров, планету, дождливый угол улицы. Каждый мир на этом сайте был создан из одного текстового описания и опубликован как страница, в которую можно играть.
FAQ
Что такое флипбук Poiyomi? +
Это раздел Flipbook шейдера Poiyomi, который прокручивает UV-окно по спрайт-листу в материале, так что один слот материала может показывать анимированный спрайт на меше аватара. Вы задаёте Rows, Columns и частоту кадров и либо позволяете индексу кадра работать от шейдерного времени, либо управляете им из состояния Animator. Предпочтительна полоса из одной строки, потому что она убирает вертикальный шов между строками.
Можно ли сделать флипбук из GIF напрямую? +
Нет. GIF хранит задержки для каждого кадра и часто только изменившиеся пиксели, тогда как флипбук-шейдер ожидает одну текстуру и одну постоянную частоту кадров. Сначала конвертируйте его: разверните каждый кадр на предыдущий, нормализуйте тайминг и запишите результат в сетку с фиксированными ячейками. Шейдер никогда не видит GIF.
Почему моя флипбук-текстура смешивается между кадрами? +
Сэмплер читает за пределами окна текущей ячейки. Установите для текстуры режим обёртки Clamp, отключите мипмапы для графики с резкими краями и сместите UV-окно на половину текселя с каждой стороны. Смешивание усиливается с расстоянием: именно там мип-цепочка начинает усреднять соседние ячейки.
Какую частоту кадров использовать для флипбука? +
От 8 до 12 FPS воспринимается как ручная анимация и подходит для спрайтов персонажей; от 24 до 30 подходит для огня, воды и других непрерывных эффектов. У шейдера ровно одна частота, поэтому если у источника был переменный тайминг, дублируйте кадры для соответствия, вместо того чтобы пытаться менять скорость в середине цикла.
Нужна ли мне сетка или достаточно одной строки? +
Одной строки достаточно для всего, что воспроизводится как последовательность, и она убирает одну ось ошибок швов. Сетки нужны для листов, которые упаковывают несколько анимаций в одну текстуру, где шейдер или движок выбирает строку для idle, run или attack. Если из листа-сетки получается только одна анимация, обрежьте его до полосы.
Работает ли флипбук с шейдером Lit из Unity URP? +
Не из коробки: у URP Lit нет индекса кадра. Используйте Shader Graph с узлом Flipbook, который принимает Columns, Rows и управляемый временем индекс, или анимируйте tiling и offset материала _BaseMap_ST с постоянной интерполяцией ключевых кадров. Встроенные Standard и Unlit используют _MainTex_ST по тем же правилам.
Назад к 3D-конструктору игр
Все 31 готовых 3D-мира, игры и сцены собраны на одной странице — в каждую можно играть.
Открыть библиотеку спрайтовых ресурсов
Каждый ресурс в этой библиотеке — прозрачный, готовый для игры PNG с записанным промптом: ресурсы спрайт-листов и бесплатные спрайт-листы, отмеченные тегами по действию, стилю и теме. Бесплатно для просмотра и скачивания.