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

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

Версия 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('checker', 'assets/pics/checker.png');
    }

    create ()
    {
        const frag = `
        precision mediump float;

        uniform sampler2D iChannel0;

        varying vec2 outTexCoord;

        void main ()
        {
            vec4 pixel = texture2D(iChannel0, outTexCoord);

            gl_FragColor = vec4(outTexCoord.xyx * pixel.rgb, 1.0);
        }
        `;

        const shader = this.add.shader({
            name: 'simpleTexture',
            fragmentSource: frag,
            initialUniforms: {
                iChannel0: 0
            }
        }, 400, 300, 800, 600, [ 'checker' ]);

        //  Or, set the texture like this:

        // shader.setUniform('iChannel0', 0);
        // shader.setTextures([ 'checker' ]);
    }
}

const game = new Phaser.Game({
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
});

Подготовка сцены и загрузка ассетов

Как и в любой сцене Phaser, мы начинаем с загрузки необходимых ресурсов. В данном случае нам нужна только одна текстурная карта, которая будет использоваться шейдером в качестве исходных данных для цвета.

preload ()
{
    this.load.setBaseURL('https://raw.githubusercontent.com/phaserjs/examples/master/public/');
    this.load.image('checker', 'assets/pics/checker.png');
}

Метод preload загружает изображение с шахматной текстурой и присваивает ему ключ 'checker'. Эта текстура будет передана в шейдер как uniform-переменная.

Создание шейдера: от кода до объекта

Шейдер создается непосредственно в методе create. Для этого используется метод this.add.shader(). Ключевые компоненты — это GLSL-код фрагментного шейдера и его начальные параметры.

const frag = `
precision mediump float;

uniform sampler2D iChannel0;

varying vec2 outTexCoord;

void main ()
{
    vec4 pixel = texture2D(iChannel0, outTexCoord);

    gl_FragColor = vec4(outTexCoord.xyx * pixel.rgb, 1.0);
}
`;

Этот код на GLSL определяет, как будет окрашен каждый пиксель. sampler2D iChannel0 — это ссылка на текстуру. outTexCoord — это автоматически переданные координаты текстуры (от 0.0 до 1.0).

1.  `vec4 pixel = texture2D(iChannel0, outTexCoord);` — берет цвет пикселя из текстуры по текущим координатам.
2.  `gl_FragColor = vec4(outTexCoord.xyx * pixel.rgb, 1.0);` — вычисляет итоговый цвет. Координаты текстуры (красный и зеленый каналы) умножаются на цвет пикселя из текстуры, создавая градиентный эффект, зависящий от положения и исходного изображения.

Инициализация шейдера в Phaser

Созданный код шейдера нужно превратить в игровой объект. Это делает метод this.add.shader().

const shader = this.add.shader({
    name: 'simpleTexture',
    fragmentSource: frag,
    initialUniforms: {
        iChannel0: 0
    }
}, 400, 300, 800, 600, [ 'checker' ]);

Разберем аргументы: - **Конфигурационный объект:** name — внутреннее имя, fragmentSource — наш GLSL-код, initialUniforms — задает, что uniform-переменная iChannel0 будет связана с текстурным блоком под индексом 0. - **Позиция и размер:** 400, 300 — координаты центра шейдера на холсте, 800, 600 — его ширина и высота. - **Массив текстур:** [ 'checker' ] — ключи текстур, которые будут загружены в шейдер. Индекс в этом массиве (0) соответствует индексу текстурного блока, указанному в initialUniforms.iChannel0.

Альтернативный способ настройки

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

//  Or, set the texture like this:

// shader.setUniform('iChannel0', 0);
// shader.setTextures([ 'checker' ]);

Это полезно, если текстура загружается динамически или нужно менять ее во время выполнения. setUniform привязывает переменную iChannel0 к текстурному блоку 0, а setTextures загружает массив текстур в соответствующие блоки.

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

Для работы шейдеров критически важно использовать WebGL-рендерер. Это настраивается при создании экземпляра игры.

const game = new Phaser.Game({
    type: Phaser.WEBGL, // Обязательно WEBGL, а не AUTO или CANVAS
    parent: 'phaser-example',
    width: 800,
    height: 600,
    scene: Example
});

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

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

Вы создали свой первый шейдер в Phaser 3, который динамически окрашивает текстуру на основе координат. Это основа для бесчисленных эффектов. Поэкспериментируйте: измените формулу в gl_FragColor (попробуйте sin(outTexCoord.x * 10.0) для полос), передайте в шейдер время через uniform-переменную для анимации или подставьте другую текстуру для создания сложных масок и смешивания.