В основе рендеринга в PixiJS лежит работа с буферами вершин и шейдерами WebGL. Любая геометрия представляется набором вершин, каждая из которых содержит набор атрибутов — числовых данных, передаваемых в вершинный шейдер. Классическая конфигурация включает:
aVertexPosition);aTextureCoord);aColor);Пользовательские атрибуты вершин позволяют расширить этот набор произвольными данными: весами анимации, смещениями, индексами, параметрами освещения, масками и любыми другими значениями, необходимыми для вычислений на GPU.
В PixiJS работа с атрибутами осуществляется через классы:
PIXI.GeometryPIXI.BufferPIXI.ShaderPIXI.MeshПонимание их взаимодействия является ключевым для реализации пользовательских атрибутов.
PIXI.Geometry хранит:
PIXI.Buffer);Добавление пользовательского атрибута выполняется методом
addAttribute.
Пример создания геометрии с дополнительным атрибутом смещения:
const geometry = new PIXI.Geometry()
.addAttribute(
'aVertexPosition',
[
0, 0,
100, 0,
100, 100,
0, 100
],
2
)
.addAttribute(
'aOffset',
[
0, 0,
10, 0,
10, 10,
0, 10
],
2
)
.addIndex([0, 1, 2, 0, 2, 3]);
addAttributeaddAttribute(name, buffer, size, normalized, type, stride, start)
name — имя атрибута, совпадающее с именем в
шейдере;buffer — массив данных или экземпляр
PIXI.Buffer;size — количество компонентов на вершину (1–4);normalized — требуется ли нормализация;type — тип данных (PIXI.TYPES.FLOAT по
умолчанию);stride — шаг в байтах;start — смещение в байтах.Пользовательский атрибут должен быть объявлен в вершинном шейдере:
attribute vec2 aVertexPosition;
attribute vec2 aOffset;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
void main(void)
{
vec3 position = projectionMatrix * translationMatrix *
vec3(aVertexPosition + aOffset, 1.0);
gl_Position = vec4(position.xy, 0.0, 1.0);
}
Ключевые требования:
Geometry и в GLSL должно совпадать.vec2, vec3,
float) должна соответствовать параметру
size.type буфера.Для динамически изменяемых данных создаётся отдельный
PIXI.Buffer:
const offsetBuffer = new PIXI.Buffer(new Float32Array([
0, 0,
10, 0,
10, 10,
0, 10
]), true);
geometry.addAttribute('aOffset', offsetBuffer, 2);
Второй параметр true означает, что буфер динамический
(static = false), что позволяет обновлять данные:
offsetBuffer.data[0] = 5;
offsetBuffer.update();
Для повышения производительности данные могут храниться в одном
буфере с использованием шага (stride) и смещения
(start).
Пример структуры:
[x, y, offsetX, offsetY,
x, y, offsetX, offsetY,
...]
Создание:
const buffer = new PIXI.Buffer(new Float32Array([
0, 0, 0, 0,
100, 0, 10, 0,
100, 100, 10, 10,
0, 100, 0, 10
]));
geometry
.addAttribute('aVertexPosition', buffer, 2, false, PIXI.TYPES.FLOAT, 16, 0)
.addAttribute('aOffset', buffer, 2, false, PIXI.TYPES.FLOAT, 16, 8);
Здесь:
stride = 16 байт (4 float × 4 байта);start = 0 для позиции;start = 8 для смещения.Интерливинг снижает количество привязок буферов и повышает эффективность передачи данных в GPU.
PixiJS поддерживает различные типы:
PIXI.TYPES.FLOATPIXI.TYPES.UNSIGNED_BYTEPIXI.TYPES.UNSIGNED_SHORTPIXI.TYPES.INTPIXI.TYPES.SHORTПример использования цветового атрибута:
geometry.addAttribute(
'aCustomColor',
new Uint8Array([
255, 0, 0, 255,
0, 255, 0, 255,
0, 0, 255, 255,
255, 255, 0, 255
]),
4,
true,
PIXI.TYPES.UNSIGNED_BYTE
);
Параметр normalized = true преобразует значения из
диапазона [0, 255] в [0.0, 1.0].
В шейдере:
attribute vec4 aCustomColor;
varying vec4 vColor;
void main(void)
{
vColor = aCustomColor;
}
Пользовательские атрибуты доступны только в вершинном шейдере. Для
передачи во фрагментный используется varying:
varying float vWeight;
attribute float aWeight;
void main(void)
{
vWeight = aWeight;
}
Во фрагментном:
varying float vWeight;
void main(void)
{
gl_FragColor = vec4(vec3(vWeight), 1.0);
}
WebGL имеет лимит на количество одновременно используемых атрибутов. Его можно получить:
renderer.gl.getParameter(renderer.gl.MAX_VERTEX_ATTRIBS);
Обычно доступно 8–16 атрибутов.
Создание пользовательского атрибута амплитуды:
const amplitudeBuffer = new PIXI.Buffer(new Float32Array([
0.0,
0.5,
1.0,
0.5
]), true);
geometry.addAttribute('aAmplitude', amplitudeBuffer, 1);
Вершинный шейдер:
attribute float aAmplitude;
uniform float uTime;
void main(void)
{
vec2 position = aVertexPosition;
position.y += sin(uTime + aAmplitude * 10.0) * 10.0;
vec3 transformed = projectionMatrix *
translationMatrix *
vec3(position, 1.0);
gl_Position = vec4(transformed.xy, 0.0, 1.0);
}
Обновление времени:
app.ticker.add((delta) => {
shader.uniforms.uTime += 0.1 * delta;
});
Такой подход переносит вычисления деформации на GPU, минимизируя нагрузку на CPU.
После настройки геометрии и шейдера создаётся
PIXI.Mesh:
const mesh = new PIXI.Mesh(geometry, shader);
app.stage.addChild(mesh);
Mesh связывает:
Типичные проблемы:
size и
vecX).stride или start.buffer.update().Для диагностики полезно:
Пользовательские атрибуты используются для:
Гибкость механизма атрибутов позволяет полностью переопределить поведение стандартных примитивов и реализовать сложные графические эффекты без изменения ядра PixiJS.