WebGL載入本地模型

netcy發表於2022-05-06

前言

大部分的webgl框架,比如threejs和babylon等,都可以載入obj和gltf模型。 我們的引擎,基於three封裝,同樣有載入模型的loader,因此載入obj和gltf模型也是很簡單就可以實現的。

不過載入檔案都是線上的檔案,也就是通過url的形式進行載入。 團隊開發的三維視覺化平臺框架,需要能夠上傳obj和gltf等格式的模型,在上傳前,需要先對模型預覽,這就涉及到如何載入本地模型的問題了。

載入本地模型

本文以gltf為例,進行說明。 載入本地模型的思路是這樣的:
既然引擎可以通過url的機制,載入模型。 那麼如果有一種機制,可以把本地檔案及其關聯的資源(比如貼圖)等轉換成url的形式,就可以進行使用loader進行訪問了。

Blob & File

首先我們學習下Blob和File物件,以下內容來自MDN:

Blob物件表示一個不可變、原始資料的類檔案物件。它的資料可以按文字或二進位制的格式進行讀取,也可以轉換成 ReadableStream 來用於資料操作。

Blob 表示的不一定是JavaScript原生格式的資料。File 介面基於Blob,繼承了 blob 的功能並將其擴充套件使其支援使用者系統上的檔案。

File 物件是特殊型別的 Blob,且可以用在任意的 Blob 型別的 context 中。比如說, FileReader, URL.createObjectURL(), createImageBitmap() (en-US)"), 及 XMLHttpRequest.send()) 都能處理 Blob File

createObjectURL

URL物件上的方法 createObjectURL可以把一個Blob物件或者File物件,轉化成一個url物件,語法如下:

objectURL = URL.createObjectURL(object);

其中object表示的是Blob或者File物件。返回的是一個url地址物件。

載入本地模型

有了上述基礎知識,大致的思路就出來了:

  • 首先 載入本地檔案,讀取file物件(可能是多個File物件,因為一個模型可能包括多個資原始檔)。
  • 找出主要檔案(gltf glb等格式的)檔案,主檔案通過 createObjectURL方法轉換成url物件
  • 找出其他檔案,通過createObjectURL方法轉換成url物件
  • 載入主檔案的url,並在載入過程中,通過地址改寫的方式,把相關的資源替換成檔案的url物件。

以上思路的大致程式碼如下:

let files = document.getElementById("file-input").files;

          if (!files.length) return;
          console.log(files);
          let rootFile;
          const fileMap = new Map();
          Array.from(files).forEach((file) => fileMap.set(file.name, file));
          Array.from(fileMap).forEach(([path, file]) => {
            if (file.name.match(/\.(gltf|glb)$/)) {
              rootFile = file;
              rootPath = path.replace(file.name, "");
            }
          });
          const fileUrl = URL.createObjectURL(rootFile);
          const gltf = await load(fileUrl, rootPath, fileMap);

function load(url, rootPath, assetMap) {
      const index = url.lastIndexOf("/");
      const baseURL = index === -1 ? "./" : url.substr(0, index + 1);
      const manager = new dt.LoadingManager();
      // Load.
      return new Promise((resolve, reject) => {
        manager.setURLModifier((url, path) => {
          const normalizedURL =
            rootPath +
            decodeURI(url)
            .replace(baseURL, "")
            .replace(/^(\.?\/)/, "");

          if (assetMap.has(normalizedURL)) {
            const blob = assetMap.get(normalizedURL);
            const blobURL = URL.createObjectURL(blob);
            blobURLs.push(blobURL);
            return blobURL;
          }
          return (path || "") + url;
        });
        const loader = new dt.GLTFLoader(manager).setCrossOrigin("anonymous");
        loader.setDRACOLoader(new dt.DRACOLoader());
        loader.setMeshoptDecoder(MeshoptDecoder);
        const blobURLs = [];
        let time = new Date().getTime();
        loader.load(url,(gltf) => {
            const scene = gltf.scene || gltf.scenes[0];
            const clips = gltf.animations || [];
            if (!scene) {
              throw new Error("This model contains no scene");
            }
            console.log("delta", new Date().getTime() - time);
            blobURLs.forEach(URL.revokeObjectURL);
            resolve(gltf);
          },
          undefined,
          reject
        );
      });
    }

總結

通過上述方式,可以寫簡單工具,幫助開發和建模人員隨時檢視模型的情況。

image.png
除gltf模型外,其他格式的模型,比如fbx或者obj,也可以類似操作。

如果對視覺化感興趣,可以和我交流,微信541002349。 關注公號“ITMan彪叔” 可以及時收到更多有價值的文章。

相關文章