Instanced rendering — это техника отрисовки большого количества однотипных объектов с использованием одного набора геометрии и одного draw-вызова. В основе лежит возможность GPU применять разные атрибуты к каждой «инстанции» (экземпляру) геометрии без повторной передачи вершинных данных.
В WebGL 1 instancing реализуется через расширение
ANGLE_instanced_arrays, в WebGL 2 — встроенными
функциями:
gl.drawArraysInstancedgl.drawElementsInstancedgl.vertexAttribDivisorВ стандартном пайплайне PixiJS instancing применяется ограниченно. Для нестандартных задач (десятки тысяч однотипных спрайтов, частиц, тайлов) эффективнее реализовать instanced rendering вручную через низкоуровневый API PixiJS.
Классическая схема без instancing:
для каждого объекта:
bind geometry
bind uniforms
draw
С instancing:
bind geometry один раз
bind instance attributes
drawInstanced(количество_инстанций)
Данные делятся на два типа:
Per-vertex данные (общие для всех экземпляров)
Per-instance данные
GPU повторно использует вершинный буфер, меняя только атрибуты инстанций.
Для ручной реализации instancing используются:
PIXI.GeometryPIXI.BufferPIXI.ShaderPIXI.Meshrenderer.glПример создания кастомной геометрии:
const geometry = new PIXI.Geometry()
.addAttribute('aVertexPosition', vertices, 2)
.addAttribute('aUV', uvs, 2)
.addIndex(indices);
Для instancing потребуется дополнительный буфер с divisor.
Instanced-атрибут отличается тем, что обновляется один раз на экземпляр, а не на вершину.
const instanceData = new Float32Array(maxInstances * 4);
// например: x, y, scale, rotation
const instanceBuffer = new PIXI.Buffer(instanceData, false, false);
geometry.addAttribute(
'aInstanceData',
instanceBuffer,
4, // размер атрибута
false,
PIXI.TYPES.FLOAT,
0,
0,
true // <-- instanced
);
Последний параметр true включает режим instancing внутри
PixiJS.
Внутри PixiJS это приводит к вызову
vertexAttribDivisor.
Пример GLSL-шейдера:
precision mediump float;
attribute vec2 aVertexPosition;
attribute vec2 aUV;
attribute vec4 aInstanceData; // x, y, scale, rotation
uniform mat3 projectionMatrix;
varying vec2 vUV;
void main() {
float x = aInstanceData.x;
float y = aInstanceData.y;
float scale = aInstanceData.z;
float rotation = aInstanceData.w;
vec2 scaled = aVertexPosition * scale;
float cosR = cos(rotation);
float sinR = sin(rotation);
vec2 rotated = vec2(
scaled.x * cosR - scaled.y * sinR,
scaled.x * sinR + scaled.y * cosR
);
vec2 worldPosition = rotated + vec2(x, y);
gl_Position = vec4((projectionMatrix * vec3(worldPosition, 1.0)).xy, 0.0, 1.0);
vUV = aUV;
}
Ключевая особенность: геометрия остаётся неизменной, а трансформация берётся из атрибутов экземпляра.
precision mediump float;
varying vec2 vUV;
uniform sampler2D uTexture;
void main() {
gl_FragColor = texture2D(uTexture, vUV);
}
По умолчанию PIXI.Mesh не вызывает
drawInstanced. Необходимо вмешательство через
renderer.geometry.
mesh._render = function(renderer) {
const gl = renderer.gl;
renderer.shader.bind(this.shader);
renderer.geometry.bind(this.geometry);
const ext = renderer.geometry.hasInstance
? renderer.gl.getExtension('ANGLE_instanced_arrays')
: null;
if (ext) {
ext.drawElementsInstancedANGLE(
gl.TRIANGLES,
this.geometry.getIndex().data.length,
gl.UNSIGNED_SHORT,
0,
instanceCount
);
}
};
В WebGL2:
gl.drawElementsInstanced(
gl.TRIANGLES,
indexCount,
gl.UNSIGNED_SHORT,
0,
instanceCount
);
При изменении параметров экземпляров необходимо:
instanceBuffer.update();
Если используется большой массив:
buffer.update(data, offset)Вместо:
[x, y, scale, rotation]
можно использовать:
mat3 или mat4
Преимущества:
Недостатки:
Оптимизация через:
UNSIGNED_BYTEHALF_FLOATПример:
geometry.addAttribute(
'aColor',
colorBuffer,
4,
true,
PIXI.TYPES.UNSIGNED_BYTE,
0,
0,
true
);
Вершинный шейдер:
attribute vec4 aColor;
varying vec4 vColor;
void main() {
vColor = aColor;
}
Фрагментный:
varying vec4 vColor;
void main() {
gl_FragColor = texture2D(uTexture, vUV) * vColor;
}
При 50 000–200 000 экземпляров важно учитывать:
gl.MAX_VERTEX_ATTRIBSInstancing хорошо сочетается с текстурным атласом.
Дополнительный атрибут:
aFrame: vec4 (u0, v0, u1, v1)
Вершинный шейдер:
attribute vec4 aFrame;
vUV = mix(aFrame.xy, aFrame.zw, aUV);
Это позволяет каждому экземпляру использовать свой фрейм, не меняя текстуру и не создавая новые draw-вызовы.
При необходимости сортировки прозрачных объектов instancing может потребовать разделения на несколько проходов.
| Sprite batching | Instanced rendering |
|---|---|
| Управляется Pixi автоматически | Реализуется вручную |
| Работает только со спрайтами | Работает с любой геометрией |
| Ограничен внутренней архитектурой | Полный контроль |
| Меньше гибкости | Максимальная гибкость |
Instancing предпочтителен для:
| WebGL1 | WebGL2 |
|---|---|
| Требуется ANGLE_instanced_arrays | Встроенная поддержка |
| Нет UBO | Есть UBO |
| Нет texture buffer | Есть texture buffer |
| Ограниченная производительность | Более эффективен |
PixiJS автоматически выбирает контекст, но ручная реализация должна учитывать версию.
Передача:
Анимация выполняется полностью в шейдере.
В WebGL2 возможно хранить параметры экземпляров в текстуре и читать
их через texelFetch.
Преимущества:
Instancing снижает:
При 100 000 объектов:
GPU становится главным ограничением, а не CPU.
CPU обрабатывает только обновление массива параметров. GPU выполняет всю трансформацию и окрашивание.
Такой подход позволяет достигать высокой частоты кадров даже при экстремальном количестве объектов на сцене.
Instanced rendering вручную в PixiJS открывает доступ к полноценному низкоуровневому контролю над WebGL, превращая библиотеку из 2D-фреймворка в гибкую GPU-платформу для высоконагруженной графики.