Интеграция Mapbox GL JS и Three.js основана на совместном использовании WebGL-контекста и синхронизации двух независимых сценографических систем: картографической сцены Mapbox и 3D-сцены Three.js. Mapbox GL JS управляет рендерингом тайлов, векторных слоёв и камерой карты, тогда как Three.js отвечает за произвольную 3D-графику, освещение и сложные геометрии.
Ключевым механизмом объединения выступает интерфейс пользовательского
слоя CustomLayerInterface, позволяющий внедрять сторонний
WebGL-рендеринг внутрь пайплайна Mapbox.
Mapbox GL JS использует WebGL для отрисовки карты через собственный контекст. Three.js по умолчанию создаёт собственный WebGLRenderer, что приводит к конфликту контекстов при прямом объединении.
Корректная интеграция строится на принципе:
Mapbox GL JS использует проекцию Web Mercator (EPSG:3857), где:
Для интеграции Three.js используется преобразование координат:
lngLat → Mercator coordinateMapbox предоставляет утилиту:
mapboxgl.MercatorCoordinate.fromLngLat([lng, lat], altitude)
Результатом является объект с нормализованными координатами
x, y, z, совместимыми с 3D
сценой.
Mapbox GL JS определяет структуру пользовательского слоя через интерфейс:
onAddrenderonRemoveЭтот слой внедряется в pipeline рендеринга карты и вызывается синхронно с каждым кадром.
Основная задача — переиспользование WebGL context карты:
const customLayer = {
id: 'three-layer',
type: 'custom',
renderingMode: '3d',
onAdd: function (map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
}
};
Важный аспект заключается в передаче canvas и
context из Mapbox в Three.js renderer. Это исключает
создание второго WebGL-контекста.
Mapbox управляет собственной матрицей камеры, которую необходимо синхронизировать с Three.js:
modelMatrix — трансформация объектов в мировом
пространствеprojectionMatrix — матрица проекции камеры Mapboxrender: function (gl, matrix) {
const m = new THREE.Matrix4().fromArray(matrix);
this.camera.projectionMatrix = m;
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
}
Matrix, передаваемая Mapbox, уже содержит итоговую комбинацию view-projection, что позволяет использовать её напрямую.
Добавление объектов в сцену требует конвертации координат:
const mercator = mapboxgl.MercatorCoordinate.fromLngLat(
[30.5234, 50.4501],
0
);
const object = new THREE.Mesh(
new THREE.BoxGeometry(1000, 1000, 1000),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
object.position.set(mercator.x, mercator.y, mercator.z);
Для масштабирования используется коэффициент:
const scale = mercator.meterInMercatorCoordinateUnits();
object.scale.setScalar(scale);
Это позволяет согласовать реальные метры с системой координат карты.
Mapbox активно использует depth buffer для тайлов и слоёв. При добавлении Three.js сцены важно управлять состоянием WebGL:
this.renderer.autoClear = false;
render: function (gl, matrix) {
this.renderer.clearDepth();
this.renderer.render(this.scene, this.camera);
}
Без очистки depth buffer возможны артефакты перекрытия слоёв.
Также важно сбрасывать состояние WebGL:
this.renderer.state.reset();
Three.js сцена существует в том же пространстве, что и карта, поэтому освещение требует согласования:
DirectionalLight для имитации
солнечного светаconst light = new THREE.DirectionalLight(0xffffff, 1.0);
light.position.set(0, -70, 100);
this.scene.add(light);
Для повышения реалистичности применяется AmbientLight с
низкой интенсивностью.
Mapbox управляет циклом рендеринга, поэтому Three.js не должен
запускать собственный requestAnimationFrame.
Обновление логики выполняется внутри render:
render: function (gl, matrix) {
this.cube.rotation.z += 0.01;
const m = new THREE.Matrix4().fromArray(matrix);
this.camera.projectionMatrix = m;
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
}
Таким образом Three.js полностью подчинён циклу Mapbox.
Mapbox GL JS использует нелинейную систему масштабирования в зависимости от zoom level. Для стабильного поведения объектов применяется компенсация масштаба:
Это предотвращает визуальные искажения при приближении карты.
При интеграции WebGL сцен критичны следующие аспекты:
renderer.state.reset()Дополнительно эффективным является разделение сцены на статическую и динамическую части.
Mapbox GL JS и Three.js используют общий depth buffer, поэтому порядок отрисовки строго контролируется:
При необходимости отключается depth test:
this.renderer.getContext().disable(this.renderer.getContext().DEPTH_TEST);
При сложных сценах может использоваться:
Raycasting требует обратного преобразования:
map.unproject([x, y]);
Web Mercator в больших масштабах приводит к проблемам точности float32. Для их компенсации применяется:
События Mapbox:
могут быть преобразованы в Three.js raycasting через:
Это обеспечивает интерактивность 3D объектов внутри карты.
Mapbox GL JS полностью контролирует жизненный цикл WebGL контекста:
Three.js renderer должен работать в режиме внешнего контекста без попыток его пересоздания.