BinomialLLC / basis_universal

Basis Universal GPU Texture Codec
Apache License 2.0
2.67k stars 260 forks source link

Saving and loading two-channel normal maps (ASTC) #346

Open UltraEngine opened 1 year ago

UltraEngine commented 1 year ago

I'm trying to implement platform-independent compressed normal maps that use Z reconstruction in the shader.

It looks like this is the correct way to save a two-channel image:

m_comp_params.m_swizzle[0] = 0;
m_comp_params.m_swizzle[1] = 0;
m_comp_params.m_swizzle[2] = 0;
m_comp_params.m_swizzle[3] = 1;
I am working on the assumption that a two-channel image should always be transcoded into BC5 format (or its equivalent on mobile). Is there a way to detect either the swizzle states or number of channels when the image is loaded? I can't seem to find anything like this in the basisu_image_info or basisu_file_info structures. If we had this information it would be very easy to select a transcode format: Channels PC format
1 BC4
2 BC5
3, 4, unknown BC7

I know the KTX2 SDK provides this information, but I prefer to use Basis and DDS for all textures. It is also possible to store hints in the userdata values, but this feels like a hack and I would like something more versatile that will work with any basis file.

thokra1 commented 1 year ago

Not sure about how BasisU handles this, but when doing normal map compression with BasisU via libKTX, then the correct swizzle for UASTC compressed normal maps would be

ktxBasisParams basis{};
basis.structSize = sizeof(ktxBasisParams);
basis.inputSwizzle[0] = 'r';
basis.inputSwizzle[1] = 'r';
basis.inputSwizzle[2] = 'r';
basis.inputSwizzle[3] = 'g';

Having swizzles of 0 and 1 would, at least in OpenGL terms, mean that you don't take the actual R and G values, but replace them with 0 in rgb and 1 in a when sampling in a shader.

Nowadays, the only options one needs to really consider is BCn on desktop platforms (Windows, Linux, OSX, ...) and ASTC on mobile and embedded platforms and if for some reason that doesn't work, you have multiple legacy formats to fallback to (PVRTC1/2 and ETC2/EAC on iOS/Android, S3TC on desktops). Let's assume, we only target BCn and ASTC.

When sampling the texture in a shader, the necessary swizzle depends on the platform your running on. When transcoding to ASTC on the target platform, you need to sample with an .ra swizzle, when transcoding to BC5, you'd sample with an .rg swizzle.

To be able to use a single swizzle like .rg everywhere, you'd need to set the appropriate swizzle on the texture, i.e. if you wanted to always use an .rg swizzle in the shader, you'd need to set the appropriate texture swizzle when transcoding to ASTC. However, this is not possible when using WebGL (because one of the main backends is Direct3D, which doesn't support texture swizzles), so the choices u need to make depend on the actual backend and platform.

When not having native texture swizzle support, the only options you have is to either

This gets even more complicated when supporting both compressed and uncompressed normal maps, which you sometimes want, especially with 8-bit normal maps. Assuming the uncompressed data is also stored as a two-channel RG image, then on an ASTC platform, you'd have two cases to consider, because both sampling from .ra and from .rg would be necessary. In addition to the above mentioned options, you'd then have a third option for the uncompressed normal maps: on ASTC platforms, you'd simply blow up the RG texture into and RGBA texture, swizzled to RRRG again, so you can simply use the static permutations doing .ra swizzling just like for ASTC compressed textures.

I guess going to UASTC->ASTC and UASTC->BC7 and uncompressed RRRG textures would be a viable solution, but when I investigated this avenue, I found some more or less heavy quality issues with UASTC->BC7 transcoding for normal maps.