GLSL-шейдеры в Phaser: uniforms и attributes

Phaser использует WebGL для рендеринга графики, что позволяет применять GLSL-шейдеры для создания сложных визуальных эффектов. Шейдеры делятся на вершинные (vertex) и фрагментные (fragment). Вершинные шейдеры управляют положением вершин на экране, а фрагментные — цветом каждого пикселя. В Phaser чаще используются фрагментные шейдеры для эффектов постобработки, фильтров и анимации текстур.

Структура GLSL-шейдера

Каждый шейдер состоит из двух ключевых компонентов:

  • Attributes — входные данные на вершину. Например, позиция, цвет или текстурные координаты. Они задаются для каждой вершины и могут использоваться для вычисления трансформаций.
  • Uniforms — глобальные переменные, одинаковые для всех вершин или фрагментов. Используются для передачи данных из JavaScript, таких как время, текстуры или параметры эффектов.

Пример простого фрагментного шейдера:

precision mediump float;

uniform float u_time;
uniform vec2 u_resolution;

void main() {
    vec2 st = gl_FragCoord.xy / u_resolution;
    gl_FragColor = vec4(st.x, st.y, abs(sin(u_time)), 1.0);
}

Здесь u_time и u_resolutionuniforms, которые будут обновляться с каждой итерацией в Phaser.


Подключение шейдера в Phaser

Phaser 3 предоставляет удобный класс Phaser.Renderer.WebGL.Pipelines.PostFXPipeline для интеграции пользовательских шейдеров. Основные шаги:

  1. Создание кастомного пайплайна
class MyShaderPipeline extends Phaser.Renderer.WebGL.Pipelines.PostFXPipeline {
    constructor(game) {
        super({
            game,
            fragShader: document.getElementById('myShader').textContent
        });
    }

    onPreRender() {
        this.setFloat1('u_time', this.game.loop.time / 1000);
        this.set2f('u_resolution', this.game.scale.width, this.game.scale.height);
    }
}
  • Метод onPreRender вызывается перед каждым кадром для обновления uniforms.
  • setFloat1 и set2f устанавливают значения для шейдера.
  1. Регистрация пайплайна в игре
const config = {
    type: Phaser.WEBGL,
    width: 800,
    height: 600,
    scene: { preload, create },
    pipeline: { myShader: MyShaderPipeline }
};

const game = new Phaser.Game(config);
  1. Применение пайплайна к объекту
this.add.sprite(400, 300, 'myTexture').setPipeline('myShader');

Теперь к спрайту будет применяться фрагментный шейдер с переданными uniform-параметрами.


Работа с Attributes

Attributes передаются шейдеру через вершинные данные, чаще всего в форме буферов. Phaser автоматически создаёт стандартные атрибуты:

  • aPosition — координаты вершины;
  • aTexCoord — текстурные координаты;
  • aColor — цвет вершины.

Создание кастомного атрибута:

this.renderer.setVertexAttrib('aCustom', new Float32Array([0, 1, 0, 1]), 2);
  • Первым аргументом указывается имя атрибута в шейдере.
  • Вторым — массив значений для каждой вершины.
  • Третьим — размер (количество компонентов на вершину, например 2 для vec2).

В шейдере атрибут объявляется как:

attribute vec2 aCustom;

Использование атрибута позволяет создавать динамическую деформацию спрайтов и эффекты искажения без изменения текстуры на стороне CPU.


Uniforms: типы и обновление

Uniforms бывают различных типов:

  • float, vec2, vec3, vec4 — скаляры и векторы;
  • sampler2D — текстурные объекты;
  • int, bool — целые числа и булевы значения.

В Phaser uniforms обновляются через методы пайплайна:

  • setFloat1(name, value) — float;
  • setFloat2(name, x, y) — vec2;
  • setFloat3(name, x, y, z) — vec3;
  • setFloat4(name, x, y, z, w) — vec4;
  • setTexture(name, texture) — sampler2D.

Пример динамического изменения цвета:

this.pipeline.setFloat1('u_time', this.game.loop.time / 1000);
this.pipeline.setFloat3('u_color', Math.sin(t), Math.cos(t), Math.sin(t * 0.5));

Примеры эффектов

  1. Анимация через uniform времени
gl_FragColor = vec4(abs(sin(u_time)), st.y, st.x, 1.0);
  • Использование u_time позволяет создавать пульсирующие, волновые и мерцающие эффекты.
  1. Деформация через атрибуты
vec2 newPos = aTexCoord + vec2(sin(aPosition.x * 10.0 + u_time) * 0.05, 0.0);
vec4 color = texture2D(uSampler, newPos);
  • Атрибут aPosition используется для искажения текстуры, создавая эффект волн или колебаний.
  1. Маскирование текстур
vec4 texColor = texture2D(uSampler, aTexCoord);
vec4 maskColor = texture2D(uMask, aTexCoord);
gl_FragColor = texColor * maskColor.a;
  • Комбинирование нескольких sampler2D uniform позволяет создавать маски, градиенты и сложные фильтры.

Практические советы

  • Всегда нормализовать координаты в шейдере через u_resolution для независимости от размера экрана.
  • Использовать mediump для точности и производительности на мобильных устройствах.
  • Обновление uniforms в onPreRender гарантирует синхронизацию с игровым циклом.
  • Комбинировать атрибуты и uniforms для максимальной гибкости: атрибуты управляют формой, uniforms — параметрами эффекта.
  • Для тестирования шейдеров удобно использовать отдельный HTML <script type="x-shader/x-fragment"> и вставлять текст в Phaser через document.getElementById.

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