前言
使用webgl开发三维应用的时候,经常会发现三维场景加载比较慢,往往需要等待挺长时间,这样用户的体验就很不友好。造成加载慢的原因,主要是三维应用涉及到的资源文件会特别多,这些资源文件主要是模型及其图片,往往这些模型和图片都会比较大。
为了加快三维场景的加快速度,可以使用IndexedDB在客户端进行资源缓存。IndexedDB,即客户端持久化数据库!使用本缓存技术,在初次访问后,3D场景中的文件级别数据将写入访问设备本地缓存数据库,在客户端实现永久的生命周期,清除浏览器缓存也不影响已缓存的3D模型文件。
IndexedDB介绍
IndexedDB
是一个前端数据持久化解决方案(即前端缓存),由浏览器实现。
IndexedDB又如下特点
- 基于文件存储。意味着其容量可达到硬盘可用空间上限
- 非关系型数据库。意味着扩展或收缩字段一般无须修改数据库和表结构(除非新增字段用做索引)
- 键值对存储。意味着存取无须字符串转换过程
- 存储类型丰富。意味着浏览器缓存中不再是只能存字符串了
- 异步:意味着所有操作都要在回调中进行
本地浏览器拥有三种永久存储数据技术,分别为Web Storage、IndexedDB、Web SQL。IndexedDB具备查询高效、存储空间大和异步操作等技术特征,有巨大的优势。
存储空间大。IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。在HTML5本地存储中,IndexedDB存储的数据则是最多的。
查询高效。IndexedDB是一种轻量级NOSQL数据库,是由浏览器自带。相比Web Sql更加高效,包括索引、事务处理和查询功能。
异步操作。IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
与此同时,IndexedDB 内部采用对象仓库存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象,满足了三维场景的存储需要。
因此 使用IndexedDB缓存是一种最为优异的前端缓存方案。像Babylon.js,其引擎层面已经支持了IndexedDB缓存。可以参考如下文档:
https://doc.babylonjs.com/divingDeeper/scene/optimizeCached。
three.js使用IndexedDB的思路
有关具体如何使用IndexedDB,有很多资料进行介绍,此文不在赘述。
使用IndexedDB缓存模型资源,首先需要获取模型相关的资源,这些模型资源包括模型文件以及相关的图片文件。比如对于GLTF模型而言,其资源包括.gltf的模型主文件,.bin格式的文件,纹理贴图文件等等。首次加载加一个模型的时候,肯定是加载网络上的资源文件,通过threejs的LoadingMananger可以收集一个gltf模型的各种资源文件。代码如下:
const resourceCollector = []; const loadingManager = new LoadingManager(); loadingManager.setURLModifier( (url,path) => { console.log(url); if(url.startsWith("data:") || url.startsWith("blob:")) { return url; } resourceCollector.push(url); return url; });
上述代码resourceCollector收集了加载模型过程中所有的模型资源的地址。收集之后把所有资源存储到IndexedDB中:
saveGltfModel:async function(options,resourceCollector){ const gltfUrl = options.gltfPath; const blobs = {}; for(let i = 0;i < resourceCollector.length;i ++) { let url = resourceCollector[i]; let blob = await loadAsBlob(url); blobs[url] = blob; await addToDatabase("model",{key:url,blob}) } await addToDatabase("model_info",{key:gltfUrl,content:resourceCollector}); },
其中loadAsBlob是把一个资源加载成为blob对象,代码如下:
const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = "blob"; xhr.onerror = function() {reject("Network error.")}; xhr.onload = function() { if (xhr.status === 200) {resolve(xhr.response)} else {reject("Loading error:" + xhr.statusText)} }; xhr.send();
而addToDatabase方法把资源添加到IndexedDB数据库。
function addToDatabase(storename, data) { const promise = new Promise( (resolve,reject) => { let store = database.transaction(storename, 'readwrite').objectStore(storename); let countReq = store.count(data.key); countReq.onsuccess = function(event) { console.log("count:",event.target.result); let count = event.target.result; if(count == 0) { let request = store.add(data); request.onerror = function (event) { console.error('add添加数据库中已有该数据') reject(event); }; request.onsuccess = function (event) { console.log('add添加数据已存入数据库') resolve(event); }; } }; }); }
下一次获取模型的时候,可以先判断是否以及本地存储,如果已经本地存储,就可以直接从本地获取模型资源:
if(this.indexDbCache && indexedDB) { if(database == null) { database = await initialDB(); } const storeObject = await findInDatabase("model_info",key); if(storeObject) { return this.loadGltfInDb(options); } }
缓存效果测评
通过测试可以发现对于比较大的场景,模型加载的速度可以提高几倍,十几倍甚至几十倍。由此可见,IndexedDB缓存效果很明显。