Sprite Fusion Sprite Normals — генератор карт нормалей для пиксель-арт спрайтов

· 6 мин чтения
gamedev pixel-art rust cli graphics
📂 Исходный код на GitHub

Инструмент на Rust для генерации карт нормалей из пиксель-арт спрайтов, листов кадров и анимаций GIF/APNG. Поставляется как CLI и как модуль WebAssembly, работает локально. Часть проекта Sprite Fusion, лицензия MIT.

Sprite Fusion Sprite Normals — генератор карт нормалей для пиксель-арт спрайтов

Sprite Fusion Sprite Normals — инструмент, который из обычного пиксель-арт спрайта делает карту нормалей (normal map). Написан на Rust и поставляется в двух видах: как CLI для консоли и как библиотека WebAssembly для браузера. Он умеет брать одиночные спрайты, листы кадров и анимации GIF/APNG, считать из них рельеф поверхности и сохранять результат локально, на вашей машине. Онлайн-версия доступна на https://www.spritefusion.com/sprite-normals. У проекта есть и API, и подключение через MCP.

Зачем спрайту карта нормалей

Карта нормалей — это отдельная картинка, в которой для каждого пикселя записано, куда «смотрит» поверхность: вверх, вниз, влево, вправо. Движок читает эти направления и считает, как на пиксель падает свет. Обычный 2D-спрайт начинает реагировать на движущийся источник света: одна и та же картинка выглядит иначе, если свет слева или справа. Такие 2D-игры с динамическим освещением часто называют 2.5D.

У инструмента три главных свойства:

  • рельеф считается из силуэта спрайта и из его собственной штриховки, рисовать карту руками не нужно;
  • исходное пиксельное разрешение и прозрачность сохраняются — картинка не размывается и не увеличивается;
  • спрайты, листы кадров и анимации обрабатываются локально, без отправки файлов на сервер.

Проект подходит для трёх задач. Первое — 2D-игры, в которых есть движущиеся источники света и спрайты с картами нормалей. Второе — анимации в пиксель-арте, которым нужны кадры карт нормалей, точно совпадающие с кадрами исходной анимации. Третье — конвейеры подготовки ассетов, где обработка должна идти либо через нативный CLI, либо прямо в браузере.

Для конвейера CLI удобен тем, что его легко встроить в скрипт сборки: положили спрайты в одну папку командой с --output-dir, получили рядом такую же папку с картами нормалей. Ручной проход по файлам не нужен.

CLI

Сборка из исходников

Для сборки нужен Rust версии 1.85 или новее. Из каталога проекта:

cargo build --release --locked

Флаг --locked заставляет cargo брать версии зависимостей ровно из файла Cargo.lock. Готовый бинарник окажется в target/release/spritefusion-sprite-normals. Чтобы поставить его локально:

cargo install --path . --locked

Использование

spritefusion-sprite-normals <INPUT>... [OPTIONS]

Аргумент <INPUT> — это один или несколько входных файлов: PNG-спрайты или листы кадров, а также GIF и APNG. Доступные опции:

Опция Что делает Значение по умолчанию
-o <OUTPUT> Куда сохранить карту нормалей для одного входа —
--output-dir <DIR> Каталог для пакетной обработки —
--normal-strength <VALUE> Общая сила рельефа 1.5
--detail-strength <VALUE> Рельеф из нарисованной штриховки 6
--detail-scale <PIXELS> Сглаживание штриховки 0.6
--shape-strength <VALUE> Объём из силуэта 1.15
--frame-size <WIDTHxHEIGHT> Разрезать равномерный PNG-лист на ячейки —
--normal-format directx Перевернуть зелёный канал; по умолчанию OpenGL OpenGL
--force Перезаписывать существующие результаты; входные файлы защищены выкл.

Опции делятся на две группы. --shape-strength отвечает за общий объём, который инструмент выводит из силуэта. --detail-strength и --detail-scale отвечают за мелкий рельеф, который он выводит из нарисованной штриховки. --normal-strength задаёт общую силу карты: логично подбирать его последним, когда две другие группы уже настроены.

Примеры:

spritefusion-sprite-normals sprite.png -o sprite_n.png
spritefusion-sprite-normals sprites/*.png --output-dir normals
spritefusion-sprite-normals sprite.png -o sprite_n.png --normal-strength 2
spritefusion-sprite-normals sheet.png --frame-size 32x32 -o sheet_n.png
spritefusion-sprite-normals walk.gif -o walk_n.png

Если не указывать путь вывода, файлы сохраняются рядом с исходниками с именем вида <name>_n.png. Полный список опций даёт команда spritefusion-sprite-normals --help. Опция --force перезаписывает уже готовые результаты, но входные файлы защищены: инструмент никогда не пишет поверх оригиналов.

Формат по умолчанию — OpenGL. Если ваш движок ждёт карту нормалей в формате DirectX, зелёный канал надо перевернуть — это делает опция --normal-format directx.

Анимации

GIF, APNG и PNG-листы с опцией --frame-size дают на выходе три файла. На примере анимации walk:

  • walk_n.png — атлас карт нормалей без потерь;
  • walk_n.color.png — цветной атлас с той же раскладкой кадров;
  • walk_n.frames.json — размеры атласа, размеры кадра, число колонок, число кадров и тайминг.

Кадры уложены построчно, слева направо и сверху вниз. В поле delays_ms лежат пары [числитель, знаменатель] в миллисекундах. PNG-листы сохраняют свою раскладку и получают задержку кадра по умолчанию 100 мс. Сколько раз анимация зациклена в исходнике, в результат не выгружается.

Есть важная деталь по работе с такими файлами в движке. PNG с картой нормалей — это не картинка для показа, а файл с данными: отключите для него sRGB-сэмплирование в движке. Рисуйте арт в его родном пиксельном разрешении. Сама генерация оценивает рельеф по цвету и форме, поэтому нарисованные художником блики тоже могут превратиться в детали поверхности.

WebAssembly

Сам модуль для браузера собирается под цель wasm32-unknown-unknown и требует CLI wasm-bindgen версии 0.2.126:

cargo build --release --target wasm32-unknown-unknown --no-default-features --locked
wasm-bindgen target/wasm32-unknown-unknown/release/spritefusion_sprite_normals.wasm --target web --out-dir pkg

Готовый пакет подключается в проект так:

import init, { generate_normal_map } from "./pkg/spritefusion_sprite_normals.js";

await init();
const result = generate_normal_map(inputBytes, JSON.stringify({ strength: 1.5 }));
try {
  const normalPng = result.normal_png;
  const colorPng = result.color_png;
  const metadata = JSON.parse(result.metadata);
  // Use these bytes to save files or create image URLs.
} finally {
  result.free();
}

Первый аргумент — Uint8Array с байтами входного файла, второй — строка с настройками; передайте "{}", если нужны значения по умолчанию. Доступные ключи: strength, detail, smoothing, volume, frame_width, frame_height, directx. Байты в возвращаемых массивах принадлежат этим массивам — после вызова free() они остаются валидными.

CLI и WASM-модуль используют один и тот же генератор и один декодер анимаций, так что результат в браузере и в консоли совпадает.

Ограничения на вход

CLI и WASM-модуль работают на одном коде, поэтому ограничения на вход общие:

  • размер входного файла — до 64 МиБ;
  • до 4 194 304 пикселей вместе с отступами для обработки;
  • для анимаций дополнительно — до 256 кадров и 4 194 304 декодированных пикселей суммарно.

Разработка

Стандартная проверка для Rust-проекта:

cargo test --locked
cargo clippy --all-targets --locked -- -D warnings
cargo fmt --check

Связанные проекты и лицензия

Sprite Normals — часть проекта Sprite Fusion. Сам Sprite Fusion — инструмент для генерации пиксель-арт спрайтов и анимаций в разработке игр.

Из других открытых инструментов для пиксель-арта в README отмечен Pixel Snapper — он приводит пиксели к чистой сетке и сделан специально, чтобы приводить в порядок пиксель-арт, сгенерированный ИИ.

Лицензия — MIT, автор — Hugo Duprez.

Источник: https://github.com/Hugo-Dz/spritefusion-sprite-normals