WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期
WebGL地图引擎架构和渲染核心设计|WebGL地图引擎系列第二期 作者:J
前言
今天这篇文章来聊聊 WebGL 地图引擎的架构及其渲染核心的设计。Web 地图产品在采用 WebGL 技术绘制前早已经有 DOM 实现和采用 Canvas2D 绘图技术实现的版本,因此这里不再详细描述整个地图引擎的架构,我们重点只看和 WebGL 相关的内容。
从功能实现上看,WebGL 地图引擎需要以下部分
数据的请求、解析以及缓存的管理。
WebGL 相关的 Shader、状态管理。
WebGL 涉及的各种 Buffer、纹理对象的创建和管理。
不同类型的元素绘制。
在架构设计时需要考虑以下这些因素
模块是解耦的。方便不同人共同协作开发。
高性能的。通过架构解决一些性能问题。
高效的。通过封装隐藏底层细节。
当然,各个方面不可能完全达到最理想状态,因此需要根据项目特点进行权衡。
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 的每一帧处理当中:
如上图所示,Scene 可能在任意时刻收到事件,但所有的绘制操作都放在 rAF 的每一帧里进行。不受事件派发的影响,这样保证了地图渲染的流畅性。这种策略同样可以运用在其他有用户频繁交互的界面上,以便提升界面动画的流畅性。