百度地图开放平台
更新时间: 2026/09/17 17:42
WebGLCustomLayer 图层

当内置的点、线、面图层无法满足需求时,可以通过 BMap.WebGLCustomLayer 直接在地图的 WebGL 上下文中,使用自定义 GLSL 着色器绘制内容,实现粒子系统、流场、光效等高度个性化的可视化效果。图层会负责着色器编译、GL 资源管理与逐帧调度,你只需关注着色器与顶点数据。

添加自定义图层

创建图层时,vertexShader 与 fragmentShader 为必填的着色器源码。顶点使用图层局部坐标,强烈建议传入 referCenter 作为坐标原点,避免墨卡托坐标量级过大造成 float32 精度丢失。顶点属性在 attributes 中声明,其中 type: 'lnglat' 是语法糖,图层会自动把经纬度投影成局部坐标。
下面通过一组发光点演示基础用法:在 attributes 中声明逐顶点的位置与颜色,配合内置的 u_time 实现呼吸动画。

var CENTER = new BMap.Point(116.404, 39.915);
var map = new BMap.Map('map');
map.centerAndZoom(CENTER, 13);
// 顶点着色器:u_ 打头的多为内置 uniform,声明即由图层自动注入
var vertexShader = `
attribute vec2 a_pos;
attribute vec4 a_color;
uniform mat4 u_mvpMatrix; // 图层局部坐标 -> 裁剪空间
uniform float u_pixelRatio; // 设备像素比
uniform float u_size;
varying vec4 v_color;
void main() {
v_color = a_color;
gl_Position = u_mvpMatrix * vec4(a_pos, 0.0, 1.0);
gl_PointSize = u_size * u_pixelRatio;
}
`;
// 片元着色器:用内置 u_time 做呼吸动画,并把方点裁成圆形光斑
var fragmentShader = `
precision mediump float;
uniform float u_opacity;
uniform float u_time;
varying vec4 v_color;
void main() {
float d = distance(gl_PointCoord, vec2(0.5)) * 2.0;
if (d > 1.0) { discard; }
float breath = 0.5 + 0.5 * sin(u_time * 2.0);
float core = smoothstep(1.0, 0.0, d);
gl_FragColor = vec4(v_color.rgb, v_color.a * mix(0.35, 1.0, breath) * core * core * u_opacity);
}
`;
var layer = new BMap.WebGLCustomLayer({
referCenter: CENTER, // 图层局部坐标原点,强烈建议传入
animation: true, // 每帧自动重绘,u_time 随之推进
vertexShader: vertexShader,
fragmentShader: fragmentShader,
attributes: {
// type:'lnglat' 会自动把经纬度投影成局部坐标,无需再写 size
a_pos: {
data: [[116.40282, 39.93227], [116.40396, 39.91512], [116.38909, 39.92451]],
type: 'lnglat'
},
// Uint8Array + normalized:着色器里读到 0~1,省显存
a_color: {
data: new Uint8Array([82, 196, 26, 255, 255, 77, 79, 255, 19, 194, 194, 255]),
size: 4,
normalized: true
}
},
uniforms: {u_size: 56},
draw: {mode: 'POINTS'}
});
map.addLayer(layer);
声明式与命令式

图层支持两种组织绘制的方式,二者等价,也可混用:
声明式:几何固定时,把顶点数据、uniform 与绘制指令直接写在构造参数里,无需提供 onRender,图层会按 draw 描述自动绘制一次(上面的发光点即是)。
命令式:几何逐帧变化,或需要在一帧内多次提交绘制时,构造参数只给着色器,改在 onRender 回调里通过绘制上下文 ctx 逐帧组织数据并调用 ctx.draw()。

内置 uniform

在着色器中声明以下 uniform 后,图层会逐帧自动赋值;未声明则不产生任何开销:

uniform mat4 u_mvpMatrix; // 图层局部坐标 -> 裁剪空间
uniform float u_opacity; // 图层透明度
uniform float u_zoom; // 当前缩放级别
uniform float u_time; // 动画时间(秒)
uniform vec2 u_resolution; // 画布尺寸(像素)
uniform float u_pixelRatio; // 设备像素比
uniform float u_unitsPerPixel; // 一个屏幕像素对应的局部坐标单位
uniform vec2 u_center; // 地图中心
uniform float u_heading; // 相机朝向(度)
uniform float u_tilt; // 相机倾斜角(度)
使用索引绘制面

给构造参数传入 indices 后,图层会自动改用 drawElements 索引绘制。下面用两个三角形拼出一个矩形,四个角各给一种颜色,片元阶段插值出渐变覆盖面。

// 逐顶点色的通用着色器,后续示例复用
var vsVColor = `
attribute vec2 a_pos;
attribute vec4 a_color;
uniform mat4 u_mvpMatrix;
varying vec4 v_color;
void main() {
v_color = a_color;
gl_Position = u_mvpMatrix * vec4(a_pos, 0.0, 1.0);
}
`;
var fsVColor = `
precision mediump float;
uniform float u_opacity;
varying vec4 v_color;
void main() {
gl_FragColor = vec4(v_color.rgb, v_color.a * u_opacity);
}
`;
var corners = [[116.36, 39.90], [116.45, 39.90], [116.45, 39.94], [116.36, 39.94]];
var layer = new BMap.WebGLCustomLayer({
referCenter: CENTER,
vertexShader: vsVColor,
fragmentShader: fsVColor,
attributes: {
a_pos: {data: corners, type: 'lnglat'},
a_color: {
data: new Uint8Array([
79, 140, 255, 170,
107, 225, 255, 170,
124, 255, 178, 170,
255, 209, 102, 170
]),
size: 4,
normalized: true
}
},
indices: [0, 1, 2, 0, 2, 3], // 两个三角形拼成矩形,传了 indices 即走 drawElements
draw: {mode: 'TRIANGLES'}
});
map.addLayer(layer);
命令式逐帧绘制

对于每帧都在变化的几何,可在 onRender 回调中借助绘制上下文实时计算顶点。下面用 ctx.getUnitsPerPixel() 让扫描半径在屏幕上恒定为 150 像素,用 ctx.getTime() 驱动旋转,实现雷达扫描效果。

var layer = new BMap.WebGLCustomLayer({
referCenter: CENTER,
animation: true,
vertexShader: vsVColor, // 沿用上文定义的着色器
fragmentShader: fsVColor,
onRender: function (ctx) {
var radius = 150 * ctx.getUnitsPerPixel(); // 屏幕上恒定 150px
var sweep = ctx.getTime() * 1.4; // 随时间旋转
var n = 64;
var pos = [0, 0]; // 扇心
var col = [80, 230, 170, 0]; // 扇心透明
for (var i = 0; i <= n; i++) {
var a = sweep - i / n * 1.2;
pos.push(Math.cos(a) * radius, Math.sin(a) * radius);
col.push(80, 230, 170, Math.round(210 * (1 - i / n)));
}
ctx.setAttribute('a_pos', {data: new Float32Array(pos), size: 2});
ctx.setAttribute('a_color', {data: new Uint8Array(col), size: 4, normalized: true});
ctx.draw({mode: 'TRIANGLE_FAN'});
}
});
map.addLayer(layer);
渲染状态与混合模式

深度测试、混合、面剔除等全局 GL 状态必须通过 renderState 声明,切勿直接调用 gl.enable()、gl.blendFunc(),否则会破坏地图对状态的缓存,导致底图文字、图标、marker 出现难以定位的渲染异常。
blend 支持 normal(标准 alpha 混合,默认)、additive(颜色相加,仅在深色背景且图元互相重叠时才明显)、screen(滤色,浅色底图也能提亮)等。下面通过一层深色底衬叠加 additive 发光点演示效果:

// additive 需深色背景才看得出叠加,先铺一层深色底衬(zIndex 小,先画)
var backdrop = new BMap.WebGLCustomLayer({
referCenter: CENTER,
zIndex: 1,
vertexShader: vsVColor,
fragmentShader: fsVColor,
attributes: {
a_pos: {
data: [[116.34, 39.86], [116.47, 39.86], [116.47, 39.97], [116.34, 39.97]],
type: 'lnglat'
},
a_color: {
data: new Uint8Array([10, 14, 24, 200, 10, 14, 24, 200, 10, 14, 24, 200, 10, 14, 24, 200]),
size: 4,
normalized: true
}
},
indices: [0, 1, 2, 0, 2, 3],
renderState: {blend: 'normal'},
draw: {mode: 'TRIANGLES'}
});
map.addLayer(backdrop);
// 一圈互相重叠的发光点,additive 混合,重叠处颜色累积过曝
var glow = new BMap.WebGLCustomLayer({
referCenter: CENTER,
zIndex: 2,
animation: true,
vertexShader: vertexShader, // 复用示例一的发光点着色器
fragmentShader: fragmentShader,
attributes: {
a_pos: {data: spots, type: 'lnglat'},
a_color: {data: new Uint8Array(colors), size: 4, normalized: true}
},
uniforms: {u_size: 130},
renderState: {blend: 'additive'}, // 颜色相加
draw: {mode: 'POINTS'}
});
map.addLayer(glow);
显示与隐藏

图层加入地图后,可通过 setVisible 控制显隐,通过 map.removeLayer 移除。

layer.setVisible(false); // 隐藏图层
layer.setVisible(true); // 显示图层
map.removeLayer(layer); // 从地图移除
相关类参考

BMap.WebGLCustomLayer

上一篇

面图层

下一篇

实时路况
本篇文章对您是否有帮助?