Progressive textures loading for GLTF

ProgressiveGltfTexturesLoader

I often use imgproxy.net to optimize images on my websites. Frequently, I create LQIPs (low-quality image placeholders), a technique that involves initially loading a super tiny, blurry image (2-5 KB) where a high-resolution image will eventually be placed, and then loading the high-quality image in its place. Imgproxy creates LQIPs and optimized images on-the-fly without manual work, so in storage, we only have one version of the image with maximum quality.

I thought it would be great to have something like LQIP for GLTF models. So, I created a small library for this purpose.

To achieve this effect, we should save GLTF files as gltf+bin+textures, as we want to have direct access to the textures. After that, we can import the library:

import { ProgressiveGltfTexturesLoader, ProgressiveGltfTexturesLoaderOptions } from './utils-3d/progressive-gltf-textures-loader';

Then, after create the engine object we can register plugin with options:

SceneLoader.RegisterPlugin(new ProgressiveGltfTexturesLoader({
  engine: this.engine,
  rules: [{
    match: [
      '/Assets/DamagedHelmet/glTF/*.jpg',
      '/Assets/DamagedHelmet/glTF/*.png',
    ],
    variations: [
      'rs:fill:64:64/q:80',
      'rs:fill:128:128/q:60',
      'rs:fill:256:256/q:60',
      'rs:fill:512:512/q:60',
      'q:87',
    ],
  }],
  replacer: (
    url: string,
    config: ProgressiveGltfTexturesLoaderOptions['rules'][number],
    variation: ProgressiveGltfTexturesLoaderOptions['rules'][number]['variations'][number]
  ) => {
    // Change original to URL to imgproxy path
    return `http://localhost:1234/insecure/${variation}/plain/${url}`;
  }
}));

Here I describe the rule, when progressive loading shoul work. In this case we work with images by mask:
/Assets/DamagedHelmet/glTF/*.jpg and /Assets/DamagedHelmet/glTF/*.png.

Then we define image variations for this rule, here you can see 5 variations with different size and quality. The last (hi res) variant does not change the size, just define quality.

And replacer function that gots original URL and varation that the progressive loader wants to use at this moment. Here I define the imgproxy standalone version domain with port, and configure the url.

So, lastly we shoult initiate this process when we want, for example, immidiately after loading gltf:

SceneLoader.Append('https://www.babylonjs.com/Assets/DamagedHelmet/glTF/', 'DamagedHelmet.gltf', this.scene, scene => {
  scene.meshes.forEach((mesh) => gltfLoader.initProgressiveLoading(mesh));
});

without initProgressiveLoading call we got a model with minimal quality from variations config:

But, when we call this method we reach the progressive loading:

ProgressiveGltfTexturesLoader

You can see the library here with detailed readme:

9 Likes

I wonder whether this could reuse msft_lod or it is fully different ?

In theory LODs are related to distance or screen coverage.

But progressive loading is used for faster loading the first state without relation to current distance.
In theory, it gives your faster textures on the device:

  1. Load low-res textures 128×128px with 30 KB (about)
  2. User see the scene and can interactive with.
  3. Load high-res textures 2048×2048px that can be 1-2 MB.
    And here doesn’t matter when it happens, after a second or a minute. Anyway user can see your scene early.

And after loading high-res textures the script stops. But otherwise the LOD system should be updated every frame to check the distance to the camera and change the LOD level. And usage of low res textures when the model is far should work under the hood via mipmaps.

I’m gonna take this opportunity to ask: Is there a progressive loading of textures in BJS (I mean from materials and textures created in BJS)? Thanks,

I did not meet something like this in the documentation. You can manually change the texture url, or swap textures. As an example, here (progressive-gltf-textures-loader/progressive-gltf-textures-loader.ts at main · Dok11/progressive-gltf-textures-loader · GitHub) you can read an example. Here I pass the pixel buffer because the updateURL without buffer hide the texture while new texture are loading.

1 Like