О чем этот пример
Работа с динамическими текстурами и снапшотами — мощный инструмент для создания спецэффектов, UI-элементов или оптимизации рендеринга в Phaser. Однако при переносе текста на текстуру могут возникать визуальные артефакты в виде белых пикселей по краям символов. Эта статья на практическом примере показывает, как корректно создать и сохранить текстуру с текстом, избежав нежелательных побочных эффектов, и объясняет причины их появления.
Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.
Живой запуск
Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.
Исходный код
class Example extends Phaser.Scene
{
constructor ()
{
super();
}
create ()
{
const text = this.add.text(0, 0, "0123ABC", {
fontSize: "180px",
fontStyle: "900",
});
// DT test
const texture = this.textures.addDynamicTexture('text', text.width, text.height);
//texture.fill(0x000000, 0.0001);
texture.draw(text);
// Issue 5939 example:
// this.add.rectangle(0, 300, 800, 400, 0xf7f5f5).setOrigin(0);
// const texture = this.add.renderTexture(0, 300, text.width, text.height).setOrigin(0, 0);
// texture.draw(text);
texture.snapshot(image => {
const div = document.createElement('div');
div.style = 'background: white';
div.appendChild(image);
document.body.appendChild(div);
});
}
}
const config = {
type: Phaser.AUTO,
parent: 'phaser-example',
width: 800,
height: 600,
scene: Example
};
const game = new Phaser.Game(config);
Проблема: белая бахрома вокруг текста
Исходный код демонстрирует проблему, зафиксированную в Issue #5939 репозитория Phaser. При использовании RenderTexture и метода draw() для отрисовки текстового объекта на текстуре, а затем при её отображении на экране, вокруг символов часто появляются тонкие белые или цветные пиксели (фринджи).
Это происходит из-за особенностей работы WebGL-рендерера и сглаживания (anti-aliasing) при рисовании текста. Когда текст рендерится на прозрачный фон текстуры, алгоритмы сглаживания добавляют полупрозрачные пиксели на границах символов. При последующей композиции этих пикселей с фоном сцены (особенно если он не чисто чёрный) они могут стать заметными.
Решение через DynamicTexture и snapshot
Вместо RenderTexture в исправленном примере используется DynamicTexture. Это более гибкий инструмент для программного создания и управления пиксельными данными текстуры. Ключевой шаг — метод snapshot(), который асинхронно создаёт HTMLImageElement из текущего состояния текстуры.
Давайте разберем исправленный код по шагам:
const text = this.add.text(0, 0, "0123ABC", {
fontSize: "180px",
fontStyle: "900",
});
Сначала создаётся текстовый игровой объект с крупным жирным шрифтом. Он будет использован как источник для текстуры.
const texture = this.textures.addDynamicTexture('text', text.width, text.height);
Создаётся динамическая текстура с именем 'text'. Её размеры точно соответствуют размерам текстового объекта, что позволяет избежать лишних пустых областей.
texture.draw(text);
Метод draw() копирует пиксельные данные из отрендеренного текстового объекта в нашу динамическую текстуру. На этом этапе в текстуре уже содержится текст с альфа-каналом.
Создание и использование снапшота
Метод snapshot() — это финальный аккорд. Он захватывает текущее состояние WebGL-текстуры и конвертирует его в формат, понятный DOM.
texture.snapshot(image => {
const div = document.createElement('div');
div.style = 'background: white';
div.appendChild(image);
document.body.appendChild(div);
});
Колбэк-функция получает готовый HTML-элемент <img>. Этот элемент можно встроить куда угодно: добавить на страницу (как в примере), использовать как источник для другого канваса или даже сохранить на диск пользователя. В примере изображение помещается в div с белым фоном, чтобы продемонстрировать чистоту краёв текста — белой бахромы не будет.
Важно: snapshot() выполняется асинхронно, так как для чтения данных из GPU требуется время. Не пытайтесь использовать возвращённое изображение сразу после вызова метода, работайте только внутри колбэка.
Почему это работает? За кулисами снапшота
Когда вы вызываете texture.snapshot(), Phaser выполняет серию операций:
1. **Флиппинг буфера**: Поскольку WebGL использует координаты, где Y направлен вверх, изображение может быть перевёрнутым. Phaser корректирует это.
2. **Чтение из Framebuffer**: Данные пикселей считываются из области памяти GPU (фреймбуфера), связанной с этой текстурой.
3. **Создание Bitmap**: На основе этих данных создаётся битмап-представление.
4. **Создание Image**: Битовые данные кодируются (например, в PNG) и помещаются в элемент <img>, у которого src является data-URL.
Критический момент: процесс создания снапшота "запекает" все пиксели, включая полупрозрачные края от сглаживания, в итоговое растровое изображение. При отрисовке этого изображения на белом фоне в DOM полупрозрачные пиксели смешиваются с белым, а не с фоном вашей игры, что и даёт чёткий край.
Что попробовать дальше
Использование связки DynamicTexture + snapshot() — это надёжный способ получить чистое растровое изображение любого игрового объекта, особенно текста. Этот метод решает проблему визуальных артефактов при переносе контента из WebGL-контекста в DOM или файлы.
**Идеи для экспериментов:**
1. Создавайте кэшированные снапшоты сложных UI-панелей для их быстрого вывода.
2. Реализуйте систему сохранения скриншотов игровых моментов с помощью this.renderer.snapshot() для всей сцены.
3. Генерируйте текстуры для частиц на лету, рисуя в них случайный текст или символы.
4. Попробуйте применить fill() с почти нулевой альфой к текстуре перед draw(), как в закомментированной строке, чтобы увидеть, как это влияет на конечный результат.
