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

Встроенные режимы наложения вроде `ADD` или `MULTIPLY` — это удобно, но что если вам нужен уникальный визуальный эффект, которого нет в стандартном наборе? Phaser позволяет создавать собственные blend modes на уровне 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.image('turkey', 'assets/pics/turkey-1985086.jpg');
        this.load.image('face', 'assets/pics/bw-face.png');
    }

    create ()
    {
        //  WebGL only:

        var gl = this.sys.game.renderer.gl;

        var consts = [
            gl.ZERO,
            gl.ONE,
            gl.SRC_COLOR,
            gl.ONE_MINUS_SRC_COLOR,
            gl.DST_COLOR,
            gl.ONE_MINUS_DST_COLOR,
            gl.SRC_ALPHA,
            gl.ONE_MINUS_SRC_ALPHA,
            gl.DST_ALPHA,
            gl.ONE_MINUS_DST_ALPHA,
            gl.CONSTANT_COLOR,
            gl.ONE_MINUS_CONSTANT_COLOR,
            gl.CONSTANT_ALPHA,
            gl.ONE_MINUS_CONSTANT_ALPHA,
            gl.SRC_ALPHA_SATURATE
        ];

        var equations = [
            gl.FUNC_ADD,
            gl.FUNC_SUBTRACT,
            gl.FUNC_REVERSE_SUBTRACT
        ];

        var list = [
            { val: 0, text: 'ZERO' },
            { val: 1, text: 'ONE' },
            { val: 2, text: 'SRC_COLOR' },
            { val: 3, text: 'ONE_MINUS_SRC_COLOR' },
            { val: 4, text: 'DST_COLOR' },
            { val: 5, text: 'ONE_MINUS_DST_COLOR' },
            { val: 6, text: 'SRC_ALPHA' },
            { val: 7, text: 'ONE_MINUS_SRC_ALPHA' },
            { val: 8, text: 'DST_ALPHA' },
            { val: 9, text: 'ONE_MINUS_DST_ALPHA' },
            { val: 10, text: 'CONSTANT_COLOR' },
            { val: 11, text: 'ONE_MINUS_CONSTANT_COLOR' },
            { val: 12, text: 'CONSTANT_ALPHA' },
            { val: 13, text: 'ONE_MINUS_CONSTANT_ALPHA' },
            { val: 14, text: 'SRC_ALPHA_SATURATE' }
        ];

        var list2 = [
            { val: 0, text: 'FUNC_ADD' },
            { val: 1, text: 'FUNC_SUBTRACT' },
            { val: 2, text: 'FUNC_REVERSE_SUBTRACT' }
        ];

        var newMode = [ gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ];
        var equation = equations[0];

        var renderer = this.sys.game.renderer;

        var modeIndex = renderer.addBlendMode(newMode, equation);

        // this.add.image(400, 300, 'face');
        // this.add.image(400, 300, 'dst');
        // this.add.image(400, 300, 'src').setBlendMode(modeIndex);
        // this.add.image(400, 300, 'logo').setBlendMode(modeIndex);

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

        var graphics = this.add.graphics();

        var color = 0x00ffff;
        var alpha = 1;

        graphics.fillStyle(color, alpha);

        graphics.fillCircle(400, 300, 256);

        graphics.setBlendMode(modeIndex);

        //  zero, one, one, zero, add

        //  Create the 4 select lists

        var srcRGB = $('<select>').attr('id', 'srcRGB').data('idx', 0).appendTo('body');
        var dstRGB = $('<select>').attr('id', 'dstRGB').data('idx', 1).appendTo('body');
        var srcAlpha = $('<select>').attr('id', 'srcAlpha').data('idx', 2).appendTo('body');
        var dstAlpha = $('<select>').attr('id', 'dstAlpha').data('idx', 3).appendTo('body');

        $(list).each(function() {

            srcRGB.append($("<option>").attr('value', this.val).text(this.text));
            dstRGB.append($("<option>").attr('value', this.val).text(this.text));
            srcAlpha.append($("<option>").attr('value', this.val).text(this.text));
            dstAlpha.append($("<option>").attr('value', this.val).text(this.text));

        });

        srcRGB.val('6').change();
        dstRGB.val('7').change();
        srcAlpha.val('1').change();
        dstAlpha.val('7').change();

        $('#srcRGB, #dstRGB, #srcAlpha, #dstAlpha').change(function () {

            var idx = $(this).data('idx');

            newMode[idx] = consts[this.value];

            renderer.updateBlendMode(modeIndex, newMode, equation);

        });

        var equ = $('<select>').attr('id', 'equ').appendTo('body');

        $(list2).each(function() {
            equ.append($("<option>").attr('value', this.val).text(this.text));
        });

        $(equ).on('change', function () {

            equation = equations[this.value];

            renderer.updateBlendMode(modeIndex, newMode, equation);

        });

    }

}

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

const game = new Phaser.Game(config);

Зачем нужны пользовательские blend modes?

Стандартные режимы наложения (BlendModes) в Phaser покрывают большинство базовых случаев. Однако, для достижения специфических эффектов, таких как кастомное смешивание цветов с разными уравнениями для RGB и Alpha-каналов, требуется низкоуровневый доступ к рендереру. Это особенно актуально для проектов с упором на уникальную стилистическую графику, визуальные фильтры или нестандартные эффекты свечения.

Важное ограничение: эта функциональность работает **только с рендерером WebGL**. При использовании Phaser.CANVAS методы, описанные ниже, будут недоступны.

Получение доступа к WebGL и подготовка констант

Первый шаг — получить доступ к объекту WebGL рендерера игры. Все константы, определяющие поведение пикселей источника (source) и назначения (destination), берутся напрямую из этого контекста.

var gl = this.sys.game.renderer.gl;

var consts = [
    gl.ZERO,
    gl.ONE,
    gl.SRC_COLOR,
    // ... остальные константы
];

var equations = [
    gl.FUNC_ADD,
    gl.FUNC_SUBTRACT,
    gl.FUNC_REVERSE_SUBTRACT
];

Массив consts содержит все возможные факторы наложения, а equations — уравнения, которые определяют, как эти факторы комбинируются (сложение, вычитание). Эти значения являются нативными для WebGL и передаются в движок как есть.

Создание и регистрация нового режима наложения

Пользовательский режим наложения определяется массивом из четырёх элементов. Каждый элемент — это WebGL-константа, определяющая фактор для каналов: 1. srcRGB — фактор для RGB канала источника. 2. dstRGB — фактор для RGB канала назначения. 3. srcAlpha — фактор для Alpha канала источника. 4. dstAlpha — фактор для Alpha канала назначения.

После определения массива и уравнения, режим регистрируется в рендерере. Метод возвращает его индекс, который используется для применения к игровым объектам.

var newMode = [ gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ];
var equation = equations[0]; // gl.FUNC_ADD

var renderer = this.sys.game.renderer;
var modeIndex = renderer.addBlendMode(newMode, equation);

В данном примере начальный режим имитирует стандартное альфа-смешивание. Индекс modeIndex теперь можно использовать в методе .setBlendMode().

Применение blend mode к графике и его динамическое обновление

Зарегистрированный режим применяется к любому объекту, поддерживающему наложение, например, к Graphics. В примере поверх фонового изображения рисуется полупрозрачный круг, к которому и применяется кастомный blend mode.

this.add.image(400, 300, 'turkey');
var graphics = this.add.graphics();
graphics.fillStyle(0x00ffff, 1);
graphics.fillCircle(400, 300, 256);
graphics.setBlendMode(modeIndex);

Самая мощная часть — возможность менять параметры наложения «на лету». Для этого в примере создаются элементы управления (select), изменение значений в которых вызывает метод updateBlendMode рендерера.

$('#srcRGB, #dstRGB, #srcAlpha, #dstAlpha').change(function () {
    var idx = $(this).data('idx'); // Узнаём, какой параметр меняем
    newMode[idx] = consts[this.value]; // Обновляем массив newMode
    renderer.updateBlendMode(modeIndex, newMode, equation); // Применяем изменения
});

Метод renderer.updateBlendMode принимает индекс обновляемого режима, новый массив параметров и уравнение. Это позволяет создавать интерактивные шейдерные конструкторы прямо в браузере.

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

Создание пользовательских режимов наложения в Phaser — это прямой путь к уникальной визуальной идентичности вашей игры. Вы получили инструменты для низкоуровневой работы с WebGL, регистрации своих blend modes и их динамического обновления. **Идеи для экспериментов:** 1. Создайте режим наложения, который оставляет только самые яркие области источника (подобно эффекту свечения), комбинируя gl.ONE и gl.ONE_MINUS_SRC_COLOR. 2. Реализуйте интерактивный цветовой фильтр, меняющий уравнение смешивания с FUNC_ADD на FUNC_REVERSE_SUBTRACT по клику, для создания эффекта «негатива». 3. Примените кастомный blend mode не к Graphics, а к спрайту или частицевой системе, чтобы увидеть, как он взаимодействует с текстурой.