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

netcy發表於2022-02-09

前言

使用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/div...

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彪叔” 可以及時收到更多有價值的文章。

相關文章