Пользовательские слои в Mapbox GL JS реализуются через интерфейс CustomLayerInterface, который предоставляет прямой доступ к WebGL-контексту внутри рендер-пайплайна карты. Такой механизм используется, когда стандартных слоёв недостаточно: требуется интеграция нестандартной графики, 3D-рендеринга, шейдерных эффектов или внешних движков визуализации.
Custom layer в Mapbox GL JS — это объект, который внедряется в граф рендеринга карты и участвует в каждом кадре отрисовки. Он не является изолированным компонентом: слой получает доступ к состоянию карты, матрицам камеры и WebGL-контексту.
Минимальная структура пользовательского слоя включает следующие методы:
onAdd(map, gl)render(gl, matrix)onRemove(map, gl)id, type,
renderingModeКаждый из этих элементов интегрируется в жизненный цикл рендера.
Метод onAdd вызывается один раз при добавлении слоя в
карту. В этот момент доступен экземпляр карты и WebGL-контекст.
Типичная задача внутри onAdd:
Контекст WebGL передаётся напрямую, без абстракций, поэтому работа строится на уровне WebGL API.
const customLayer = {
id: 'custom-webgl-layer',
type: 'custom',
onAdd: function(map, gl) {
this.program = createShaderProgram(gl);
this.buffer = gl.createBuffer();
}
};
Метод render(gl, matrix) вызывается каждый кадр. В этот
момент слой обязан выполнить отрисовку с учётом текущей матрицы
камеры.
matrix — это 4x4 матрица преобразования, включающая:
Она обеспечивает синхронизацию пользовательской геометрии с картой.
Пример базового рендера:
render: function(gl, matrix) {
gl.useProgram(this.program);
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer);
gl.uniformMatrix4fv(
gl.getUniformLocation(this.program, "u_matrix"),
false,
matrix
);
gl.drawArrays(gl.TRIANGLES, 0, this.vertexCount);
}
Mapbox GL JS управляет WebGL состоянием глобально, поэтому пользовательский слой обязан аккуратно сохранять и восстанавливать параметры:
Игнорирование этого приводит к конфликтам с другими слоями карты.
Типовой паттерн:
render: function(gl, matrix) {
const depthTest = gl.getParameter(gl.DEPTH_TEST);
gl.enable(gl.DEPTH_TEST);
// draw custom geometry
if (!depthTest) {
gl.disable(gl.DEPTH_TEST);
}
}
Custom layer поддерживает два режима рендеринга:
"2d" — рендеринг в пространстве экрана"3d" — рендеринг в географическом пространстве
картыВ 3D-режиме геометрия должна быть преобразована с учётом координат карты (longitude, latitude, altitude). В этом случае matrix уже содержит геопривязку.
const layer = {
id: 'geo-layer',
type: 'custom',
renderingMode: '3d'
};
В 3D режиме важно учитывать:
Одно из наиболее частых применений custom layers — интеграция с Three.js. В этом случае Mapbox GL JS передаёт WebGL-контекст в сцену Three.js.
Типовой подход:
onAdd: function(map, gl) {
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl
});
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera();
}
render:
render: function(gl, matrix) {
this.camera.projectionMatrix = new THREE.Matrix4().fromArray(matrix);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
}
Критически важный момент — отсутствие конфликта управления WebGL state между Mapbox и Three.js.
Custom layers напрямую используют vertex buffers:
gl.createBuffer()gl.bufferData()gl.vertexAttribPointer()Пример подготовки геометрии:
const vertices = new Float32Array([
0, 0, 0,
1, 0, 0,
0, 1, 0
]);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
В рендере:
const positionLocation = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
Mapbox GL JS предоставляет данные о состоянии карты:
Эти параметры используются для динамической адаптации визуализации.
const zoom = map.getZoom();
const center = map.getCenter();
В custom layer часто реализуется:
Custom layers работают внутри основного render loop, поэтому их производительность напрямую влияет на FPS карты.
Ключевые ограничения:
Оптимизация обычно включает:
Custom layer полностью контролирует shader pipeline:
Пример vertex shader:
attribute vec3 a_position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * vec4(a_position, 1.0);
}
Fragment shader:
precision mediump float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
Шейдеры компилируются вручную через WebGL API, без абстракций.
Метод onRemove отвечает за освобождение памяти:
onRemove: function(map, gl) {
gl.deleteBuffer(this.buffer);
gl.deleteProgram(this.program);
}
Игнорирование очистки приводит к утечкам памяти в долгоживущих приложениях.
Custom layers применяются для:
Каждый сценарий требует строгого контроля WebGL pipeline и синхронизации с матрицами карты.
Custom layer имеет позицию в порядке слоёв. Он может быть размещён:
Порядок влияет на визуальную композицию сцены, но не изолирует WebGL state, поэтому конфликты возможны при неправильной архитектуре рендера.