Optimizing Multi‑Scene Rendering on AMap by Sharing a WebGL Context
Gaode solved the WebGL‑context limit in AMap by rendering all visual layers on a single canvas with a shared context, disabling autoClear, preserving depth ordering, and using per‑layer EffectComposers merged via a final composer, enabling multiple scenes and post‑processing without exceeding browser limits.
Gaode recently optimized its visual layer components, addressing many issues such as merging rendering of multiple scenes and overlay rendering. The article shares the experience and solutions.
When developing visual map layers, a common problem is rendering multiple scenes (e.g., traffic routes, area ranges, POIs) in the same spatial system while keeping layer‑wise control. The naive solution is to create a separate canvas for each scene and stack the canvases, but browsers limit the number of active WebGL contexts (Chrome on PC ~8, mobile devices even fewer). Exceeding the limit produces the error “WARNING: Too many active WebGL contexts”, and the earliest created context is disabled.
Solution – Shared WebGL Context : Render all layers with a single canvas and share the same WebGL context among multiple renderers. Set the renderer’s autoClear to false and enable depth so that the layers preserve correct depth ordering.
// 1.创建CustomLayer,为最终渲染结果提供容器
customLayer = new AMap.CustomLayer(canvas, {
zooms: [3, 22],
zIndex: 0,
alwaysRender: true
})
const gl = canvas.getContext('webgl')
// 2.逐个创建renderer
const renderer1 = createRenderer()
const renderer2 = createRenderer()
function createRenderer(){
const {innerWidth, innerHeight} = window
const renderer = new THREE.WebGLRenderer({
context: gl,
alpha: true,
depth: true // 多个场景共享深度关系
})
// 保证多个场景叠加不会互相清除
renderer.autoClear = false
renderer.setClearAlpha(0)
renderer.setSize(innerWidth, innerHeight)
return renderer
}
// 3.逐个渲染renderer
function animate(){
// 由于是同个坐标系的图层,camera可以是共享的
renderer1.render(scene1, camera)
renderer2.render(scene2, camera)
requestAnimationFrame(animate)
}With this setup, all layers are rendered into the same canvas, avoiding the WebGL‑context limit while preserving depth relationships.
Post‑processing with EffectComposer : When each layer also needs post‑processing effects, create an EffectComposer for each layer, add a RenderPass and any additional passes, then convert the composer’s output to a ShaderPass that can be added to a final composer which merges the results.
// 代码实现(创建最终合成处理器)
const renderer = this.createRenderer()
const composer = new EffectComposer(renderer)
const renderPass = new RenderPass(new THREE.Scene(), new THREE.Camera())
renderPass.clear = true
composer.addPass(renderPass) // 渲染通道
// 添加其他处理器结果
composer.addPass(createPassShader(composer1))
composer.addPass(createPassShader(composer2))
// 添加最终效果和输出通道
const bloomPass = this.createBloomPass()
composer.addPass(bloomPass)
composer.addPass(this.createOutputPass())
/**
* 创建1个着色通道用于叠加渲染处理器
* @param {EffectComposer} composer
* @param {Object} option
*/
createPassShader (composer, option = { mode: 0 }) {
const { mode } = option
const res = new ShaderPass(new THREE.ShaderMaterial({
uniforms: {
baseTexture: { value: null },
coverTexture: { value: null },
mode: { value: mode } // 0 颜色叠加,1 baseTexture遮盖coverTexture, 2 coverTexture遮盖baseTexture
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}` ,
fragmentShader: `
uniform sampler2D baseTexture;
uniform sampler2D coverTexture;
uniform int mode;
varying vec2 vUv;
void main() {
vec4 baseColor = texture2D(baseTexture, vUv);
vec4 bloomColor = texture2D(coverTexture, vUv);
if(mode == 0){
gl_FragColor = ( baseColor + vec4( 1.0 ) * bloomColor );
}else if(mode == 1){
gl_FragColor = bloomColor * (1.0 - baseColor.a) + baseColor;
}else if(mode ==2){
gl_FragColor = baseColor * (1.0 - bloomColor.a) + bloomColor;
}
}` ,
defines: {}
}), 'baseTexture')
return res
}The final render loop calls each layer’s composer first, then the final composer:
animate (time) {
// 各图层仅渲染到缓冲对象
composer1.render()
composer2.render()
// 最终合并渲染
composer.render()
requestAnimationFrame(this.animate)
}The article includes several screenshots showing the combined rendering result, where three layers are displayed with bloom effects while sharing a single canvas for geometry and a separate canvas for post‑processing.
Author note: Provided by Gaode developer community member Zhang Linhai. This is not official documentation and represents the author’s personal view.
Amap Tech
Official Amap technology account showcasing all of Amap's technical innovations.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.