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
npm install --save @maptiler/geosplats
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();
});
<!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>
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;
}
Configure terrain blending
You can enable or disable terrain blending dynamically at any time using the setTerrainBlending method on a SplatModel instance:
// 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 optionalTerrainBlendingParamsobject 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 to explore all available configuration options.
Related examples
Add splat model
ExamplesLearn 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
ExamplesLearn how to enable the model camera collision. Sets whether the camera movement is restricted by model.
Opacity and visibility
ExamplesLearn how to dynamically control the transparency and visibility of photorealistic splats objects using the MapTiler GeoSplats SDK.
Edit model in custom editor
ExamplesBuild a custom 3D editor to adjust the position, rotation, and scale of splat models and save their properties to a database.