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

DXT5/BC3 S3TCSRGB compressed texture is rendering in linear colour space (?) #6763

Closed
yaustar opened this issue Mar 5, 2024 · 7 comments
Closed
Assignees

Comments

@yaustar
Copy link

yaustar commented Mar 5, 2024

Version

  • Phaser Version: 3.80.1
  • Operating system: Mac
  • Browser: Chrome

Description

Using an example image and compressing to DXT5 SRGB with pvrtextool, it renders too dark (in linear colour space?)

Original image
runeGrey_rectangle_sheet

PVR Tex Tool settings
image

Rendered image
image

Example Test Code

Codepen: https://codepen.io/mikewong/pen/qBwdyaa

Additional Information

@BenjaminDRichards
Copy link
Collaborator

Thanks for putting compressed textures through their paces! It's good to make sure it's all working properly.

The Problem

I've completed a survey of WebGL's colour handling, and how various people have tried to solve it.

The problem is that WebGL just treats compressed textures as the darker color space. I believe that OpenGL has some facilities for adjusting this, but they do not exist in WebGL.

The good news is, everything in that color space is handled properly. I took your PNG and converted it to linear and SRGB PVR formats, then sampled individual pixels with PVRTexTool. The linear pixels have lower values than the SRGB pixels. However, both textures render exactly the same in WebGL (and they render darker than desired). Thus, we prove that WebGL is getting the same color values from the different textures, because it knows what color space they're encoded in. It's just getting that color space consistently dark.

Possible Solutions

It's possible to write shader code to apply gamma correction to texture samples, as part of a fully color managed pipeline. This is impractical for Phaser right now.

The more practical solution is to brighten the image instead. This is what many projects appear to have done around the web - but it's always been haphazardly implemented. Everybody gets this wrong! Projects borrow test images from other projects, and update them to newer formats like ASTC, but fail to apply the color adjustments, so some of their images render correctly and some render dark, within the same demos. This appears to be a very sparsely understood area of WebGL texture handling.

A Practical Solution

Here's what I've found to be the best solution:

  1. Create the base texture as a PNG. This could be a spritesheet out of TexturePacker, complete with texture atlas.
  2. Create an interim lightened PNG.
  3. Convert the lightened PNG to compressed textures as necessary.

Lightening Images

I used ImageMagick to correct PNG brightness. This is the OG of image adjustment tools and it does the job right, so far as I can tell. I have assumed that the issue is indeed sRGB/linear color conversion, and not some hidden gamma value.

I follow their discussion of sRGB Colorspace Correction. Basically, sRGB is a bit more complicated than a simple gamma curve, so it's best to be very specific about color conversion. That boils down to the following command:

magick image.png -set colorspace RGB -colorspace sRGB image-lightened.png

You can probably find other tools to do colorspace correction, but this one is direct and easy to automate.

Converting to Compressed Textures

Now encode your compressed textures from the new lightened data.

Converting with TexturePacker

TexturePacker will do this happily. Import the lightened image to a new project, select the appropriate encoding, and ensure that you've disabled all extrusion, margins etc. You just want the same image, with new encoding. TP also supports command-line encoding, if you have several types of encoding to do.

Accessibility: buy TexturePacker to access compressed texture options.

Converting with PVRTexTool

PVRTexTool is also very useful. It's not a texture packer, just a texture encoder, but the GUI is extremely good at showing you exactly what's going on with compressed textures. It also supports generating MIPMaps if you know exactly what you're doing (this is a quick way to crash WebGL, because MIPMaps have very strict requirements - basically, don't use MIPMaps unless your texture width and height are powers of two). It can also do command-line encoding, for those automated pipelines.

Accessibility: PVRTexTool is free, but requires a developer account to access the download.

Converting with ImageMagick

I was unable to find documentation for using ImageMagick to create the compressed textures directly, but it wouldn't surprise me if somebody out there has figured it out.

Accessibility: "magick" with a "K".

Conclusion

WebGL compressed textures must be lightened before encoding. Fortunately, the tools to do this are accessible. I hope this helps get the most out of compressed textures!

@yaustar
Copy link
Author

yaustar commented Mar 7, 2024

Thanks for providing the detailed instructions on how to convert the texture image 👍

@yaustar
Copy link
Author

yaustar commented Mar 7, 2024

Hm, still running into issues with the rendering (not sure if this should be a separate ticket) where semi transparent alpha images are rendering as solid colour https://codepen.io/mikewong/pen/NWmxKVa

Original image:
semi-alpha

Converted to DXT5 with Texture Packer to:
semi-transparent.pvr.zip

Which when previewed, looks like this:
image

But in Phaser 3.70.0 (doesn't run in Phaser 3.80.1 due to #6756), it looks like this: (note the squares underneath the arcades)
image

I'm loading the full atlas above via the multi texture atlas route in 3.80.1 as that renders but still has solid colour where there should be semi transparent pixels

@BenjaminDRichards
Copy link
Collaborator

This texture actually will work in 3.80! Change the type from S3TC to S3TCSRGB (WebGL implements these as different extensions, apparently because it was an early extension and they didn't know any better). It will appear darker, of course, but my machine produces the following results from your code:

Screenshot 2024-03-08 134934 Screenshot 2024-03-08 134951 Screenshot 2024-03-08 134957

The alpha certainly appears to be misbehaving when we're misinterpreting the data as linear S3TC in 3.70. I'm not certain why that is, but I suspect it's because we're just giving it the wrong data. I know there are some minor differences between S3TC and S3TCSRGB beyond just the colorspace; for example, the S3TCSRGB extension has different rules for MIPMap dimensions. I'm not sure it's worth figuring out the exact reason why it's getting alpha wrong, though, because it was never intended to be used this way. Phaser 3.80 is correct - or at least as correct as I can understand to make it!


WebGL can be a bit obtuse with the error codes. We've tried to make them a bit friendlier, so if you try to load this texture with S3TC it says Compressed Texture Invalid: "labs". Texture format S3TC with internal format 35919 not supported by the GPU. Texture invalid. This is often due to the texture using sRGB instead of linear RGB.. But what is internal format 35919? All the documentation around WebGL uses hexcodes instead. We can use the browser console to get (35919).toString(16) = 0x8c4f, which will eventually lead you to the format COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT. In Phaser 3.70 we accidentally interpreted it as COMPRESSED_RGBA_S3TC_DXT5_EXT.

The other way to see what you're looking at is to just use PVRTexTool, which will open the PVR and immediately tell you BC3 UNorm sRGB in the bottom-left corner. BC3 means DXT5, which is part of the S3TC/S3TCSRGB set of formats; UNorm means it's in the data representation expected for WebGL, unsigned as opposed to signed; and sRGB means sRGB. PVRTexTool also shows me that the alpha values are encoded as expected in the squares you pointed out (75 instead of 255).

I hope that helps you properly identify texture compression! It's pretty obscure stuff unless you know where to look.

@yaustar
Copy link
Author

yaustar commented Mar 12, 2024

This texture actually will work in 3.80!

Ah, changing it to S3TCSRGB works for the codepen but not in our actual project 🤔 . We are loading it a different way but don't see errors in the console.log

        loader.texture('somekey', {
          S3TCSRGB: { type: "PVR", multiAtlasURL: { textures: [someJsonData] } },
        }),

Edit: That said, it is working here: https://codepen.io/mikewong/pen/NWmxKVa?editors=0010. Maybe my file is the issue, it's been a while since I last looked it

Edit2: Rexported the texture with Texture Packer which got it loading but even with 3.80.1 which solves the loading issue but looks like the step of lightening ruins the semi transparency areas.

(What it should look like on the left, what it looks like with DXT5 sRGB on the right)
image

@BenjaminDRichards
Copy link
Collaborator

Apologies, I didn't see this until just now. GitHub is not very good at showing me updates for some reason!

This is difficult to debug without looking at the actual files, I'm afraid. Is it possible to link the relevant files? Original, lightened, and compressed would give me good points to check. Obviously the texture was encoded incorrectly - it looks like the alpha is 1-bit, which looks more like DXT1/BC1 than DXT5. Or maybe premultiplication did something unexpected.

@yaustar
Copy link
Author

yaustar commented Apr 2, 2024

This is difficult to debug without looking at the actual files, I'm afraid. Is it possible to link the relevant files?

I will have to come back to this in a week or so as it's already taking too much dev time on my end trying to debug the issue and we've already deprioritised it.

I doubt it's treating the alpha as 1 bit otherwise the area highlighted here, you wouldn't be able to see the ground through it

image

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

No branches or pull requests

3 participants