I'm looking for good algorithms for compressing textures offline (ie decompressing them at install or load time, I know about using DXT/3DC to save runtime video memory, but these create awful visual artifacts while often being larger in disk than a lossless format like JPEG-LS, even after using LZMA or something for entropy). Since the data will only need to be read by my program, I don't need a portable format like JPEG, which means I'm open to more experimental algorithms. To be clear, I'm concerned with reducing the download size of my game; I will be using them uncompressed on the video card.
I'm looking for formats that support different features... For example, 10 bits-per-channel color (HDR) for higher color fidelity under dynamic lighting conditions, normal map formats(16 bits in X and Y), alpha channels, etc, etc...
NOTE: My particular use case is for 2.5D backgrounds. In their uncompressed format, they are 10 bytes per pixel stored in 3 textures:
4 bytes - color - D3DFMT_A2R10G10B10
4 bytes - normal - D3DFMT_G16R16F
2 bytes - depth - D3DFMT_R16F
I store these in 1024x1024 tiles, and since every area has a different background, these can get really large really fast. However, I think knowing more about image codecs in general will help with finding an efficient method to compress these.
I'm looking for good algorithms for compressing textures offline
What is your purpose in compressing these images? There are generally two reasons to compress a texture:
You want to make the texture smaller so that it takes up less memory on the GPU.
You want to make the texture smaller so that it takes less time to load/requires less harddrive space, resulting in a smaller download.
The most important thing you can understand is this: you cannot satisfy both of these at once. You must pick one: smaller GPU memory, or smaller disk space/load time.
So let's look at both cases.
Case 1
Your possibilities here are the various "Block Compressed" types, using the D3D 10 language for them.
I'll assume you know what BC1-3 are, since they are formerly known as DXT1, DXT3, and DXT5, respectively. BC4 and BC5 are for 1 and 2-channel formats respectively. These two can be either unsigned or signed normalized.
BC5, 2-channel compressed, can be used for storing tangent-space normal maps, with the third coordinate (Z) reconstructed in your shader. It does a reasonably good job.
BC6H and BC7 are quite new. BC6H is a compressed, floating-point format. BC7 is a more accurate way to compressed RGBA color data. You can only get BC6H and BC7 support from DX11-class hardware.
The OpenGL internal formats for these are as follows:
- BC1:
GL_COMPRESSED_RGB_S3TC_DXT1_EXT
and GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
- BC2:
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
- BC3:
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
- BC4:
GL_COMPRESSED_RED_RGTC1
and GL_COMPRESSED_SIGNED_RED_RGTC1
- BC5:
GL_COMPRESSED_RG_RGTC2
and GL_COMPRESSED_SIGNED_RG_RGTC2
- BC6H:
GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB
and GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB
- BC7:
GL_COMPRESSED_RGBA_BPTC_UNORM_ARB
The BC1-3 and BC7 formats can also be in the sRGB colorspace. OpenGL provides similar sRGB variants and GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB.
Obviously, none of these are lossless.
As I understand it, BC6H and BC7 are not widely supported in tools, though I seem to recall that NVIDIA's texture tools do support them.
Case 2
If you're talking about normal 8-bit-per-channel images, you already know your options. But for more exotic texture types, like floating-point, 10-bit-per-channel, and normal maps, you're out of luck.
The simple fact is that most general image compression software is intended for regular old images. Most people who deal with floating-point textures don't really care enough about loading speed or disk space to bother with compressing them. They're big, but they've accepted that. And it's really hard to losslessly compress floating-point values.
So really, just stick with known paths for regular images, and use zlib or 7z or some other general compression method for the others. That's generally about the best you can do.
Do note that any lossless image compression technique can be made to work on any pixel data, so long as that data can fit. So you can always put a normal map in a PNG, even if it is a 2-component normal map (the third component is 0). It may not compress well, but it will probably beat out pure-zip.
4 bytes - color - D3DFMT_A2R10G10B10
If you're storing a color, a normal, and a depth, then I can only assume that you are doing some form of deferred rendering-type thing here. That is, the color is the diffuse reflectance, not the light radiating from the background.
Given that, 10-bit colors are pretty much overkill. You don't need that level of color accuracy for a diffuse reflectance value; you can get the same level of accuracy from an sRGB color value. HDR is all about the lighting; you can do HDR just fine with lower color-depth.
Once you're dealing with regular sRGB colors, you can start using standard image compression techniques.
4 bytes - normal - D3DFMT_G16R16F
There's no reason you can't take that down to G8B8 using signed-normalised values. Games do this all the time, and normals are not sensitive enough to need 16 bits worth of accuracy. And they're normals, so even using floating-point is way overkill.
Whether you can live with the artifacts produced by feeding it through BC5 compression is up to you. But you can at least go down from 32-bpp to 16-bpp with no detectable loss of fidelity.
Ultimately, everything is a tradeoff. You need to decide how much image quality you are willing to give up to get your game's data size under control. And since it's a tradeoff, you need to run actual tests to see where you are willing to draw that line.