---
title: "Blend splat models with 3D terrain | Geosplats sdk"
description: "Learn how to blend 3D splat models with terrain."
---

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

# Blend splat models with 3D terrain

This example shows how to blend a **splat model** with the surrounding **3D terrain**. Terrain blending smoothly integrates photorealistic Gaussian splats into digital elevation models (DEM), eliminating sharp boundaries and clipping artifacts between the captured model and the map’s elevation surface.

Use the dropdown menu in the top-left corner to:

-   Activate or deactivate terrain blending dynamically with the toggle button.
-   Adjust the blending **intensity** (vertical displacement) with the intensity slider.
-   Fine-tune the **smoothness** (transition band width) with the smoothness slider.

### NPM module setup

```bash
npm install --save @maptiler/geosplats
```

#### main.js

```js
import { SplatModel, Map, config } from "@maptiler/geosplats";
import "@maptiler/geosplats/dist/maptiler-geosplats.css";

config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";
const map = new Map({
  apiKey: config.apiKey,
  container: "map",
  center: [-60.04487, -18.1391],
  zoom: 17.5,
  pitch: 60,
});

let splatModel;
let isBlendingEnabled = true;

const dropdownToggle = document.getElementById("dropdown-toggle");
const dropdownPanel = document.getElementById("dropdown-panel");
const btnToggleBlend = document.getElementById("btn-toggle-blend");
const blendControls = document.getElementById("blend-controls");
const intensitySlider = document.getElementById("intensity-slider");
const smoothnessSlider = document.getElementById("smoothness-slider");
const intensityVal = document.getElementById("intensity-val");
const smoothnessVal = document.getElementById("smoothness-val");

dropdownToggle.addEventListener("click", () => {
  const isCollapsed = dropdownPanel.classList.toggle("collapsed");
  dropdownToggle.classList.toggle("collapsed", isCollapsed);
  dropdownToggle.setAttribute("aria-expanded", !isCollapsed);
});

function applyTerrainBlending() {
  if (!splatModel) return;
  const intensity = Math.max(0.001, parseFloat(intensitySlider.value));
  const smoothness = Math.max(0.001, parseFloat(smoothnessSlider.value));
  splatModel.setTerrainBlending(isBlendingEnabled, {
    terrainDisplacement: intensity,
    terrainSmoothness: smoothness,
  });
}

btnToggleBlend.addEventListener("click", () => {
  isBlendingEnabled = !isBlendingEnabled;
  if (isBlendingEnabled) {
    btnToggleBlend.textContent = "Deactivate terrain blending";
    btnToggleBlend.classList.remove("inactive");
    btnToggleBlend.classList.add("active");
    blendControls.classList.remove("disabled");
    intensitySlider.disabled = false;
    smoothnessSlider.disabled = false;
  } else {
    btnToggleBlend.textContent = "Activate terrain blending";
    btnToggleBlend.classList.remove("active");
    btnToggleBlend.classList.add("inactive");
    blendControls.classList.add("disabled");
    intensitySlider.disabled = true;
    smoothnessSlider.disabled = true;
  }
  applyTerrainBlending();
});

intensitySlider.addEventListener("input", (e) => {
  intensityVal.textContent = parseFloat(e.target.value).toFixed(2);
  if (isBlendingEnabled) {
    applyTerrainBlending();
  }
});

smoothnessSlider.addEventListener("input", (e) => {
  smoothnessVal.textContent = parseFloat(e.target.value).toFixed(2);
  if (isBlendingEnabled) {
    applyTerrainBlending();
  }
});

map.on("load", () => {
  splatModel = new SplatModel({
    model: "019e646c-153e-7b9e-9004-c1a3297f202a",
  });
  map.addSplatModel(splatModel);
});

map.once("splatmodel:load", () => {
  splatModel.fit();
});
```

#### index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="robots" content="noindex" />
    <title>Terrain Blending</title>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <body>
    <div id="map"></div>

    <div id="terrain-blending-ui">
      <button class="dropdown-toggle-btn" id="dropdown-toggle" type="button" aria-expanded="true">
        <span>Terrain Blending</span>
        <svg class="dropdown-arrow" viewBox="0 0 24 24">
          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
        </svg>
      </button>
      <div class="dropdown-panel" id="dropdown-panel">
        <button id="btn-toggle-blend" class="btn-toggle-blend active" type="button">
          Deactivate terrain blending
        </button>
        <div id="blend-controls" class="blend-controls">
          <div class="control-group">
            <div class="control-label-row">
              <label for="intensity-slider">Intensity</label>
              <span id="intensity-val" class="control-value">0.97</span>
            </div>
            <input type="range" id="intensity-slider" min="0" max="2" step="0.01" value="0.97" />
          </div>
          <div class="control-group">
            <div class="control-label-row">
              <label for="smoothness-slider">Smoothness</label>
              <span id="smoothness-val" class="control-value">1.00</span>
            </div>
            <input type="range" id="smoothness-slider" min="0" max="1" step="0.01" value="1.00" />
          </div>
        </div>
      </div>
    </div>
    <script type="module" src="./main.js"></script>
  </body>
</html>
```

#### style.css

```css
body {
  font-family: Inter, sans-serif;
  margin: 0;
  overflow: hidden;
}

#map {
  width: 100%;
  height: 100vh;
}

#terrain-blending-ui {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 1000;
  font-family:
    Inter,
    -apple-system,
    BlinkMacSystemFont,
    "Segoe UI",
    Roboto,
    sans-serif;
  user-select: none;
}

.dropdown-toggle-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  background: rgba(255, 255, 255, 0.95);
  color: #1a1a1a;
  border: 1px solid rgba(0, 0, 0, 0.15);
  border-radius: 6px;
  padding: 8px 12px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
  transition:
    background-color 0.2s,
    border-color 0.2s;
  min-width: 160px;
}

.dropdown-toggle-btn:hover {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.3);
}

.dropdown-arrow {
  width: 18px;
  height: 18px;
  fill: #555;
  transition: transform 0.25s ease;
}

.dropdown-toggle-btn.collapsed .dropdown-arrow {
  transform: rotate(-90deg);
}

.dropdown-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 250px;
  background: rgba(255, 255, 255, 0.96);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 8px;
  padding: 14px;
  margin-top: 6px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}

.dropdown-panel.collapsed {
  display: none;
}

.btn-toggle-blend {
  width: 100%;
  padding: 8px 12px;
  border: none;
  border-radius: 5px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.2s ease;
  color: #ffffff;
}

.btn-toggle-blend.active {
  background-color: #dc3545;
}

.btn-toggle-blend.active:hover {
  background-color: #bb2d3b;
}

.btn-toggle-blend.inactive {
  background-color: #3174ff;
}

.btn-toggle-blend.inactive:hover {
  background-color: #1e5fe3;
}

.blend-controls {
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: opacity 0.2s ease;
}

.blend-controls.disabled {
  opacity: 0.4;
  pointer-events: none;
}

.control-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.control-label-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 11px;
  font-weight: 600;
  color: #333;
}

.control-value {
  font-family: monospace;
  font-size: 11px;
  color: #555;
  background: #eee;
  padding: 1px 5px;
  border-radius: 3px;
}

input[type="range"] {
  width: 100%;
  height: 6px;
  margin: 2px 0;
  cursor: pointer;
}
```

### Basic JavaScript setup

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Terrain Blending</title>
    <link
      rel="stylesheet"
      href="https://cdn.maptiler.com/maptiler-geosplats/v1.1.2/maptiler-geosplats.css"
    />
    <style>
      body {
        font-family: Inter, sans-serif;
        margin: 0;
        overflow: hidden;
      }

      #map {
        width: 100%;
        height: 100vh;
      }

      #terrain-blending-ui {
        position: absolute;
        top: 10px;
        left: 10px;
        z-index: 1000;
        font-family:
          Inter,
          -apple-system,
          BlinkMacSystemFont,
          "Segoe UI",
          Roboto,
          sans-serif;
        user-select: none;
      }

      .dropdown-toggle-btn {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        background: rgba(255, 255, 255, 0.95);
        color: #1a1a1a;
        border: 1px solid rgba(0, 0, 0, 0.15);
        border-radius: 6px;
        padding: 8px 12px;
        font-size: 13px;
        font-weight: 600;
        cursor: pointer;
        box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
        transition:
          background-color 0.2s,
          border-color 0.2s;
        min-width: 160px;
      }

      .dropdown-toggle-btn:hover {
        background: #ffffff;
        border-color: rgba(0, 0, 0, 0.3);
      }

      .dropdown-arrow {
        width: 18px;
        height: 18px;
        fill: #555;
        transition: transform 0.25s ease;
      }

      .dropdown-toggle-btn.collapsed .dropdown-arrow {
        transform: rotate(-90deg);
      }

      .dropdown-panel {
        display: flex;
        flex-direction: column;
        gap: 12px;
        width: 250px;
        background: rgba(255, 255, 255, 0.96);
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
        border: 1px solid rgba(0, 0, 0, 0.12);
        border-radius: 8px;
        padding: 14px;
        margin-top: 6px;
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
      }

      .dropdown-panel.collapsed {
        display: none;
      }

      .btn-toggle-blend {
        width: 100%;
        padding: 8px 12px;
        border: none;
        border-radius: 5px;
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
        transition: background-color 0.2s ease;
        color: #ffffff;
      }

      .btn-toggle-blend.active {
        background-color: #dc3545;
      }

      .btn-toggle-blend.active:hover {
        background-color: #bb2d3b;
      }

      .btn-toggle-blend.inactive {
        background-color: #3174ff;
      }

      .btn-toggle-blend.inactive:hover {
        background-color: #1e5fe3;
      }

      .blend-controls {
        display: flex;
        flex-direction: column;
        gap: 10px;
        transition: opacity 0.2s ease;
      }

      .blend-controls.disabled {
        opacity: 0.4;
        pointer-events: none;
      }

      .control-group {
        display: flex;
        flex-direction: column;
        gap: 4px;
      }

      .control-label-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-size: 11px;
        font-weight: 600;
        color: #333;
      }

      .control-value {
        font-family: monospace;
        font-size: 11px;
        color: #555;
        background: #eee;
        padding: 1px 5px;
        border-radius: 3px;
      }

      input[type="range"] {
        width: 100%;
        height: 6px;
        margin: 2px 0;
        cursor: pointer;
      }
    </style>
  </head>

  <body>
    <div id="map"></div>

    <div id="terrain-blending-ui">
      <button class="dropdown-toggle-btn" id="dropdown-toggle" type="button" aria-expanded="true">
        <span>Terrain Blending</span>
        <svg class="dropdown-arrow" viewBox="0 0 24 24">
          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" />
        </svg>
      </button>
      <div class="dropdown-panel" id="dropdown-panel">
        <button id="btn-toggle-blend" class="btn-toggle-blend active" type="button">
          Deactivate terrain blending
        </button>
        <div id="blend-controls" class="blend-controls">
          <div class="control-group">
            <div class="control-label-row">
              <label for="intensity-slider">Intensity</label>
              <span id="intensity-val" class="control-value">0.97</span>
            </div>
            <input type="range" id="intensity-slider" min="0" max="2" step="0.01" value="0.97" />
          </div>
          <div class="control-group">
            <div class="control-label-row">
              <label for="smoothness-slider">Smoothness</label>
              <span id="smoothness-val" class="control-value">1.00</span>
            </div>
            <input type="range" id="smoothness-slider" min="0" max="1" step="0.01" value="1.00" />
          </div>
        </div>
      </div>
    </div>

    <script type="module">
      import {
        SplatModel,
        Map,
        config,
      } from "https://cdn.maptiler.com/maptiler-geosplats/v1.1.2/maptiler-geosplats.mjs";

      config.apiKey = "YOUR_MAPTILER_API_KEY_HERE";
      const map = new Map({
        apiKey: config.apiKey,
        container: "map",
        center: [-60.04487, -18.1391],
        zoom: 17.5,
        pitch: 60,
      });

      let splatModel;
      let isBlendingEnabled = true;

      const dropdownToggle = document.getElementById("dropdown-toggle");
      const dropdownPanel = document.getElementById("dropdown-panel");
      const btnToggleBlend = document.getElementById("btn-toggle-blend");
      const blendControls = document.getElementById("blend-controls");
      const intensitySlider = document.getElementById("intensity-slider");
      const smoothnessSlider = document.getElementById("smoothness-slider");
      const intensityVal = document.getElementById("intensity-val");
      const smoothnessVal = document.getElementById("smoothness-val");

      dropdownToggle.addEventListener("click", () => {
        const isCollapsed = dropdownPanel.classList.toggle("collapsed");
        dropdownToggle.classList.toggle("collapsed", isCollapsed);
        dropdownToggle.setAttribute("aria-expanded", !isCollapsed);
      });

      function applyTerrainBlending() {
        if (!splatModel) return;
        const intensity = Math.max(0.001, parseFloat(intensitySlider.value));
        const smoothness = Math.max(0.001, parseFloat(smoothnessSlider.value));
        splatModel.setTerrainBlending(isBlendingEnabled, {
          terrainDisplacement: intensity,
          terrainSmoothness: smoothness,
        });
      }

      btnToggleBlend.addEventListener("click", () => {
        isBlendingEnabled = !isBlendingEnabled;
        if (isBlendingEnabled) {
          btnToggleBlend.textContent = "Deactivate terrain blending";
          btnToggleBlend.classList.remove("inactive");
          btnToggleBlend.classList.add("active");
          blendControls.classList.remove("disabled");
          intensitySlider.disabled = false;
          smoothnessSlider.disabled = false;
        } else {
          btnToggleBlend.textContent = "Activate terrain blending";
          btnToggleBlend.classList.remove("active");
          btnToggleBlend.classList.add("inactive");
          blendControls.classList.add("disabled");
          intensitySlider.disabled = true;
          smoothnessSlider.disabled = true;
        }
        applyTerrainBlending();
      });

      intensitySlider.addEventListener("input", (e) => {
        intensityVal.textContent = parseFloat(e.target.value).toFixed(2);
        if (isBlendingEnabled) {
          applyTerrainBlending();
        }
      });

      smoothnessSlider.addEventListener("input", (e) => {
        smoothnessVal.textContent = parseFloat(e.target.value).toFixed(2);
        if (isBlendingEnabled) {
          applyTerrainBlending();
        }
      });

      map.on("load", () => {
        splatModel = new SplatModel({
          model: "019e646c-153e-7b9e-9004-c1a3297f202a",
        });
        map.addSplatModel(splatModel);
      });

      map.once("splatmodel:load", () => {
        splatModel.fit();
      });
    </script>
  </body>
</html>
```

## Configure terrain blending

You can enable or disable terrain blending dynamically at any time using the [`setTerrainBlending`](https://docs-qmgv.pages.dev/geosplats-sdk/api/classes/SplatModel/#setterrainblending) method on a `SplatModel` instance:

```javascript
// Disable terrain blending
splatModel.setTerrainBlending(false);

// Enable terrain blending with custom intensity and smoothness
splatModel.setTerrainBlending(true, {
  terrainDisplacement: 0.97,
  terrainSmoothness: 1.0,
});
```

The method accepts two arguments:

-   `enabled`: A boolean indicating whether terrain blending is active.
-   `params`: An optional [`TerrainBlendingParams`](https://docs-qmgv.pages.dev/geosplats-sdk/api/interfaces/TerrainBlendingParams/) object with the following properties:
    -   `terrainDisplacement`: Controls the displacement factor and elevation alignment between the splat model and the terrain mesh.
    -   `terrainSmoothness`: Controls the width and softness of the transition boundary between the splats and the underlying terrain surface.

## Learn more

Check out the [setTerrainBlending method in the MapTiler GeoSplats SDK reference](https://docs-qmgv.pages.dev/geosplats-sdk/api/classes/SplatModel/#setterrainblending) to explore all available configuration options.

## Related examples

### [Add splat model](https://docs-qmgv.pages.dev/geosplats-sdk/examples/add-geosplats/)

Examples

Learn how to load a map using the GeoSplats SDK and add a photorealistic splat model. A splat model offer a modern alternative to 3D meshes, perfect for complex scenes.

### [Model camera collision](https://docs-qmgv.pages.dev/geosplats-sdk/examples/camera-collision/)

Examples

Learn how to enable the model camera collision. Sets whether the camera movement is restricted by model.

### [Opacity and visibility](https://docs-qmgv.pages.dev/geosplats-sdk/examples/opacity-visibility/)

Examples

Learn how to dynamically control the transparency and visibility of photorealistic splats objects using the MapTiler GeoSplats SDK.

### [Edit model in custom editor](https://docs-qmgv.pages.dev/geosplats-sdk/examples/custom-editor/)

Examples

Build a custom 3D editor to adjust the position, rotation, and scale of splat models and save their properties to a database.
