WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期

WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期   作者:J

前言



今天这篇文章来聊聊 WebGL 地图引擎的架构及其渲染核心的设计。Web 地图产品在采用 WebGL 技术绘制前早已经有 DOM 实现和采用 Canvas2D 绘图技术实现的版本,因此这里不再详细描述整个地图引擎的架构,我们重点只看和 WebGL 相关的内容。


从功能实现上看,WebGL 地图引擎需要以下部分

  1. 数据的请求、解析以及缓存的管理。

  2. WebGL 相关的 Shader、状态管理。

  3. WebGL 涉及的各种 Buffer、纹理对象的创建和管理。

  4. 不同类型的元素绘制。


在架构设计时需要考虑以下这些因素

  1. 模块是解耦的。方便不同人共同协作开发。

  2. 高性能的。通过架构解决一些性能问题。

  3. 高效的。通过封装隐藏底层细节。


当然,各个方面不可能完全达到最理想状态,因此需要根据项目特点进行权衡。


WebGL 渲染引擎的架构

总体架构如下所示:

WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期


主要模块说明

  • Map 是地图引擎的核心类,表示地图本身。初始化时创建 Scene、LayerManager 和 OverlayManager 等管理器。


  • Scene 模块是地图的渲染核心逻辑,其中关键的作用是创建一个渲染循环,具体策略会在后面提到。


  • LayerManager 负责计算视野内所需要的网格数据,并通知每一个 Layer 加载数据。


  • Layer 通过 Worker 发送请求,Worker 收到数据后解析并返回,Worker 统一由 WorkerManager 管理。


  • Features 负责保存当前视野内的数据,负责绘制的 Painter 类从中获取数据进行绘制。只要约定好数据格式,请求、解析数据的工作与绘制工作可以完全解耦。在实际开发中,这两大部分也是由不同开发人员独立并行开发的。


  • Painter 还引用了一些和 WebGL 绘制相关的模块,主要有:WebGLProgram 负责管理 shader、Draw Utils 提供了各种类型的元素的绘制函数、TextureAtlas 负责动态纹理合并、Camera 负责视图矩阵运算。这些模块都对一些较为底层的计算或 WebGL 调用做了封装,以便提升开发效率。


核心渲染逻辑

Scene 模块实现了核心的渲染逻辑,该模块监听 Map 的各种状态变化的事件:

  • center_change:中心点变化触发此事件。


  • zoom_change:级别变化触发此事件。


  • heading_change:正北方向变化触发此事件。


  • tilt_change:倾斜角度变化触发此事件。


Scene 收到事件后会启动一个动画循环,通过 requestAnimationFrame(下文简称rAF)实现,并保证只有一个 rAF 循环。rAF 循环启动后会在100ms后自动停止,直到下次再被启动。


在有些 WebGL 教程中会创建一个永久运行的 rAF 来实现动画。例如在 Professional WebGL Programming (p223-p224) 一书中有这样的例子:

function draw(currentTime) {
    // Request a new call to draw the next frame before you actually
    // start drawing the current frame.
    requestAnimationFrame(draw);
    // Update the position of the moving objects in your scene
 
    // Draw your scene
 
}

function startup() {
    // Do your usual setup and initialization     
    draw();
}

 

下面是 ThreeJS 文档中的一段代码:

function render() {
    requestAnimationFrame(render);
    renderer.render(scene, camera);

render();


上述代码无一例外使用了 rAF 来处理 WebGL 动画,在绘制方法中使用 rAF 来重复调用自己,从而实现循环绘制。这样做有什么问题吗?首先这个循环会不停的循环往复,除非你的 3D 场景确实每时每刻都有物体在运动需要刷新,否则循环绘制重复的内容是一种浪费,尤其是在移动端,这样做会大大增加耗电量。其次,在 iOS 上,苹果不允许 APP 后台访问 OpenGL 相关的图形接口,否则会强制终结。所以当你的程序运行在 APP 中的某个 WebView 时,如果用户按了 HOME 健让 APP 切换到后台,这个 APP 很可能被系统终结掉。因此在实际开发中,渲染循环通常是按需启动,并自动停止的。对地图来说,只有用户操作地图或者通过接口调用才会导致地图发生状态的变化,否则地图是静止的,静止时也就没有必要再通过循环进行渲染。


从另一个角度看 Map 派发事件可能会比较频繁,通过 rAF 方式可以把频繁的事件处理统一同步到 rAF 的每一帧处理当中:


WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期


如上图所示,Scene 可能在任意时刻收到事件,但所有的绘制操作都放在 rAF 的每一帧里进行。不受事件派发的影响,这样保证了地图渲染的流畅性。这种策略同样可以运用在其他有用户频繁交互的界面上,以便提升界面动画的流畅性。


WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期