Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Specific file results in very bad compression results when mipmaps are enabled #759

Closed
hybridherbst opened this issue Aug 31, 2023 · 21 comments

Comments

@hybridherbst
Copy link

hybridherbst commented Aug 31, 2023

Related to

A specific file ends up with very bad quality after compression, but only if mipmaps are enabled.

This texture here: https://github.com/donmccurdy/glTF-Transform/files/12327580/watch.zip

Compression without mipmaps:
image

Compression with mipmaps:
image

Compressing without mipmaps results in expected quality. This is the first time we're hitting a particular texture giving such results, however an outlier like this is quite problematic for the use of KTX in production pipelines.

@MarkCallow
Copy link
Collaborator

Please list the command and options that is being used.

Images are compressed to ETC1S individually. BasisLZ is then applied to all levels, starting from the largest, using a global codebook from which previously seen runs of data may be reused. It is therefore most unlikely that adding mipmaps could affect the quality of the base image. @hybridherbst are you sure the 2 images you posted here are displaying the same miplevel?

@hybridherbst
Copy link
Author

@MarkCallow I'm not sure if this displays the same mip level but I am sure that this is the mip level viewers like three.js and others choose when displaying the file. In the original zip I added glb files where the ktx2 textures have and have not mipmaps as per the commands below.

Command is pretty much "default options for a linear file" as far as I can tell:

toktx --genmipmap --bcmp --assign_oetf linear --assign_primaries none metallicRoughness.mips.ktx2 metallicRoughness.jpg

vs.

toktx --bcmp --assign_oetf linear --assign_primaries none metallicRoughness.ktx2 metallicRoughness.jpg

Here's the extracted textures again:
metallicRoughness.zip

@hybridherbst
Copy link
Author

hybridherbst commented Sep 1, 2023

I found a viewer that lets me take a closer look at the invidiual mip levels. It appears to me that for this particular file, mip levels feel lower resolution much faster than they should.

This is mip 4 when using --uastc:
image

And this is mip 4 when using --bcmp:
image

Mip 2 when using --uastc:
image

Mip 2 when using --bcmp:
image

Mip 0 also already looks "not great" but not that bad as the others. I understand there should be some difference but not that strong. Is libktx calculating each mip level from the original texture, or is it calculating them from each other iteratively? In that case I think the latter may be going wrong.

Also I can confirm what you said, that mip 0 does indeed look right (identical in etc1s with and without mipmaps), it's the lower mip levels that are broken.
image

@MarkCallow
Copy link
Collaborator

I don't think there is anything unexpected here. Here is an image showing all the mip levels in an uncompressed version of the texture:

Screenshot 2023-09-01 at 21 46 34

Here is the image using UASTC. Virtually the same:

Screenshot 2023-09-01 at 21 46 56

And here is the image using ETC1S/Basis-LZ:

Screenshot 2023-09-01 at 21 47 03

With ETC1S the smaller mip levels are noticeably worse but as the source data is (a) not color data (which is what ETC is designed for) and (b) has already been compressed with JPEG, the degradation is expected.

@hybridherbst
Copy link
Author

hybridherbst commented Sep 1, 2023

I disagree. Almost identical textures don't result in the same level of quality loss; this is the first time we're seeing that level of degradation over thousands of processed textures. And yes, we're using ETC also for data textures in some cases where file size is more important, and so far that has never resulted in such bad compression.

Please don't get me wrong, I just want to figure out where this bug comes from and if there's a way to avoid it - we're super happy with libktx in general and from my perspective this is an outlier.

@MarkCallow
Copy link
Collaborator

MarkCallow commented Sep 2, 2023

Is libktx calculating each mip level from the original texture, or is it calculating them from each other iteratively? In that case I think the latter may be going wrong.

Each level is calculated from the original texture. The screen shot I posted of the uncompressed texture shows the images that will be input to the BasisU encoders.

I disagree. Almost identical textures don't result in the same level of quality loss; this is the first time we're seeing that level of degradation over thousands of processed textures. And yes, we're using ETC also for data textures in some cases where file size is more important, and so far that has never resulted in such bad compression.

@hybridherbst have you made similar metallic roughness textures using only the default ETC1S/Basis-LZ parameters and not had this problem? If so,

  1. what is different between ones without the problem and this one?
  2. please provide an example that does not have this problem.

Please try the interactive glTF-Compressor Tool and see if you can get better results for this texture by fine tuning the compression parameters.

If something is going on it is happening within the BasisU ETC1S encoder and is outside my expertise. Therefore I must consult @richgel999. Sorry to bother you Rich but please give us your opinion about this. Follow this link metallicRoughness.zip for the original jpg. The link provided in the initial comment only has .glb files.

@MarkCallow
Copy link
Collaborator

@hybridherbst have you found a similar metallic-roughness texture that does not display this issue? Have you tried the interactive tool?

@MarkCallow
Copy link
Collaborator

MarkCallow commented Nov 28, 2023

The source file metallicRoughness.jpg is sRGB which you are overriding with --assign_oetf linear thereby causing the mipmap generator to filter in sRGB space which is mathematically incorrect. If you remove --assign_oetf linear the jaggies will disappear. Never, ever use --assign_oetf linear unless you are absolutely certain that the metadata in the source file is wrong.

@donmccurdy
Copy link
Contributor

donmccurdy commented Feb 18, 2024

you are overriding with --assign_oetf linear thereby causing the mipmap generator to filter in sRGB space

Did you mean "to filter in linear space" here? I don't quite understand this statement... is specifying --assign-oetf linear causing an sRGB encoding somewhere?

Data textures (like metallic/roughness) are always linear in glTF. They cannot be sRGB, and the glTF spec is strict about saying that metadata in the file MUST be ignored, because the metadata is so often wrong... Tools processing glTF files should always use --assign_oetf as a result.

If the source file really has sRGB-encoded the metallic roughness data, though, data loss is baked into the source file.

@hybridherbst
Copy link
Author

We're using etc1s for data textures fine, still besides this one singular texture which produces so much worse results.

I'm not sure I understand both of your comments above – seems like Mark is saying "never use --assign_oetf linear" while gltf-transform always uses --assign_oetf linear for, well, linear data. What is the correct approach here?

@donmccurdy
Copy link
Contributor

I don't think there's any other choice glTF Transform could make but --assign_oetf linear. It's very rare that metal/rough data is actually sRGB-encoded (and always incorrect), but very common that the metadata is wrong. I can't tell if that was the cause here, it sounds plausible... but it's hard to tell with metal/rough data.

Certainly unfortunate that image metadata is such a mess though. :/

@MarkCallow
Copy link
Collaborator

Did you mean "to filter in linear space" here?

Yes I meant filter in linear space. The data, according to the file metadata, is sRGB and when I removed the -assign-oetf linear from the command I did not see the excessive jaggies in the resulting texture. This suggests the file metadata is correct. Therefore if the source file is intended for metallic/roughness or other data use, it is incorrect and that is where the problem lies.

@donmccurdy glTF-Transform should issue a warning if someone tries to use an image whose metadata says sRGB for data use. Image tools these days generally set the metadata correctly so such a mismatch suggests a cockpit error when producing the image.

What is the correct approach here?

Make sure the source image is linear.

@MarkCallow
Copy link
Collaborator

I don't think there's any other choice glTF Transform could make but --assign_oetf linear

Note that if you specify a UNORM format to --format ktx create will convert the input from sRGB to linear (if the metadata indicates its sRGB). Mipmaps will first be correctly generated by decoding the sRGB data before filtering and re-encoding before the final conversion to linear takes place (these are separate steps within the application). If you force --assign-oetf linear then not only are the mipmaps screwed up because the encoded data was filtered but sRGB data is then uploaded unchanged to the UNORM texture so when sampled in the the shader will receive sRGB encoded data.

As I wrote earlier you should be really sure the metadata is wrong before forcing linear. @donmccurdy do you have any evidence that the metadata is wrong for reasons other than cockpit error?

@donmccurdy
Copy link
Contributor

@MarkCallow I have no evidence either way for the specific texture in this thread. It could certainly be user error!

But in the broader 3D modeling ecosystem, I would not describe the problem as one of user error. These decisions are being made at lower levels of software abstraction than most users understand or control — they export a model from Substance Painter, Maya, 3DS Max, or Blender, with 5-10 textures embedded. They do not know which texture uses which color space, and they probably don't have control over that choice even if they do understand it.

For a quick test, I opened two examples from the glTF Sample Models repository. These are simply the first two samples I opened — I did not have to go digging.

Mosquito In Amber:

role profile in metadata valid
baseColor sRGB
metallicRoughness sRGB
normal -
baseColor sRGB
normal sRGB

Sheen Chair:

role profile in metadata valid
baseColor -
normal -
baseColor -
occlusion -
baseColor -
metallicRoughness sRGB
metallicRoughness -

I agree that "image tools these days generally set the metadata correctly", but only in the context of tools for opening and editing one texture at a time. Within the context of a larger 3D art workflow with many models and textures, there are "weak links" more often than not. For three.js, the web platform does not even give us the ability to read the color space of a JPEG or PNG texture (KTX2 is an exception) without duplicating the texture in memory.

I'd be glad to see the glTF Validator, glTF Transform, and other tools flag the issue more clearly. Perhaps we can improve the state of the 3D ecosystem. But it's a problem that long predates glTF, and I consider the image metadata to be mostly useless today, outside of very carefully controlled workflows.

@MarkCallow
Copy link
Collaborator

MarkCallow commented Feb 20, 2024

@donmccurdy there is a difference between the metadata being wrong or non-existent and the colorspace of the source being inappropriate for the task. --assign-oetf is the right choice in the first case. Letting ktx create to do its job and convert the sRGB input to linear is the right choice in the second case. It will do this when the selected VkFormat is a UNORM format. Forcing linear leads to the issue in this bug. There will still be artifacts from having used an inappropriate encoding on the source file due to values that are lost during the initial encoding but far less than you will get from treating sRGB data as linear throughout texture creation and rendering.

@donmccurdy
Copy link
Contributor

donmccurdy commented Feb 20, 2024

@MarkCallow In my experience the second case — data using an inappropriate colorspace with correct metadata — is rare. It's overwhelmingly more common that the metadata is wrong. Easily a 100:1 ratio. Perhaps this ticket represents an exception, but I can't change the default behavior based on that.

It doesn't seem that there's a general solution here, at the level of either KTX Software or glTF Transform. As you said above, the texture data must be linear.

@MarkCallow
Copy link
Collaborator

In my experience the second case — data using an inappropriate colorspace with correct metadata — is rare. It's overwhelmingly more common that the metadata is wrong.

@donmccurdy how do you know this? My experience with image editing tools is that encoding the data and setting metadata tend to go together when writing a file.

In my previous comment I should have noted that a better choice for the second case is --convert-oetf linear. With this the data is converted to linear before mipmap generation, saving the time that decoding and encoding takes during that process.

@MarkCallow
Copy link
Collaborator

@donmccurdy how do you know this?

Pinging again.

For example in the following case

metallicRoughness sRGB ❌

how do you know the metadata is wrong vs. the file being inadvertently encoded into sRGB?

I think it is basically a guess in which case glTF-Compressor should warn the user that either the metadata is wrong or the wrong encoding has been used. It should have an option or options for the user to indicate which is the case: --assign-oetf for the first and --convert -oetf for the second.

@donmccurdy
Copy link
Contributor

donmccurdy commented Feb 23, 2024

how do you know this? My experience with image editing tools is that encoding the data and setting metadata tend to go together when writing a file.

This is simply my experience with PBR materials and their associated textures. It's also the reason why glTF specifies ICC profiles must be ignored. I agree that modern "image editing tools" are pretty good about color spaces. But 3D art pipelines are more complicated and less consistent. In particular — I think the distinction between "sRGB color" and "non-color data" is not consistently represented in metadata. Metadata for wide gamut colorspaces like "Display P3" or "Rec. 2020" is less likely to be a side effect of broken workflows, and is therefore more reliable when present.

@MarkCallow
Copy link
Collaborator

| I think the distinction between "sRGB color" and "non-color data" is not consistently represented in metadata.

Again I ask how can you tell if the metadata contradicts what's actually in the file or if the non-color data has been incorrectly (as in "it has but should not have been") encoded into sRGB.

Is there any image metadata that describes a distinction between color and non-color data? Metadata offering a choice between linear and sRGB does not. The latter simply says the input data has been encoded into a non-linear space.

I still think glTF-Compressor should inform the user and provide them the opportunity to guide it to a resolution instead of deciding the meta always contradicts the data that is actually in the file. That can lead to much worse results than decoding the sRGB data, if that is what it is, and using that.

@donmccurdy
Copy link
Contributor

Again I ask how can you tell if the metadata contradicts what's actually in the file ...

I cannot look at a JPG containing metal/rough data, annotated as "sRGB", and tell you with confidence whether the metadata is wrong, or the actual choice of encoding is wrong. Other than a probabilistic expectation that it's usually the metadata. Is that what you're asking?

Is there any image metadata that describes a distinction between color and non-color data?

None that I'm aware of. Omission of an ICC profile perhaps, but I suspect many tools will simply interpret that as "sRGB".

I still think glTF-Compressor should inform the user and ...

Ok — I have no particular opinion on what the glTF Compressor should do. I'd certainly be supportive of getting the glTF Validator to report inconsistent image color space metadata.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants