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

Визуальные эффекты — ключевой элемент игровой атмосферы. Phaser предоставляет мощный инструмент `add.shader` для работы с GLSL-шейдерами, позволяя создавать сложные графические трансформации, такие как плавные градиенты, прямо на Canvas или WebGL. Эта статья на практическом примере покажет, как загружать, настраивать и применять шейдеры для создания линейных градиентов, что откроет путь к уникальному стилю вашей игры без необходимости рисовать каждую текстуру вручную.

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

Живой запуск

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

Исходный код


class Example extends Phaser.Scene
{
    preload ()
    {
        
        this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('touhou', 'assets/pics/touhou1.png');

        this.load.glsl('gradient-color', 'assets/shaders/gradients/gradient-color.glsl');
        this.load.glsl('gradient-process', 'assets/shaders/gradients/gradient-process.glsl');
        this.load.glsl('srgb', 'assets/shaders/gradients/srgb-color.glsl');
        this.load.glsl('value-linear', 'assets/shaders/gradients/value-linear.glsl');
    }

    create ()
    {
        const getSource = (key) => this.cache.shader.get(key).glsl;
        const gradientShader = this.add.shader(
            {
                name: 'Gradient',
                shaderAdditions: [
                    // This addition controls a linear gradient.
                    // It must be defined in the shader before the gradient function is called.
                    {
                        name: 'LINEAR',
                        additions: { fragmentHeader: getSource('value-linear') }
                    },
                    // This addition defines standard gradient functionality.
                    {
                        name: 'STANDARD',
                        additions: {
                            fragmentHeader: [
                                getSource('srgb'),
                                getSource('gradient-color')
                            ].join('\n'),
                            fragmentProcess: getSource('gradient-process')
                        }
                    }
                ],
                setupUniforms: (setUniform) => {
                    setUniform('positionFrom', [0, 0]);
                    setUniform('positionTo', [0, 1]);
                    setUniform('color1', [0, 1, 0, 1]);
                    setUniform('color2', [0, 0, 1, 1]);
                    setUniform('steps', 16);
                    setUniform('repeat', 1);
                }
            }, 640, 360, 1280, 720);

        const sprite = this.add.image(640, 360, 'touhou');
        sprite.enableFilters().filters.internal.addPixelate();
    }
}

const config = {
    type: Phaser.AUTO,
    width: 1280,
    height: 720,
    backgroundColor: '#000000',
    parent: 'phaser-example',
    scene: Example
};

const game = new Phaser.Game(config);

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

Phaser позволяет загружать GLSL-код как отдельные ресурсы через метод this.load.glsl. Это удобно для организации сложных шейдеров, разбитых на модули.

В методе preload() примера загружаются четыре шейдерных файла. Ключевой момент: метод setBaseURL задаёт общий путь, чтобы не указывать полные URL для каждого ресурса.

this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
this.load.image('touhou', 'assets/pics/touhou1.png');

this.load.glsl('gradient-color', 'assets/shaders/gradients/gradient-color.glsl');
this.load.glsl('gradient-process', 'assets/shaders/gradients/gradient-process.glsl');
this.load.glsl('srgb', 'assets/shaders/gradients/srgb-color.glsl');
this.load.glsl('value-linear', 'assets/shaders/gradients/value-linear.glsl');

Сборка шейдера из модулей

После загрузки шейдеры хранятся в кеше this.cache.shader. Основная магия происходит в create() при создании объекта через this.add.shader. Шейдер конфигурируется как объект с именем и набором "добавлений" (Shader Additions).

Вспомогательная функция getSource извлекает исходный GLSL-код по ключу. Добавления LINEAR и STANDARD — это модули, которые встраивают свой код в определённые части итогового шейдера (fragmentHeader и fragmentProcess). Это похоже на систему импортов или инклюдов в GLSL.

const getSource = (key) => this.cache.shader.get(key).glsl;
const gradientShader = this.add.shader(
    {
        name: 'Gradient',
        shaderAdditions: [
            {
                name: 'LINEAR',
                additions: { fragmentHeader: getSource('value-linear') }
            },
            {
                name: 'STANDARD',
                additions: {
                    fragmentHeader: [
                        getSource('srgb'),
                        getSource('gradient-color')
                    ].join('\n'),
                    fragmentProcess: getSource('gradient-process')
                }
            }
        ],
        // ... setupUniforms
    }, 640, 360, 1280, 720);

Настройка uniform-переменных градиента

Параметры, которые передаются из JavaScript в шейдер, называются uniform-переменными. Функция setupUniforms вызывается для их инициализации. Внутри неё используется колбэк setUniform, чтобы задать значение.

В примере настраивается линейный градиент: - positionFrom и positionTo — векторы начала и конца градиента в нормализованных координатах (от 0 до 1). Здесь градиент вертикальный: от верхнего края (y=0) к нижнему (y=1). - color1 и color2 — цвета в формате RGBA (значения от 0 до 1). Здесь от зелёного к синему. - steps — количество шагов (дискретность) градиента. - repeat — режим повторения.

setupUniforms: (setUniform) => {
    setUniform('positionFrom', [0, 0]);
    setUniform('positionTo', [0, 1]);
    setUniform('color1', [0, 1, 0, 1]);
    setUniform('color2', [0, 0, 1, 1]);
    setUniform('steps', 16);
    setUniform('repeat', 1);
}

Добавление спрайта и фильтров

Шейдер градиента создаётся как отдельный объект на сцене с заданными координатами и размером. Параллельно добавляется обычный спрайт с изображением.

Важный приём: к спрайту применяется фильтр пикселизации. Метод enableFilters() активирует систему фильтров для этого объекта, а filters.internal.addPixelate() добавляет конкретный эффект. Это демонстрирует, что шейдеры (градиент) и фильтры (пикселизация) могут работать независимо и наслаиваться друг на друга, создавая комплексный визуал.

const sprite = this.add.image(640, 360, 'touhou');
sprite.enableFilters().filters.internal.addPixelate();

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

Использование модульной системы шейдеров в Phaser — мощный способ создания динамических фонов, эффектов освещения или цветовых масок. Вы можете экспериментировать: измените positionFrom и positionTo для диагонального градиента, поиграйте с steps для создания полосатого эффекта или анимируйте цвета через setUniform в игровом цикле, чтобы получить плавные переходы.