3D 与地形
MapLibre 不是三维引擎,它的三维能力集中在三件事:用高程数据抬起地表、 用晕渲表现坡向、用拉伸把面变成柱体。三者都建立在倾斜视角(pitch)之上。
先有倾角,再谈三维
默认视角是正俯视,地形起伏看不出来。把 pitch 调到 45 到 60 度,
地形的立体感才出现;bearing 决定朝向,两者一起构成相机姿态。
var map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [103.0, 30.0],
zoom: 10,
pitch: 55,
bearing: -20,
maxPitch: 75,
hash: true // 把视角写进 URL,便于分享与复现
});
map.addControl(new maplibregl.NavigationControl({
visualizePitch: true
}), 'top-right');
maxPitch 默认 60,最大可到 85。角度越大,远处地面被压缩得越厉害,
瓦片在屏幕上被拉长,纹理细节也会变糊。展示地形时 50 到 65 度是常见区间。
地形:raster-dem 加 terrain
setTerrain 只是声明用哪个源抬升地表。真正的解码工作由源的
encoding 完成,配错编码时地表会变成随机坑洼。
map.on('load', function () {
map.addSource('dem', {
type: 'raster-dem',
tiles: [
'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png'
],
encoding: 'terrarium',
tileSize: 256,
maxzoom: 14
});
map.setTerrain({
source: 'dem',
exaggeration: 1.5 // 垂直放大倍数
});
map.addLayer({
id: 'hillshade',
type: 'hillshade',
source: 'dem',
paint: {
'hillshade-exaggeration': 0.4,
'hillshade-shadow-color': '#2b2b2b',
'hillshade-highlight-color': '#ffffff'
}
});
});
exaggeration 大于 1 会放大高差,让山更陡。真实地形用 1.0 到 1.2,
做视觉强调时 1.5 到 2 都合理,但超过 2 后道路与建筑会明显漂浮。
地形与图层顺序无关。setTerrain 会改写整个渲染流程,
矢量图层默认贴地渲染。需要悬浮在地面之上的要素(航线、空域边界),
用 fill-extrusion 的 base 抬升到目标高度,
普通线图层做不到离地绘制。
建筑体块:fill-extrusion
拉伸图层把面的高度解释为米。高度可以来自属性,也可以固定。 没有 height 字段时,整片建筑会变成同一高度的方块。
map.addLayer({
id: 'buildings-3d',
type: 'fill-extrusion',
source: 'base',
'source-layer': 'building',
minzoom: 13,
paint: {
'fill-extrusion-height': ['coalesce', ['get', 'height'], 8],
'fill-extrusion-base': ['coalesce', ['get', 'min_height'], 0],
'fill-extrusion-color': ['interpolate', ['linear'], ['get', 'height'],
0, '#d8dee4',
20, '#aeb9c4',
80, '#7f8d9b'
],
'fill-extrusion-opacity': 0.9,
'fill-extrusion-vertical-gradient': true
}
});
fill-extrusion-vertical-gradient 让侧面有上下明暗渐变,
体块边界更清楚。关掉后大片建筑会糊成一块纯色。
光与天空
三维图层的明暗由 light 决定。写在内联样式里最直接,
也可用 map.setLight() 运行时调整;天空与雾效则由
map.setSky() 单独设置。
map.setLight({
anchor: 'viewport', // 光源相对视口,转动地图时明暗不变
position: [1.2, 210, 30], // 球面坐标:径向、方位角、极角
color: '#ffffff',
intensity: 0.45
});
map.setSky({
'sky-color': '#8fb8de',
'horizon-color': '#e8eef4',
'fog-color': '#dfe6ee',
'sky-horizon-blend': 0.8
});
anchor: 'viewport' 是默认值,光固定来自屏幕方向;
改成 'map' 后光跟着地图坐标走,转动时建筑明暗会变化。
| 能力 | 依赖 | 关键属性 |
|---|---|---|
| 地表起伏 | raster-dem 源 | setTerrain 的 source 与 exaggeration |
| 坡面晕渲 | 同一个 raster-dem 源 | hillshade-exaggeration、阴影与高光色 |
| 建筑体块 | 含高度字段的面数据 | fill-extrusion-height、fill-extrusion-base |
| 明暗 | 无 | light 的 anchor、position、intensity |
| 天空与雾 | 无 | sky 的 sky-color、horizon-color、fog-color |
注意事项
倾斜后注记压在建筑里
拉伸图层与符号图层的绘制顺序决定遮挡关系。建筑放在注记之前,
体块会挡住文字;想要文字浮在上面,把 symbol 图层排在
buildings-3d 之后,并给文字加 text-allow-overlap
与描边,避免被地面颜色吃掉。
地形下的相机高度
开启地形后,map.getCenter() 仍返回平面坐标,海拔不参与计算。
需要贴合地面时读 map.queryTerrainElevation(lngLat),
它在地形未加载完成时返回 null。
移动端关闭地形
地形会额外请求一套高程瓦片,并让每帧的顶点计算量翻倍。
低端设备上按 map.getCanvas 所在窗口的宽度或
navigator.hardwareConcurrency 判断,直接跳过 setTerrain,
保留晕渲即可获得大部分视觉收益。
经纬志(2026)。《3D 与地形》。https://www.jingweizhi.com/maplibre/tutorial/terrain-3d.html(访问日期 2026-11-03)。