---
title: "Add a 3D model with babylon.js | JavaScript maps SDK"
description: "Add 3D models using custom layers and babylon.js."
---

> For AI agents: a documentation index is available at [/llms.txt](https://docs.maptiler.com/llms.txt). Markdown versions of all pages are available by appending .md to any URL.

# Add a 3D model with babylon.js

To add a 3D model to the map, employ a [custom style layer](https://docs.maptiler.com/sdk-js/api/properties/#customlayerinterface) in conjunction with [babylon.js](https://www.babylonjs.com/).

### NPM module setup

```bash
npm install --save @maptiler/sdk babylonjs babylonjs-loaders
```

#### main.js

```js
import { Map, MapStyle, config, MercatorCoordinate } from '@maptiler/sdk';
import '@maptiler/sdk/dist/maptiler-sdk.css';
import * as BABYLON from 'babylonjs';
import 'babylonjs-loaders';

config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
const map = (window.map = new Map({
    container: 'map',
    style: MapStyle.STREETS, 
    zoom: 18,
    center: [148.9819, -35.3981],
    pitch: 60,
    canvasContextAttributes: {antialias: true} // create the gl context with MSAA antialiasing, so custom layers are antialiased
}));

// World matrix parameters
const worldOrigin = [148.9819, -35.39847];
const worldAltitude = 0;

// MapTiler SDK JS default coordinate system (no rotations)
// +x east, -y north, +z up
//const worldRotate = [0, 0, 0];

// Babylon.js default coordinate system
// +x east, +y up, +z north
const worldRotate = [Math.PI / 2, 0, 0];

// Calculate mercator coordinates and scale
const worldOriginMercator = MercatorCoordinate.fromLngLat(
  worldOrigin,
  worldAltitude
);
const worldScale = worldOriginMercator.meterInMercatorCoordinateUnits();

// Calculate world matrix
const worldMatrix = BABYLON.Matrix.Compose(
  new BABYLON.Vector3(worldScale, worldScale, worldScale),
  BABYLON.Quaternion.FromEulerAngles(
    worldRotate[0],
    worldRotate[1],
    worldRotate[2]
  ),
  new BABYLON.Vector3(
    worldOriginMercator.x,
    worldOriginMercator.y,
    worldOriginMercator.z
  )
);

// configuration of the custom layer for a 3D model per the CustomLayerInterface
const customLayer = {
  id: '3d-model',
  type: 'custom',
  renderingMode: '3d',
  onAdd: function (map, gl) {
    this.engine = new BABYLON.Engine(
      gl,
      true,
      {
        useHighPrecisionMatrix: true // Important to prevent jitter at mercator scale
      },
      true
    );
    this.scene = new BABYLON.Scene(this.engine);
    this.scene.autoClear = false;
    this.scene.detachControl();

    this.scene.beforeRender = () => {
      this.engine.wipeCaches(true);
    };

    // create simple camera (will have its project matrix manually calculated)
    this.camera = new BABYLON.Camera(
      'Camera',
      new BABYLON.Vector3(0, 0, 0),
      this.scene
    );

    // create simple light
    const light = new BABYLON.HemisphericLight(
      'light1',
      new BABYLON.Vector3(0, 0, 100),
      this.scene
    );
    light.intensity = 0.7;

    // Add debug axes viewer, positioned at origin, 10 meter axis lengths
    new BABYLON.AxesViewer(this.scene, 10);

    // load GLTF model in to the scene
    BABYLON.SceneLoader.LoadAssetContainerAsync(
      'https://docs.maptiler.com/sdk-js/assets/34M_17/34M_17.gltf',
      '',
      this.scene
    ).then((modelContainer) => {
      modelContainer.addAllToScene();

      const rootMesh = modelContainer.createRootMesh();

      // If using MapTiler SDK JS coordinate system (+z up)
      //rootMesh.rotation.x = Math.PI/2

      // Create a second mesh
      const rootMesh2 = rootMesh.clone();

      // Position in babylon.js coordinate system
      rootMesh2.position.x = 25; // +east, meters
      rootMesh2.position.z = 25; // +north, meters
    });

    this.map = map;
  },
  render: function (gl, args) {
    const cameraMatrix = BABYLON.Matrix.FromArray(args.defaultProjectionData.mainMatrix);

    // world-view-projection matrix
    const wvpMatrix = worldMatrix.multiply(cameraMatrix);

    this.camera.freezeProjectionMatrix(wvpMatrix);

    this.scene.render(false);
    this.map.triggerRepaint();
  }
};

map.on('style.load', function () {
    map.addLayer(customLayer);
});
```

#### index.html

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="robots" content="noindex">
  <title>Add a 3D model with babylon.js</title>
  <link rel="stylesheet" href="./style.css">
</head>
<body>
  <div id="map"></div>
  <script type="module" src="./main.js"></script>
</body>
</html>
```

#### style.css

```css
body {margin: 0; padding: 0;}
#map {position: absolute; top: 0; bottom: 0; width: 100%;}
```

### Basic JavaScript setup

```html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Add a 3D model with babylon.js</title>
<script src="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.umd.min.js"></script>
<link href="https://cdn.maptiler.com/maptiler-sdk-js/v4.2.0/maptiler-sdk.css" rel="stylesheet" />
<style>
  body {margin: 0; padding: 0;}
  #map {position: absolute; top: 0; bottom: 0; width: 100%;}
</style>
</head>
<body>
<script src="https://unpkg.com/babylonjs@5.42.2/babylon.js"></script>
<script src="https://unpkg.com/babylonjs-loaders@5.42.2/babylonjs.loaders.min.js"></script>
<div id="map"></div>
<script>
  maptilersdk.config.apiKey = 'YOUR_MAPTILER_API_KEY_HERE';
  const map = (window.map = new maptilersdk.Map({
      container: 'map',
      style: maptilersdk.MapStyle.STREETS, 
      zoom: 18,
      center: [148.9819, -35.3981],
      pitch: 60,
      canvasContextAttributes: {antialias: true} // create the gl context with MSAA antialiasing, so custom layers are antialiased
  }));

  const BABYLON = window.BABYLON;

  // World matrix parameters
  const worldOrigin = [148.9819, -35.39847];
  const worldAltitude = 0;
  
  // MapTiler SDK JS default coordinate system (no rotations)
  // +x east, -y north, +z up
  //const worldRotate = [0, 0, 0];
  
  // Babylon.js default coordinate system
  // +x east, +y up, +z north
  const worldRotate = [Math.PI / 2, 0, 0];
  
  // Calculate mercator coordinates and scale
  const worldOriginMercator = maptilersdk.MercatorCoordinate.fromLngLat(
    worldOrigin,
    worldAltitude
  );
  const worldScale = worldOriginMercator.meterInMercatorCoordinateUnits();
  
  // Calculate world matrix
  const worldMatrix = BABYLON.Matrix.Compose(
    new BABYLON.Vector3(worldScale, worldScale, worldScale),
    BABYLON.Quaternion.FromEulerAngles(
      worldRotate[0],
      worldRotate[1],
      worldRotate[2]
    ),
    new BABYLON.Vector3(
      worldOriginMercator.x,
      worldOriginMercator.y,
      worldOriginMercator.z
    )
  );

  // configuration of the custom layer for a 3D model per the CustomLayerInterface
  const customLayer = {
    id: '3d-model',
    type: 'custom',
    renderingMode: '3d',
    onAdd: function (map, gl) {
      this.engine = new BABYLON.Engine(
        gl,
        true,
        {
          useHighPrecisionMatrix: true // Important to prevent jitter at mercator scale
        },
        true
      );
      this.scene = new BABYLON.Scene(this.engine);
      this.scene.autoClear = false;
      this.scene.detachControl();

      this.scene.beforeRender = () => {
        this.engine.wipeCaches(true);
      };

      // create simple camera (will have its project matrix manually calculated)
      this.camera = new BABYLON.Camera(
        'Camera',
        new BABYLON.Vector3(0, 0, 0),
        this.scene
      );

      // create simple light
      const light = new BABYLON.HemisphericLight(
        'light1',
        new BABYLON.Vector3(0, 0, 100),
        this.scene
      );
      light.intensity = 0.7;

      // Add debug axes viewer, positioned at origin, 10 meter axis lengths
      new BABYLON.AxesViewer(this.scene, 10);

      // load GLTF model in to the scene
      BABYLON.SceneLoader.LoadAssetContainerAsync(
        'https://docs.maptiler.com/sdk-js/assets/34M_17/34M_17.gltf',
        '',
        this.scene
      ).then((modelContainer) => {
        modelContainer.addAllToScene();

        const rootMesh = modelContainer.createRootMesh();

        // If using MapTiler SDK JS coordinate system (+z up)
        //rootMesh.rotation.x = Math.PI/2

        // Create a second mesh
        const rootMesh2 = rootMesh.clone();

        // Position in babylon.js coordinate system
        rootMesh2.position.x = 25; // +east, meters
        rootMesh2.position.z = 25; // +north, meters
      });

      this.map = map;
    },
    render: function (gl, args) {
      const cameraMatrix = BABYLON.Matrix.FromArray(args.defaultProjectionData.mainMatrix);

      // world-view-projection matrix
      const wvpMatrix = worldMatrix.multiply(cameraMatrix);

      this.camera.freezeProjectionMatrix(wvpMatrix);

      this.scene.render(false);
      this.map.triggerRepaint();
    }
  };

  map.on('style.load', function () {
      map.addLayer(customLayer);
  });
</script>
</body>
</html>
```

## Related examples

### [Add a 3D model with three.js](https://docs.maptiler.com/sdk-js/examples/add-3d-model/)

Examples

Add 3D models using custom layers and three.js.

### [Start with AR maps](https://docs.maptiler.com/sdk-js/examples/ar-control/)

Examples

Add an AR control button to map 3D viewports.

### [Display buildings in 3D](https://docs.maptiler.com/sdk-js/examples/3d-buildings/)

Examples

Use extrusions to display height of buildings in 3D.

### [3D terrain map](https://docs.maptiler.com/sdk-js/examples/3d-map/)

Tutorials

Create and display 3D terrain maps on web pages.
