Uniform-переменные: типы и передача значений

Uniform-переменные — это специальные глобальные переменные шейдера, значения которых остаются неизменными для всех вершин или фрагментов в рамках одного вызова отрисовки. В экосистеме PixiJS они используются при создании пользовательских фильтров (PIXI.Filter), кастомных шейдеров (PIXI.Shader) и низкоуровневой работы с WebGL через систему рендеринга.

Uniform-переменные:

  • объявляются в GLSL-коде шейдера с ключевым словом uniform;
  • передаются из JavaScript-кода;
  • доступны как во вершинном (vertex), так и во фрагментном (fragment) шейдере;
  • не изменяются внутри одного draw-call.

Объявление uniform-переменных в GLSL

Пример объявления во фрагментном шейдере:

precision mediump float;

uniform float uTime;
uniform vec2 uResolution;
uniform sampler2D uTexture;

void main() {
    vec2 uv = gl_FragCoord.xy / uResolution;
    vec4 color = texture2D(uTexture, uv);
    gl_FragColor = color * abs(sin(uTime));
}

Здесь используются три типа uniform-переменных:

  • float
  • vec2
  • sampler2D

Создание шейдера и передача uniform в PixiJS

В PixiJS uniform-переменные передаются через объект uniforms при создании PIXI.Filter или PIXI.Shader.

Пример создания фильтра

const fragmentShader = `
precision mediump float;

uniform float uTime;
uniform vec2 uResolution;
uniform sampler2D uSampler;

varying vec2 vTextureCoord;

void main() {
    vec4 color = texture2D(uSampler, vTextureCoord);
    gl_FragColor = color * abs(sin(uTime));
}
`;

const filter = new PIXI.Filter(null, fragmentShader, {
    uTime: 0.0,
    uResolution: [800, 600]
});

Uniform-переменные задаются как свойства обычного JavaScript-объекта.


Основные типы uniform-переменных

1. Скалярные типы

float

GLSL:

uniform float uAlpha;

PixiJS:

uniforms: {
    uAlpha: 1.0
}

Тип значения в JavaScript — number.


int

GLSL:

uniform int uMode;

PixiJS:

uniforms: {
    uMode: 2
}

Передаётся как обычное число. WebGL автоматически приводит его к целочисленному типу.


2. Векторные типы

vec2

GLSL:

uniform vec2 uOffset;

PixiJS:

uniforms: {
    uOffset: [100.0, 50.0]
}

Передаётся как массив из двух чисел.


vec3

uniform vec3 uColor;
uniforms: {
    uColor: [1.0, 0.5, 0.2]
}

Используется для RGB-цветов, направлений, координат в 3D.


vec4

uniform vec4 uColor;
uniforms: {
    uColor: [1.0, 0.5, 0.2, 1.0]
}

Применяется для RGBA-цвета, прямоугольных областей, параметров.


3. Матричные типы

mat3

uniform mat3 uMatrix;

PixiJS:

uniforms: {
    uMatrix: [
        1, 0, 0,
        0, 1, 0,
        0, 0, 1
    ]
}

Передаётся как массив из 9 чисел (column-major порядок).


mat4

uniform mat4 uProjection;
uniforms: {
    uProjection: new Float32Array(16)
}

Рекомендуется использовать Float32Array для матриц размером 4x4.


4. Текстурные типы

sampler2D

Текстуры передаются иначе, чем примитивные типы.

GLSL:

uniform sampler2D uTexture;

PixiJS:

uniforms: {
    uTexture: PIXI.Texture.from('image.png')
}

При использовании PIXI.Filter стандартный uniform uSampler автоматически содержит текстуру объекта, к которому применён фильтр.


Динамическое обновление uniform-переменных

Uniform можно изменять во время выполнения:

app.ticker.add((delta) => {
    filter.uniforms.uTime += 0.05 * delta;
});

PixiJS отслеживает изменения и передаёт обновлённые значения в GPU перед отрисовкой.

Особенности:

  • изменение происходит через filter.uniforms;
  • объект uniforms не должен полностью заменяться;
  • изменяются только значения свойств.

Правильно:

filter.uniforms.uTime = 1.5;

Неправильно:

filter.uniforms = { uTime: 1.5 };

Во втором случае связь с внутренней системой рендеринга нарушается.


Передача сложных структур

WebGL 1.0 (используется PixiJS) имеет ограничения:

  • структуры (struct) поддерживаются;
  • массивы структур ограничены;
  • динамические размеры массивов невозможны.

Пример структуры

GLSL:

struct Light {
    vec2 position;
    vec3 color;
};

uniform Light uLight;

Передача в PixiJS:

uniforms: {
    uLight: {
        position: [300, 200],
        color: [1.0, 1.0, 1.0]
    }
}

PixiJS автоматически развернёт структуру в соответствующие uniform-переменные WebGL.


Массивы uniform-переменных

Массив float

GLSL:

uniform float uWeights[5];

PixiJS:

uniforms: {
    uWeights: [0.2, 0.3, 0.1, 0.25, 0.15]
}

Массив vec2

uniform vec2 uPoints[3];
uniforms: {
    uPoints: [
        100, 100,
        200, 200,
        300, 300
    ]
}

Массив передаётся как плоский список значений.


Автоматические uniform-переменные в PixiJS

При использовании PIXI.Filter система автоматически добавляет:

  • uSampler — текстура объекта;
  • inputSize — размер входной текстуры;
  • outputFrame — область вывода;
  • resolution — масштаб рендеринга;
  • projectionMatrix — матрица проекции.

Эти переменные не требуют явного указания в uniforms.


Производительность и оптимизация

1. Минимизация обновлений

Изменение uniform вызывает синхронизацию CPU → GPU. Частые изменения больших массивов могут стать узким местом.

Рекомендации:

  • изменять только необходимые значения;
  • использовать Float32Array для больших данных;
  • избегать пересоздания массивов каждый кадр.

2. Ограничения WebGL

  • Максимальное количество uniform-переменных ограничено.
  • Размер массивов ограничен аппаратно.
  • Большие структуры могут не компилироваться на слабых устройствах.

Проверка лимитов:

const gl = app.renderer.gl;
console.log(gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS));

Типичные ошибки

Несоответствие типов

uniform vec2 uOffset;
uOffset: 10

Ошибка: требуется массив из двух чисел.


Неправильный порядок матрицы

GLSL использует column-major порядок. Неправильное заполнение матрицы приводит к искажению трансформаций.


Отсутствие precision во фрагментном шейдере

Во фрагментном шейдере необходимо указывать точность:

precision mediump float;

Без этого шейдер может не скомпилироваться.


Использование uniform в кастомном Shader

Создание PIXI.Shader напрямую:

const program = PIXI.Program.from(vertexSource, fragmentSource);

const shader = new PIXI.Shader(program, {
    uTime: 0.0
});

Такой подход применяется при создании Mesh, Geometry и низкоуровневых графических примитивов.


Связь uniform с системой рендеринга PixiJS

Внутри PixiJS:

  • каждый Shader хранит UniformGroup;
  • значения кэшируются;
  • обновление происходит только при изменении;
  • система различает текстурные и числовые uniform.

Это снижает количество вызовов gl.uniform*.


Практический пример: анимированный цветовой фильтр

GLSL:

precision mediump float;

uniform float uTime;
varying vec2 vTextureCoord;
uniform sampler2D uSampler;

void main() {
    vec4 color = texture2D(uSampler, vTextureCoord);
    color.r += sin(uTime) * 0.2;
    gl_FragColor = color;
}

Jav * aScript:

const filter = new PIXI.Filter(null, fragmentShader, {
    uTime: 0
});

sprite.filters = [filter];

app.ticker.add((delta) => {
    filter.uniforms.uTime += 0.05 * delta;
});

В результате происходит динамическое изменение красного канала изображения.


Uniform-переменные являются ключевым механизмом передачи данных между JavaScript и GPU в PixiJS. Понимание их типов, ограничений и особенностей передачи позволяет создавать производительные фильтры, кастомные шейдеры и сложные графические эффекты.