起因
接到个需求:解决低配置电脑中,浏览 3DTiles 容易爆显存,导致 WebGL 崩溃的问题。
简单翻看了一下 Cesium3DTiles 的显存管理机制,编写了一个管理工具。
合理的使用该管理工具,可以有效减少由于 3DTiles 占用的显存过大而导致爆显存的问题。
Cesium3DTiles 的显存管理
Cesium 中单个 Cesium3DTiles 有自己的显存控制参数 maximumMemoryUsage 。该参数可以控制单个 Cesium3DTiles 的显存消耗。
不过,值得注意的是,maximumMemoryUsage 并不是一个强制性的值,它仅仅起到一个阈值的作用。也就是当场景中 Cesium3DTiles 使用的显存超过该值时,就会触发缓存回收机制。而缓存回收后,使用的总显存并不一定会降到该值以下。比如,在当前相机视角下,3DTiles 需要 300MB 才能达到对应的显示精度,而最大使用显存设置到 256MB 时,最终使用的显存将以 Cesium 需要的显存为准,也就是 300MB。

maximumMemoryUsage 背后的工作原理其实很简单:
- 获取当前消耗的显存数量 totalMemoryUsageInBytes (单位Byte)
- 判断消耗的显存是否大于 maximumMemoryUsage
- 如果大于,则回收缓存 trimLoadedTiles (回收上一帧中未使用的瓦片缓存)
具体实现
了解了单个 Cesium3DTiles 如何管理显存,那管理全部的 Cesium3DTiles 就简单多了。
直接上代码
代码使用 TypeScript 编写,如果想使用 JavaScript 版本,请自行用 tsc 编译,或者手动修改成 JavaScript 版(代码并不复杂)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| import * as Cesium from "cesium"; export default class Cesium3DTilesMaximumTotalMemoryUsageControl { private _scene: Cesium.Scene; private _current3DTilesTotalMemoryUsage: number;
public maximumTotalMemoryUsage: number;
constructor(scene: Cesium.Scene, maximumTotalMemoryUsage: number) { this._scene = scene; this.maximumTotalMemoryUsage = maximumTotalMemoryUsage; this._current3DTilesTotalMemoryUsage = 0;
(this._scene.primitives as any)._primitives.forEach((primitive: any) => { if (primitive instanceof Cesium.Cesium3DTileset) { primitive.tileLoad.addEventListener(this._preRender); } }); }
get current3DTilesTotalMemoryUsage() { return this._current3DTilesTotalMemoryUsage; }
private _preRender = () => { this._current3DTilesTotalMemoryUsage = 0; (this._scene.primitives as any)._primitives.forEach((primitive: any) => { if (primitive instanceof Cesium.Cesium3DTileset) { this._current3DTilesTotalMemoryUsage += primitive.totalMemoryUsageInBytes / 1024 / 1024; } });
if (this._current3DTilesTotalMemoryUsage > this.maximumTotalMemoryUsage) { (this._scene.primitives as any)._primitives.forEach((primitive: any) => { if (primitive instanceof Cesium.Cesium3DTileset) { primitive.trimLoadedTiles(); } }); } };
destroy() { (this._scene.primitives as any)._primitives.forEach((primitive: any) => { if (primitive instanceof Cesium.Cesium3DTileset) { primitive.tileLoad.removeEventListener(this._preRender); } }); Cesium.destroyObject(this); } }
|
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13
|
const memoryUsageControl = new Cesium3DTilesMaximumTotalMemoryUsageControl(scene, 1024);
memoryUsageControl.maximumTotalMemoryUsage = 2048;
console.log(memoryUsageControl.current3DTilesTotalMemoryUsage);
memoryUsageControl.destroy();
|
注意事项
值得注意的是,这里的最大显存和 Cesium3DTileset 的 maximumMemoryUsage 一样并不是一个强制性的值,不能保证消耗的显存一定会降到该值以下!!
创建该工具的时候,为了减少不必要的计算,手动为场景中的所有 3DTiles 注册了 tileLoad 事件。所以需要注意,当新的3DTiles加入时,新的3DTiles没有注册对应的事件,会导致新的3DTiles使用的显存不计入该工具记录的总显存中。