Sky, Fog, and Terrain
Combine 3D terrain, a sky layer, and atmospheric fog to create a realistic, immersive landscape. Adjust the sky colors and blend values live using the controls below.
No external libraries needed. Sky, fog, and terrain are all built into MapMetrics GL.
The Three Layers Explained
Think of these as three separate things stacked on top of each other:
☁️ Sky → paints the area above the horizon (blue sky, sunrise colors)
🌫️ Fog → adds haze to distant mountains and the horizon
🏔️ Terrain → pushes the land surface into 3DSky Properties
The sky style property controls the background above the horizon. Use map.setSky() to update it at runtime:
| Property | What it controls |
|---|---|
sky-color | The main sky color (top of the sky) |
horizon-color | Color at the horizon line |
fog-color | Sky color blending into the fog zone |
sky-horizon-blend | 0 = sharp sky edge · 1 = gradual blend into horizon |
horizon-fog-blend | 0 = sharp horizon · 1 = smooth blend into fog zone |
fog-ground-blend | 0 = fog starts high · 1 = fog blends all the way to ground |
javascript
// Set initial sky in the style
style: {
sky: {
'sky-color': '#199EF3',
'sky-horizon-blend': 0.5,
'horizon-color': '#fbe4ff',
'horizon-fog-blend': 0.5,
'fog-color': '#ffffff',
'fog-ground-blend': 0.5,
}
}
// Update sky at runtime
map.setSky({
'sky-color': '#ff6b35', // sunset orange
'horizon-color': '#ffd700',
'fog-color': '#ff8c69',
});Fog Properties
The fog property adds atmospheric haze. Toggle it with map.setFog():
javascript
// Enable fog
map.setFog({
color: '#ffffff', // fog color at ground level
'high-color': '#245cdf', // fog color at altitude
'horizon-blend': 0.05, // how fast fog kicks in near horizon
'space-color': '#000000', // outer space color (globe view)
'star-intensity': 0.15, // star brightness in space zone
});
// Disable fog
map.setFog(null);Preset Sky Themes
| Theme | sky-color | horizon-color | fog-color |
|---|---|---|---|
| Day | #199EF3 | #fbe4ff | #ffffff |
| Sunset | #ff6b35 | #ffd700 | #ff8c69 |
| Night | #0a0a2e | #1a1a4e | #0d0d2b |
Complete Example
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
<style>
body { margin: 0; }
#map { height: 100vh; width: 100%; }
#controls { position: absolute; top: 10px; left: 10px; background: white; padding: 12px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.2); font-size: 13px; min-width: 280px; }
.row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; gap: 8px; }
.row label { flex: 1; color: #374151; }
.row input[type=range] { flex: 1; }
.row span { min-width: 30px; text-align: right; color: #6b7280; }
.btns { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
button { padding: 6px 12px; border: none; border-radius: 6px; cursor: pointer; color: white; font-size: 12px; }
</style>
</head>
<body>
<div id="map"></div>
<div id="controls">
<div class="row"><label>sky-color</label><input id="sky-color" type="color" value="#199EF3" /></div>
<div class="row"><label>horizon-color</label><input id="horizon-color" type="color" value="#fbe4ff" /></div>
<div class="row"><label>fog-color</label><input id="fog-color" type="color" value="#ffffff" /></div>
<div class="row"><label>sky-horizon-blend</label><input id="sky-horizon-blend" type="range" min="0" max="1" step="0.01" value="0.5" /><span id="v-shb">0.50</span></div>
<div class="row"><label>horizon-fog-blend</label><input id="horizon-fog-blend" type="range" min="0" max="1" step="0.01" value="0.5" /><span id="v-hfb">0.50</span></div>
<div class="row"><label>fog-ground-blend</label><input id="fog-ground-blend" type="range" min="0" max="1" step="0.01" value="0.5" /><span id="v-fgb">0.50</span></div>
<div class="btns">
<button id="fog-on" style="background:#3b82f6;">Fog ON</button>
<button id="fog-off" style="background:#6b7280;">Fog OFF</button>
<button id="preset-day" style="background:#0ea5e9;">Day</button>
<button id="preset-sunset" style="background:#f97316;">Sunset</button>
<button id="preset-night" style="background:#1e1b4b;">Night</button>
</div>
</div>
<script>
const map = new mapmetricsgl.Map({
container: 'map',
zoom: 10,
center: [11.39085, 47.27574],
pitch: 65,
maxPitch: 85,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap Contributors',
maxzoom: 19,
},
terrainSource: {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
tileSize: 256,
encoding: 'terrarium',
maxzoom: 15,
},
},
layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
terrain: { source: 'terrainSource', exaggeration: 1.5 },
sky: {
'sky-color': '#199EF3',
'sky-horizon-blend': 0.5,
'horizon-color': '#fbe4ff',
'horizon-fog-blend': 0.5,
'fog-color': '#ffffff',
'fog-ground-blend': 0.5,
},
fog: { color: '#ffffff', 'high-color': '#245cdf', 'horizon-blend': 0.05 },
},
});
map.addControl(new mapmetricsgl.NavigationControl({ visualizePitch: true }), 'top-right');
map.addControl(new mapmetricsgl.TerrainControl({ source: 'terrainSource', exaggeration: 1.5 }), 'top-right');
const sky = {
'sky-color': '#199EF3', 'horizon-color': '#fbe4ff', 'fog-color': '#ffffff',
'sky-horizon-blend': 0.5, 'horizon-fog-blend': 0.5, 'fog-ground-blend': 0.5,
'atmosphere-blend': ['interpolate', ['linear'], ['zoom'], 0, 1, 12, 0],
};
map.on('load', () => {
document.getElementById('sky-color').addEventListener('input', e => { sky['sky-color'] = e.target.value; map.setSky(sky); });
document.getElementById('horizon-color').addEventListener('input', e => { sky['horizon-color'] = e.target.value; map.setSky(sky); });
document.getElementById('fog-color').addEventListener('input', e => { sky['fog-color'] = e.target.value; map.setSky(sky); });
[['sky-horizon-blend','v-shb'], ['horizon-fog-blend','v-hfb'], ['fog-ground-blend','v-fgb']].forEach(([k, vid]) => {
document.getElementById(k).addEventListener('input', e => {
sky[k] = parseFloat(e.target.value);
document.getElementById(vid).textContent = sky[k].toFixed(2);
map.setSky(sky);
});
});
document.getElementById('fog-on').addEventListener('click', () => map.setFog({ color: '#ffffff', 'high-color': '#245cdf', 'horizon-blend': 0.05 }));
document.getElementById('fog-off').addEventListener('click', () => map.setFog(null));
const presets = {
day: { 'sky-color': '#199EF3', 'horizon-color': '#fbe4ff', 'fog-color': '#ffffff', 'sky-horizon-blend': 0.5, 'horizon-fog-blend': 0.5, 'fog-ground-blend': 0.5 },
sunset: { 'sky-color': '#ff6b35', 'horizon-color': '#ffd700', 'fog-color': '#ff8c69', 'sky-horizon-blend': 0.4, 'horizon-fog-blend': 0.6, 'fog-ground-blend': 0.7 },
night: { 'sky-color': '#0a0a2e', 'horizon-color': '#1a1a4e', 'fog-color': '#0d0d2b', 'sky-horizon-blend': 0.3, 'horizon-fog-blend': 0.4, 'fog-ground-blend': 0.8 },
};
['day', 'sunset', 'night'].forEach(name => {
document.getElementById('preset-' + name).addEventListener('click', () => {
Object.assign(sky, presets[name]);
document.getElementById('sky-color').value = sky['sky-color'];
document.getElementById('horizon-color').value = sky['horizon-color'];
document.getElementById('fog-color').value = sky['fog-color'];
document.getElementById('sky-horizon-blend').value = sky['sky-horizon-blend'];
document.getElementById('v-shb').textContent = sky['sky-horizon-blend'].toFixed(2);
document.getElementById('horizon-fog-blend').value = sky['horizon-fog-blend'];
document.getElementById('v-hfb').textContent = sky['horizon-fog-blend'].toFixed(2);
document.getElementById('fog-ground-blend').value = sky['fog-ground-blend'];
document.getElementById('v-fgb').textContent = sky['fog-ground-blend'].toFixed(2);
map.setSky(sky);
});
});
});
</script>
</body>
</html>jsx
import React, { useEffect, useRef, useState, useCallback } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const PRESETS = {
day: { 'sky-color': '#199EF3', 'horizon-color': '#fbe4ff', 'fog-color': '#ffffff', 'sky-horizon-blend': 0.5, 'horizon-fog-blend': 0.5, 'fog-ground-blend': 0.5 },
sunset: { 'sky-color': '#ff6b35', 'horizon-color': '#ffd700', 'fog-color': '#ff8c69', 'sky-horizon-blend': 0.4, 'horizon-fog-blend': 0.6, 'fog-ground-blend': 0.7 },
night: { 'sky-color': '#0a0a2e', 'horizon-color': '#1a1a4e', 'fog-color': '#0d0d2b', 'sky-horizon-blend': 0.3, 'horizon-fog-blend': 0.4, 'fog-ground-blend': 0.8 },
};
const SkyFogTerrain = () => {
const mapContainer = useRef(null);
const map = useRef(null);
const [sky, setSkyState] = useState(PRESETS.day);
const [fogEnabled, setFogEnabled] = useState(true);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
zoom: 10,
center: [11.39085, 47.27574],
pitch: 65,
maxPitch: 85,
style: {
version: 8,
sources: {
osm: {
type: 'raster',
tiles: ['https://a.tile.openstreetmap.org/{z}/{x}/{y}.png'],
tileSize: 256,
attribution: '© OpenStreetMap Contributors',
maxzoom: 19,
},
terrainSource: {
type: 'raster-dem',
tiles: ['https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'],
tileSize: 256,
encoding: 'terrarium',
maxzoom: 15,
},
},
layers: [{ id: 'osm', type: 'raster', source: 'osm' }],
terrain: { source: 'terrainSource', exaggeration: 1.5 },
sky: { ...PRESETS.day, 'atmosphere-blend': ['interpolate', ['linear'], ['zoom'], 0, 1, 12, 0] },
fog: { color: '#ffffff', 'high-color': '#245cdf', 'horizon-blend': 0.05 },
},
});
map.current.addControl(new mapmetricsgl.NavigationControl({ visualizePitch: true }), 'top-right');
map.current.addControl(new mapmetricsgl.TerrainControl({ source: 'terrainSource', exaggeration: 1.5 }), 'top-right');
return () => { map.current?.remove(); map.current = null; };
}, []);
const updateSky = useCallback((patch) => {
setSkyState(prev => {
const next = { ...prev, ...patch };
map.current?.setSky({ ...next, 'atmosphere-blend': ['interpolate', ['linear'], ['zoom'], 0, 1, 12, 0] });
return next;
});
}, []);
const applyPreset = (name) => {
updateSky(PRESETS[name]);
};
const toggleFog = (on) => {
setFogEnabled(on);
if (on) map.current?.setFog({ color: '#ffffff', 'high-color': '#245cdf', 'horizon-blend': 0.05 });
else map.current?.setFog(null);
};
const btn = (active) => ({ padding: '6px 14px', border: 'none', borderRadius: 6, cursor: 'pointer', color: 'white', background: active ? '#3b82f6' : '#6b7280' });
return (
<div>
<div ref={mapContainer} style={{ height: 500, width: '100%' }} />
<div style={{ marginTop: 12, background: '#f8fafc', border: '1px solid #e2e8f0', borderRadius: 8, padding: '14px 16px', fontSize: 13 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '10px 20px' }}>
{[['sky-color', 'sky-color'], ['horizon-color', 'horizon-color'], ['fog-color', 'fog-color']].map(([key, label]) => (
<label key={key} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ minWidth: 130, color: '#374151' }}>{label}</span>
<input type="color" value={sky[key]} onChange={e => updateSky({ [key]: e.target.value })}
style={{ width: 44, height: 28, border: 'none', cursor: 'pointer', borderRadius: 4 }} />
</label>
))}
{[['sky-horizon-blend', 'sky-horizon-blend'], ['horizon-fog-blend', 'horizon-fog-blend'], ['fog-ground-blend', 'fog-ground-blend']].map(([key, label]) => (
<label key={key} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span style={{ minWidth: 130, color: '#374151' }}>{label}</span>
<input type="range" min="0" max="1" step="0.01" value={sky[key]}
onChange={e => updateSky({ [key]: parseFloat(e.target.value) })} style={{ flex: 1 }} />
<span style={{ minWidth: 30, textAlign: 'right', color: '#6b7280' }}>{sky[key].toFixed(2)}</span>
</label>
))}
</div>
<div style={{ marginTop: 10, display: 'flex', gap: 8, flexWrap: 'wrap' }}>
<button style={btn(fogEnabled)} onClick={() => toggleFog(true)}>Fog ON</button>
<button style={btn(!fogEnabled)} onClick={() => toggleFog(false)}>Fog OFF</button>
<button style={{ ...btn(false), background: '#0ea5e9' }} onClick={() => applyPreset('day')}>Day</button>
<button style={{ ...btn(false), background: '#f97316' }} onClick={() => applyPreset('sunset')}>Sunset</button>
<button style={{ ...btn(false), background: '#1e1b4b' }} onClick={() => applyPreset('night')}>Night</button>
</div>
</div>
</div>
);
};
export default SkyFogTerrain;For more information, visit the MapMetrics GitHub repository.