zeux / meshoptimizer

Mesh optimization library that makes meshes smaller and faster to render
MIT License
5.49k stars 473 forks source link

gltfpack - Add flag to retain UVs. #715

Closed johnnyrainbow closed 2 months ago

johnnyrainbow commented 2 months ago

Currently compression with -c/-cc strips UV data if there is no texture present. This is not always wanted, and means we need to add a placeholder texture in blender for every object we wish to compress with meshoptimizer.

Add a flag to keep UV data regardless of if there is a texture present 🙏

zeux commented 2 months ago

This is not just an optimization unfortunately and is dictated by glTF specification. gltfpack does remove unused UV channels during mesh processing, however even if a flag was added I don't think it could work.

Specifically, gltfpack uses scene graph node transforms to store dequantization matrices for positions, and texture transforms - via KHR_texture_transform extension - to store dequantization matrices for texture coordinates. This is done unconditionally but glTF doesn't provide an alternate way to quantize texture coordinates without a bound texture outside of [0..1] range. And KHR_texture_transform extension data attaches to texture view on a material - without which there's nowhere to store this information.

johnnyrainbow commented 2 months ago

This is not just an optimization unfortunately and is dictated by glTF specification. gltfpack does remove unused UV channels during mesh processing, however even if a flag was added I don't think it could work.

Specifically, gltfpack uses scene graph node transforms to store dequantization matrices for positions, and texture transforms - via KHR_texture_transform extension - to store dequantization matrices for texture coordinates. This is done unconditionally but glTF doesn't provide an alternate way to quantize texture coordinates without a bound texture outside of [0..1] range. And KHR_texture_transform extension data attaches to texture view on a material - without which there's nowhere to store this information.

Could the retaining UV process be handled by meshoptimizer first adding a placeholder 1x1 texture to the model? Assuming the model is textureless anyway there wouldn't be much of a downside aside from a few extra bytes

zeux commented 2 months ago

That would work around the glTF representation problem, yes, it's essentially replicating your current workaround programmatically as I understand. I don't love the idea of doing this in gltfpack though.

Doing this fully correctly requires a significant amount of scene changes and for many parts of this gltfpack doesn't currently modify these at all. This requires adding an image (gltfpack doesn't add or remove images atm!), adding a texture object (gltfpack only prunes image collections or re-encodes existing images and doesn't have a great way of adding new images), adding a material object in case the mesh didn't use any materials (same as images, no current facility for this exists in gltfpack), and there's some edge cases that maybe need to be handled, such as multiple UV sets or material variants (likely irrelevant for your specific use case, but valuable for a generic option that promises to keep UVs).

I guess in general gltfpack is built as a opinionated tool with a small scope that is free to rebuild parts of the scene while preserving the scene's integrity; for things that are easy to customize and don't interfere I'm happy to add relevant options to preserve or restrict the edits, but patching in images and materials is not that.

Have you considered scripting the "add dummy images" task via glTF-Transform (https://github.com/donmccurdy/glTF-Transform) instead? I doubt this would be a good addition to glTF-Transform itself but it exposes a scripting API via JS/TS, and I assume it would be easy enough to write a script that performs this modification. That has a benefit of script being specific to your art workflow, so you don't need to handle any edge cases that are inconvenient, and may unlock future possibilities.

If that doesn't work, I think the only way to make this reasonable for gltfpack is to provide an option that keeps all vertex attributes (gltfpack also can remove tangents, normals and colors in certain cases), and does not promise correct behavior. This by itself would not work with quantization as I explained, however gltfpack already provides an option to disable that for texture coordinates only (-vtf). This will make the file larger, but maybe the gains will be acceptable, and using this option removes the need for dequantization transform so it would not lead to issues with absent textures. The reason why I'd want to just have one option is that there are some other niche use cases for keeping removed attributes, and I'd rather have a single option that doesn't guarantee compatibility with everything else, than add per attribute type options one by one.

donmccurdy commented 2 months ago

Quantization in glTF Transform currently skips UVs containing values outside the [0,1] range, so quantization does not rely on KHR_texture_transform and shouldn't require the “dummy images” workaround. There are still some operations that would remove unused UVs (notably prune and palette), but those are easily avoided. Of course, this does mean your UVs won't be compressed as well in glTF Transform if they exceed [0,1]. I'll implement the KHR_texture_transform approach at some point, if there's interest.

A quick test, after installing Node.js, would be:

npm install --global @gltf-transform/cli

gltfpack -i in.glb -o tmp.glb -noq
gltf-transform meshopt tmp.glb out.glb

This will quantize vertex attributes (a separate implementation), then use the meshoptimizer library for vertex cache optimization and compression. Nothing further is done by the gltf-transform meshopt CLI command, but gltfpack provides other optimizations, so you may want to run gltfpack first (with the -noq flag), saving compression until last. The process could also be scripted in JS/TS.