Thanks to the Swizzled DXT5 technique (injection of red channel into alpha channel and use of DXT5 compression), we get
a normal-map that can be used in a wide range of situations and particularly in those where the specular highlights are small or
their quality is not important. In all other cases where the normal-map compression must give good results, the use of ATI's 3Dc
compression format is inevitable. The only drawback of this format is that only AT Radeon X800 and up are 3Dc-ready. So for more realism
and even if graphics controllers embark more and more memory, use compression, it's worth while! (see
S3TC Comparative Table for compression ratio).
The O3TC format is produced by the DXTViewer and is really simple to load. You will find below the O3TC file structure,
a portion of code that shows you how to load compressed textures and a pure Win32 / OpenGL demo
(complete Visual C++ 6.0 project) is provided. It shows how to load a texture in the S3TC/O3TC format (texture dims are 2048x2048, so I hope
you have a decent graphics controller since I did not add all hardware checks...) and how to setup the
texture trilinear filtering. It's sheer bliss!
DWORD o3tc_data_size;
|
O3TC_Header
// Magic number: Must be O3TC.
char magic_number[4];
// Must be filled with sizeof(O3TC_Header).
DWORD header_size;
// Version. Currently: 0x00000001.
DWORD version;
|
O3TC_Chunk_Header
// Must be filled with sizeof(O3TC_Chunk_Header):
DWORD chunk_header_size;
// Reserved
DWORD reserved1;
// The size of the data chunk that follows this one.
DWORD size;
// Reserved
DWORD reserved2;
// Pixel format:
// - O3_TC_RGB_S3TC_DXT1 = 1
// - O3_TC_RGBA_S3TC_DXT5 = 4
// - O3_TC_ATI3DC_ATI2N = 16
DWORD internal_pixel_format;
// Texture width.
DWORD width;
// Texture height.
DWORD height;
// Texture depth.
DWORD depth;
// Number of mipmaps.
DWORD num_mipmaps;
// The texture name.
char texture_name[128];
// The texture id.
DWORD texture_id;
|
O3TC_Chunk_Data
// raw array of BYTE: this represents the
// real compressed data.
BYTE *pCompressedPixmap;
|
The first 4 bytes (data_size) give the total size of the data (headers included) held in the O3TC file:
o3tc_data_size = file_size - 4. These first four bytes can be skipped. Next, the file reading is quite simple:
1 - O3TC_Header reading in order to check the magic_number and file version.
2 - O3TC_Chunk_Header reading in order to retrieve texture dimensions (width and height), number of mipmaps
and compression type (DXT1 ou DXT5).
Direct reading of compressed data chunk. The size of this chunk is given by O3TC_Chunk_Header.size.
Next, this chunk has to be loaded into the graphics memory using the glCompressedTexImage2D() function.
Normally, this chunk is identical to that held in a DDS file.
Here is, extracted from the OpenGL demo (see void cDemo::setQuadTexture()), the piece of code that loads the compressed data chunk:
offset = 0;
size = 0;
for( mip=0; mip<=numMipMaps && (width || height); mip++ )
{
if (width == 0)
width = 1;
if (height == 0)
height = 1;
if(compression_type==O3_TC_RGBA_S3TC_DXT5)
{
blockSize = 16;
internal_format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
}
else if(compression_type==O3_TC_RGB_S3TC_DXT1)
{
blockSize = 8;
internal_format = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
}
size = ((width+3)/4)*((height+3)/4) * blockSize;
glCompressedTexImage2D( GL_TEXTURE_2D,
mip,
internal_format,
width, height,
0,
size,
pCompressedPixmap + offset);
offset += size;
width >>= 1;
height >>= 1;
}