О чем этот пример

Шейдеры позволяют выводить графику вашей игры на новый уровень, добавляя динамические эффекты, которые сложно или невозможно реализовать стандартными средствами рендеринга спрайтов. В Phaser 3 работа с шейдерами инкапсулирована в удобный API, который скрывает сложность WebGL, позволяя разработчикам сосредоточиться на творческой части. В этой статье мы разберем, как загрузить и отобразить простой шейдер, связать его с игровым временем и интегрировать с обычными игровыми объектами.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    constructor()
    {
        super();
    }

    preload()
    {
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
        this.load.glsl('hsl', 'assets/shaders/hsl.frag');
        this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
    }

    create()
    {
        const shader = this.add.shader({
            name: 'hsl',
            fragmentKey: 'hsl',
            setupUniforms: (setUniform, drawingContext) =>
            {
                setUniform('time', this.game.loop.getDuration());
            },
        }, 400, 300, this.scale.width, this.scale.width);
        this.add.image(400, 300, 'logo');
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

const game = new Phaser.Game(config);

Загрузка шейдеров и ресурсов

Для работы с шейдерами в Phaser 3 используется загрузчик this.load.glsl(). Этот метод специально предназначен для загрузки текстовых файлов с исходным кодом шейдеров.

preload()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.glsl('hsl', 'assets/shaders/hsl.frag');
    this.load.image('logo', 'assets/sprites/phaser3-logo-small.png');
}

Первой строкой в preload мы задаем базовый URL для всех последующих загрузок. Далее загружаем фрагментный шейдер с ключом 'hsl' из файла hsl.frag. Параллельно загружается обычное изображение логотипа. Обратите внимание, что для отрисовки шейдеров требуется контекст WebGL, поэтому в конфигурации игры должен быть указан type: Phaser.WEBGL.

Создание и настройка шейдера

Создание объекта шейдера происходит в методе create с помощью this.add.shader(). Этот метод принимает объект конфигурации и параметры позиционирования.

const shader = this.add.shader({
    name: 'hsl',
    fragmentKey: 'hsl',
    setupUniforms: (setUniform, drawingContext) =>
    {
        setUniform('time', this.game.loop.getDuration());
    },
}, 400, 300, this.scale.width, this.scale.width);

Конфигурационный объект содержит: * name: произвольное имя шейдера. * fragmentKey: ключ, под которым был загружен исходный код шейдера (в нашем случае 'hsl'). * setupUniforms: критически важная функция, которая вызывается каждый кадр для обновления uniform-переменных шейдера. Она получает функцию setUniform для установки значений и контекст рендеринга.

Внутри setupUniforms мы передаем шейдеру uniform-переменную time со значением, равным общему времени работы игры в секундах (this.game.loop.getDuration()). Это классический прием для создания анимированных эффектов (например, плавного изменения цвета или волн).

Последние четыре аргумента this.add.shader() задают позицию шейдера по X и Y (400, 300) и его ширину с высотой. Интересно, что в примере и ширина, и высота установлены в this.scale.width, что создаст квадратный шейдер, растянутый на всю ширину окна игры.

Интеграция шейдера с игровым миром

Шейдер в Phaser — это такой же игровой объект (Game Object), как спрайт или текст. Его можно перемещать, масштабировать и накладывать на другие объекты. Порядок добавления объектов определяет их слой (z-index).

this.add.image(400, 300, 'logo');

В примере после создания шейдера на те же координаты (400, 300) добавляется изображение логотипа. Поскольку изображение добавлено позже, оно будет отрисовано поверх шейдера. Это позволяет использовать шейдеры в качестве фона или сложных динамических задних планов под обычными спрайтами. Вы можете экспериментировать с порядком вызовов, чтобы поместить шейдер поверх графики или между слоями.

Конфигурация игры для WebGL

Без правильной конфигурации шейдеры работать не будут. Ключевой параметр — type.

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
};

Установка type: Phaser.WEBGL обязательна для использования шейдеров, так как они требуют контекста WebGL. Если указать Phaser.AUTO, движок попытается использовать WebGL, но откатится к Canvas, если он недоступен, что приведет к ошибке при создании шейдера. Явное указание Phaser.WEBGL гарантирует, что игра запустится только при поддержке необходимого контекста.

Что попробовать дальше

Как видно из примера, базовое использование шейдеров в Phaser 3 сводится к их загрузке, созданию объекта и передаче динамических данных через uniform-переменные. Это открывает огромный простор для экспериментов. Попробуйте изменить размеры шейдера, чтобы он занимал только часть экрана. Передавайте в шейдер не только time, но и позицию мыши (this.input.activePointer.x / y), создавая интерактивные эффекты. Самый интересный шаг — написание или модификация собственных шейдеров на GLSL для создания уникальных визуальных стилей вашей игры.