Compressed Texture Workflow (glTF|Basis)

I’m hitting GPU Memory problems with my current workflow of using glTFs with jpg and png images on mobile. Sadly I can’t find a good setup to avoid that.

I guess the next step is to either use GPU compressed texture formats like basis or to reduce the texture resolution. Sadly I haven’t figured out a good workflow for rcreating and linking optimized assets.

My questions are:

  1. Is the basis texture format already a feasible format for all platforms?
  2. Is it possible to link basis textures in glTF files?
  3. What tools do you use to create optimized textures (with Mipmaps / Texture Atlases)?
  4. Is there a possibility to check the available GPU memory to load low res textures for mobile devices?
  5. Is there an ETA for including basis textures in glTFs?

Right now, we’re using Blender to export the glTF files, which only supports exporting jpegs and pngs.
I would love to hear from your workflows to optimize the textures and models for the web!

We tested basis textures and are really satisfied with the reduction in filesize. So far I encountered some problems with 8k textures on mobile (rendering just black), but Im unsure what the cause is.

To answer some of your questions:

I converted the blender output texture from png to basis using the supplied tool and replaced the texture slot in the gltf manually (editing code in the model file).

The officially supplied converter from basis universal team. Creating mipmaps for the textures increases conversion time and filesize by a noticeable amount, but I couldnt see a difference in Performance or visual representation.

To throw in some Numbers: In one case the filesize on 4k textures went from 13.9 MB JPG to 7.0 MB basis, and in another from 7.4 MB to 0.53 MB without mipmaps and very little loss of detail.


Hi @weiserhei,

thanks a lot for your insights! So I guess you’re using the custom GLTFLoader from the basis live demo example (

As I understand there will be a ktx2 wrapper around basis files in glTF in the long run ( Is it worth waiting on that?

I see, so I might need to build a little automation tool.

That sounds really good! Thanks a lot lot for the real world dara comparison!

@weiserhei: I modified the gltf loader to support basis textures so done in
I also got the provided model there to work. Sadly when I try to put the PavingStone example from ThreeJS on a simple cube, the cube stays black. Here is the file I’m working with: (526.9 KB)

I edited the texture to point to the basis texture:
“textures” : [
“source” : 0
“images” : [
“mimeType” : “image/basis”,
“name” : “d_image_template”,
“uri” : “PavingStones.basis”
“extensionsUsed”: [
“extensionsRequired”: [

Any idea what I’m missing?

Your images-part looks correct. Make sure to include the necessary lib files and that they are loaded which are required by basis.
I imported your files into my project and without modifying anything this is the result:

Thanks a lot for your test. That’s interesting.

I tried digging a bit further, the logic for calling BasisTextureLoader and loading the texture seems to be correct, strangly it just is not rendering.
I made a small repro:

The front box is the one I uploaded here, which has the basis texture seemingly in the map property, but is not rendering it. The cube in the back is generated and the texture is set for the material manually, this one works as expected.

Forked repo:
glTF Loader | Html Repro file

Seems like you didn’t go down the road of using extensions. How did you change the loader to support basis textures?

I dont remember changing the loader, this Part should be running Out of the Box. I followed this article on medium:

Edit: i would have to Check the source Control to actually verify my Statements, they are Just coming from my memory :smile:

Additionally I have compressed and converted my gltf-basis Models using DRACO, but this doesnt affect your Cube.
Im not using Extensions, These lines are Missing in my Models - do you mind Sharing some information about this?

Ah, I stumbled over that post a while ago. As far as I can tell, it’s only about loading the textures themselves (as in the BasisTexture Loader example I integrated in the reproduction, which actually works). I guess my question then is: How did you connect the BasisTextureLoader with your GLTFLoader?

I wanted to use the extension “GOOGLE_texture_basis”, which they also use in their example. It will most likely be replaced, but from what I can tell, this might be the standard right now. My idea was that the extesion can signal which textureloader should be used. The problem is, I can’t tell which step does not work, since at least the basis texture is loaded and some texture is in the map channel of the material, but it does not show in the renderer.

The link to enable basis textures happens via the loading Manager. I guess this part of code should be doing the job, but its just an extract from my project for the sake of simplicity.

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { BasisTextureLoader } from "three/examples/jsm/loaders/BasisTextureLoader.js";

const basisLoader = new BasisTextureLoader(loadingManager);
loadingManager.addHandler(/\.basis$/i, basisLoader);

const gltfLoader = new GLTFLoader(loadingManager);

Then proceed to load your model and add it to the scene.

It will definitely be replaced. :sweat_smile: I made it up to get a functional demo, but it’s not an official standard at all sorry! The real extensions are in progress.

Hm, I actually do that as well. I tried using the default loading manager like so:

var basisLoader = new BasisTextureLoader();
basisLoader.setTranscoderPath( 'js/libs/basis/' );
basisLoader.detectSupport( renderer );
THREE.DefaultLoadingManager.addHandler( /\.basis$/, basisLoader );

// model
var loader = new GLTFLoader().setPath( 'models/gltf/BasisBlock/glTF-basis/' );

I also tried creating an own loading manager:

var loadingManager = new THREE.LoadingManager();
var basisLoader = new BasisTextureLoader(loadingManager);
basisLoader.setTranscoderPath( 'js/libs/basis/' );
basisLoader.detectSupport( renderer );
loadingManager.addHandler( /\.basis$/, basisLoader );

// model
var loader = new GLTFLoader(loadingManager).setPath( 'models/gltf/BasisBlock/glTF-basis/' );

I also tried using the default GLTFLoader without the extension support, since that is what you seem to do. I can’t put my finger on what I’m missing here…

Yout stated that quite clearly in the code, no worries. Still, since there was a working example out there, it was the first point for me to look at to see how it is implemented. Maybe you are able to tell me why the basis texture is not rendering? I followed your example quite rigorously. You can find it over here:

1 Like

Sorry for the delay on this. If you set texture.minFilter = texture.maxFilter = THREE.LinearFilter you’ll see the texture:

Those settings should probably be in the glTF file itself, as a sampler for the texture, but without an official extension it’s pretty DIY at the moment.


Aha! Thanks so much for the info. I somehow missed that in all the threads I read. Perfect, the example now works for me, finally I can join the basis party :partying_face:

1 Like

Hello, I want to ask whether the compressed PBR texture map needs code to compile. This is the basis of my first use of KTX2, thanks!

@WenBin_Liang it’s probably a bit too early to start using .ktx2 files. If you want to use Basis, for now, I would create .basis files and load them separately from your models.

It will be possible to embed .ktx2 files in .gltf and .glb files before too long, but not yet. The three.js part of that is being developed in, along with various tasks in the glTF and KTX-Software repositories.

Thanks for your solution! It works for me! :smile:

thanks for this very helpful thread. I learned a lot along the way. I’m working on adding .basis textures into a gltf. (I’m adding them manually) and then load the gltf according to the code snippets above. It seems to work generally, meaning the model is loaded and the textures seem to be loaded as well. However, I’m getting the following error:

WebGL: drawElements: texture bound to texture unit 1 is not renderable. It maybe non-power-of-2 and have incompatible texture filtering or is not ‘texture complete’, or it is a float/half-float type with linear filtering and without the relevant float/half-float linear extension enabled.

Now the texture is definitely a power-of-2. I also see these errors

[Warning] THREE.WebGLRenderer: WEBGL_compressed_texture_astc extension not supported.
[Warning] THREE.WebGLRenderer: EXT_texture_compression_bptc extension not supported.
[Warning] THREE.WebGLRenderer: WEBGL_compressed_texture_etc1 extension not supported.
[Warning] THREE.WebGLRenderer: WEBGL_compressed_texture_pvrtc extension not supported.
[Warning] THREE.WebGLRenderer: WEBKIT_WEBGL_compressed_texture_pvrtc extension not supported.

That is for Chrome 81.0 and Safari 13.1.

My goal is to use compressed textures inside glTF on desktop and mobile eventually.

The basis textures I created with the command line tool basisu tex.png

Any hints highly appreciated.

- mipmap was missing. Adding that to basis command-line fixed it

Some of the info here was great to get gLTF + basis loading correctly. It works fine if there is just one texture. When I attempt a model that has 3 different textures/images for 3 different meshes inside the gLTF, it seems to only be applying the first to all the meshes. I tried changing the name and order in the Images array and position or index in a lot of other places. Nothing seems to change which texture is applied to which mesh. Any ideas ?

There will be a new workflow available with three.js r119. It’s still experimental (the Basis extension for glTF is not complete yet, and things may change) but it’s likely to be more useable than the older workflows described earlier in this thread. Please try:

Note that the --zstd option for Basis UASTC is not yet supported in my viewer, and I’m not sure if that support will make it into r119 or not. The PR is Once r119 is released I will update as well (and the experimental version above may stop working).