Progressively Load .glTF Files
Introduction
Downloading large assets is often slow even with a decent internet connection. Progressively loading discrete levels of detail (LODs) is one technique to combat this. The idea is to store multiple LODs in the asset and progressively load them from low to high quality. This method improves the time to first render since the lowest quality LOD is often dramatically smaller than the highest quality LOD.
Progressive Loading With LODsThis demo playground loads a glTF binary (a.k.a. GLB) asset which is a single file store on the server. It uses HTTP range requests to partially download parts of the GLB. The glTF loader minimizes the amount of HTTP requests sent to the server for optimal efficiency. It also shows progress of each stage of the download. Console logging is enabled to show what the glTF loader is doing.
About MSFT_lod
MSFT_lod is a Microsoft glTF extension for discrete LODs. It has support for both node and material LODs. Babylon.js currently only supports progressively loading these LODs for the purpose of reducing the time to first render. LODs that switch based on rendering distance or screen coverage is not currently supported.
There are not many tools that can create assets with MSFT_lod at the moment. The asset in the demo above is exported from Adobe Dimension for sharing on the web. BabylonPolymorph will eventually be able to do this, but this project is still very early. There is also glTF-Toolkit for Windows Mixed Reality which unfortunately is not well suited for Babylon.js, but perhaps it can be modified to work better.
Using HTTP Range Requests
A glTF asset can be either loose files or packed together into a glTF binary (GLB). Serving files as GLB on a server is typically not a good idea except when using HTTP range requests. Using range requests will enable the loader to partially download a range of the asset instead of all at once. The glTF loader in Babylon.js supports range requests. It can be enabled like this:
BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce(function (loader) {if (loader.name === "gltf") {loader.useRangeRequests = true;}});
Caveats
- The HTTP server hosting the asset must support range requests.
- The LODs in the GLB should be authored with a contiguous range per LOD for maximum efficiency.
Showing Progress
When loading large assets either using loose files or with range requests, it is useful to show the download progress. Progress is supported through the progress callback of BABYLON.SceneLoader
methods which is a small subset of the HTTP request progress event. Here is an example from the demo above:
BABYLON.SceneLoader.AppendAsync(url, undefined, scene, function (event) {// Compute the percentage for each stage unless the length is not computable.// The lengthComputable is often false when serving content that is gzipped.const percentage = event.lengthComputable ? " " + Math.floor(event.loaded / event.total * 100) + "%" : "";// Check if an LOD is loading yet.if (lodNext === null) {// Ignore GLB header progress.if (event.total === 20) return;// Show that the glTF is downloading.bottomLine.text = "Loading glTF..." + percentage;}else {// Show that the LOD is downloading.bottomLine.text = "Loading '" + lodNames[lodNext] + "' LOD..." + percentage;}}, ".glb")
Key Notes
- Gzipped content hosted on server often results in
lengthComputable
equalingfalse
which in turn causes thetotal
to be zero. When this happens, the only choices are to not show the progress or show the number of bytes downloaded instead. - When using HTTP range requests with a GLB, the first thing that is downloaded is the GLB header which downloads very quickly and is almost always 20 bytes loaded and 20 bytes total when the progress event fires. Ignore it by checking when
total
is exactly 20.
Debugging LODs
It is useful to stop at a specific LOD to inspect the results. This can be achieved by setting the maxLODsToLoad
property on the MSFT_lod loader extension:
BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce(function (loader) {if (loader.name === "gltf") {loader.onExtensionLoadedObservable.add(function (extension) {if (extension.name === "MSFT_lod") {// Stop at the first LOD.extension.maxLODsToLoad = 1;}}}}
Enabling Logging
Enabling logging is often useful to understand and debug the loading of a glTF asset. This is especially true for MSFT_lod assets. Logging can be enabled like this:
BABYLON.SceneLoader.OnPluginActivatedObservable.addOnce(function (loader) {if (loader.name === "gltf") {loader.loggingEnabled = true;}});
Here is an example console log from the demo above:
BJS - [16:31:29]: Binary version: 2BJS - [16:31:29]: JSON length: 59200BJS - [16:31:29]: Loading 0BJS - [16:31:29]: Asset version: 2.0BJS - [16:31:29]: Asset generator: Adobe Dimension - 9ce31d2be91132df879a9b9136f9df4ef7b7fa2cBJS - [16:31:29]: LOADINGBJS - [16:31:29]: /scenes/0 sceneBJS - [16:31:29]: /nodes/0 render_camera_n3dBJS - [16:31:29]: /cameras/0 render_cameraBJS - [16:31:29]: /nodes/1 ground_plane_n3dBJS - [16:31:29]: /nodes/2 sportsCar_4KBJS - [16:31:29]: /nodes/3 car_lores.objBJS - [16:31:29]: /nodes/4 g car_lores_gt_lores_lowdetails group1BJS - [16:31:29]: /nodes/5 car_lores_gt_lores_lowdetails group1BJS - [16:31:29]: /meshes/0 car_lores_gt_lores_lowdetails group1BJS - [16:31:29]: /meshes/0/primitives/0BJS - [16:31:29]: deferredBJS - [16:31:30]: /materials/12/extensions/MSFT_lodBJS - [16:31:30]: /materials/0 details_lod2BJS - [16:31:30]: /materials/0/normalTextureBJS - [16:31:30]: /textures/2 tmp_image_pie_10f1_2c55_943eBJS - [16:31:30]: /images/2 normalBJS - [16:31:30]: deferredBJS - [16:31:30]: /materials/0/occlusionTextureBJS - [16:31:30]: /textures/1 ambient_roughness_metallicBJS - [16:31:30]: /images/1 ambient_roughness_metallicBJS - [16:31:30]: deferred...BJS - [16:31:32]: Loading buffer range [0-4864522]BJS - [16:31:37]: READYBJS - [16:31:37]: Loading buffer range [4864523-8856965]BJS - [16:31:39]: Loaded material LOD 1BJS - [16:31:39]: Loading buffer range [8856966-67953580]BJS - [16:32:00]: Loaded material LOD 2BJS - [16:32:00]: COMPLETE
Note the deferred
message when loading primitives and images. The loader is deferring the download until it has determined what ranges of the GLB is necessary for the LODs. Once the ranges are determined, the loader loads the range for the first LOD before the state changes to READY
which indicates the asset is ready for viewing. Then it loads the remaining LODs in sequence until all the LODs are loaded before the state changes to COMPELTE
which indicates the asset is completely loaded.