GLB Optimizer for Geometry and Texture Conversion (WEBP and KTX2)

GLB KTX Optimizer updated! - https://glb.babylonpress.org/
Thanks to @Andrew_M suggestion another button added: Wireframe Mode.

Click this button to toggle between usual rendering mode and wireframe mode.

Rendering mode settings are saved and applied after instant reload (“R” key) and after usual page reload.
To hide the bottom menu bar press ‘Space’.

1 Like

@labris
There’s something wrong with this amazing website!!!

Should work now :slight_smile:

Yeah, it works well, but there is a real fear that one day the site won’t work, and I use it almost every day :pensive:

Should be available for a long time :slight_smile:
(just need to tune better my automatic hosting payments).

I am making some improvements, especially with KTX textures conversion speed, and will make the repo public when the code will be in suitable condition.

:+1: :+1: :+1:

Hey @labris, and thanks for this great tool. It has been really helpful for asset creation within the small team that I am working with. I have a feature request (below), but would also like to offer to help with it’s development, if it benefits you.

We are using KTX textures, and would like to be able to use UTASC for some (normals, high frequency detail maps, etc) and ETC1 for the others (albedo, metallic, etc). For that purpose, we would like to be able to assign different compression methods to PBR material texture inputs. It is a feature available in glTF-Transform which we would like to port or emulate to your web-UI based tool.

Thanks again, and let me know how or if you would like any help,
r

Hello @rdurnin and thank you for the feedback! I am very glad that this tool is useful.
Here is my latest optimization demo (not yet finished completely) - BabylonPress WORKSHOP
The size of 70 GLB models is less than 45 Mb (1024*1024 texture resolution).

This feature could be quite easily implemented. To make convenient UI for it is harder but possible :slight_smile:
I was going also to implement another worker pool for KTX conversion, so it would be much faster (at least I hope so).
Among other planned improvements there are:

  • More user settings for saved file names (user prefix, postfix, etc)
  • User color for comparison screenshots and the ability to choose texture channel to compare
  • center() function to center the model
  • Change all materials transparency mode (often needed for FBX-converted models)
  • Change all materials backface culling
  • Add the ability to read/add metadata like author, copyright etc
  • Multiple files processing
  • Continue according your needs :slight_smile:

I’ll prepare the repo and will send you DM. Your help will be very much appreciated!

Greetings, kind of late to the party here but is there an API we could use to build a backend service that uses this optimizer?

1 Like

Could you be more specific?

@labris . So what I see above is a web based tool to drag and drop a glb file. Looks like the user can save back out an optimized glb file. I want to do the same without a UI as a backend service. Ideally, the backend service would take an input glb, run through the same optimization steps as the webapp and then save the optimized glb in blob storage somewhere. The details of the backend service isnt important here, several ways that can be achieved , what Id like is if there is an API I can use to do what the webapp does. Do you have the API available somewhere?

My tool is based (among several other packages) mainly on GLTF-Transform - https://gltf-transform.dev/ which has quite detailed API and CLI as well.
Actually, you can do everything with this API with the same settings as in GLB Optimizer. The main difference it that GLTF-Transform doesn’t provide any visual comparison which is done with Babylon screenshots processed through Pixel Match.
Another thing is that, since GLB Optimizer is based on Sandbox, is should accept URLS with parameters. But this function, while working in the Sandbox, is temporarily switched off in my GLB Optimizer.
If you have a lot of user uploaded GLBs it is possible to implement instant optimization “on-the-fly” when uploaded GLB is automatically optimized at the loading stage.
Let me know if you have any questions!

2 Likes

Here is the latest update - https://glb.babylonpress.org/
To choose the mode (ETC/UASTC) for each texture slot choose KTX2/USER in the Texture Format drop-up list, then check needed slots in the ‘User defined KTX2 Compression for Texture Slots’ panel in Settings.
Let me know if you’ll have any questions and suggestions!

It is a pity but detail map are not supported in GLTF/GLB format, they should be provided separately. Currently I am testing the feature which will allow to convert images to UASTC and download them in .ktx2 format (actually it works already but needs better interface).

1 Like

Wow, and thanks! Can I select to use ECT1S for the textures which don’t use UASTC or is that implied through the menu?

Thanks again, and I will look through any release code this weekend,
r

Slots which are not checked are processed with ETC1S, all checked with UASTC (don’t forget to choose KTX2/USER mode first).
image
In case of UI bugs just reload the page :slight_smile:

Fab, and thanks a lot. How can I help you in return?

r

Let’s try to prepare the code to be open source!
It definitely needs some improvements before release :slight_smile:

1 Like

This is a great tool! Thank you for sharing this.

1000kb (original mesh)

  • becomes 650kb with your webp formatter
  • becomes 400kb when gzipped (or 350kb with xz)

:blush:

Note: I have been also using GZIP instead of XZ since it is built into modern browsers (Along with TAR using an addon) and it’s very performant.

Here’s a quick example of how to do GZIP on the frontend.

<h1>GZIP</h1>

<h2>COMPRESS</h2>
<input type="file" id="gzip-file-input"/>

<h2>DECOMPRESS</h2>
<input type="file" id="gunzip-file-input"/>

<script type="text/javascript">
async function gzip() {
    let uploadField = document.getElementById("gzip-file-input");
    let blobFile;

    // File reader
    let fileReader = new FileReader();
    fileReader.onload = async function(e) {
        blobFile = new Blob([e.target.result], { type: 'application/octet-stream' });

        // Compress the tar blob with gzip
        const inputReadableStream = blobFile.stream();
        const compressedStream = inputReadableStream.pipeThrough(new CompressionStream("gzip"));

        // Convert the compressed stream back to a Blob
        const chunks = [];
        const reader = compressedStream.getReader();

        let result;
        while (!(result = await reader.read()).done) {
            chunks.push(result.value);
        }
        const gzipBlob = new Blob(chunks, { type: 'application/gzip' });

        // Download gzip file with the original file name + ".gz"
        const url = URL.createObjectURL(gzipBlob);
        const link = document.createElement('a');
        link.href = url;
        link.download = uploadField.files[0].name  + ".gz";
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    // Use readAsArrayBuffer to keep binary data intact
    if (uploadField.files.length > 0) {
        fileReader.readAsArrayBuffer(uploadField.files[0]);
    }
}

async function gunzip() {
    let uploadField = document.getElementById("gunzip-file-input");

    // Get the compressed file as an ArrayBuffer
    let arrayBuffer = await uploadField.files[0].arrayBuffer();
    const compressedBlob = new Blob([arrayBuffer], { type: 'application/gzip' });
    const decompressionStream = new DecompressionStream("gzip");
    const decompressedStream = compressedBlob.stream().pipeThrough(decompressionStream);

    // Convert the decompressed stream into a Blob
    const decompressedChunks = [];
    const reader = decompressedStream.getReader();
    let readResult;
    while (!(readResult = await reader.read()).done) {
        decompressedChunks.push(readResult.value);
    }

    // Attempt to get the original MIME type if known, or default to octet-stream
    const decompressedBlob = new Blob(decompressedChunks, {
        type: uploadField.files[0].type || 'application/octet-stream'
    });

    // Download file
    const url = URL.createObjectURL(decompressedBlob);
    const link = document.createElement('a');
    link.href = url;
    link.download = uploadField.files[0].name.replace(/\.gz$/i, '');
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
}

document.getElementById("gzip-file-input").addEventListener("change", gzip);
document.getElementById("gunzip-file-input").addEventListener("change", gunzip);
</script>
3 Likes

Using another character I made I was able to go from 4.62mb to 1.28mb to 647kb gzipped. This is incredible!

@labris If I ever make any money from my games, I’m going to buy you a coffee!

2 Likes

How gracious of you. You’re a real giver :grinning: :joy:

3 Likes