使用IndexedDB快取給WebGL三維程式加速

前言

使用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快取。可以參考如下文檔:
//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快取效果很明顯。

如果對可視化感興趣,可以和我交流,微信541002349。

關注公號「ITMan彪叔」 可以及時收到更多有價值的文章。