How to: load DDS files in OpenGL.

1649 篇文章 11 订阅
1623 篇文章 22 订阅

Introduction to texture compression in OpenGL

There are basically two ways to use texture compression in OpenGL: you can either use GL_ARB_texture_compression, just uploading your textures like you always do using glTexImage2D(), where you specify a compressed format as the target format (you can also use GL_EXT_texture_compression_s3tc to use specific S3TC/DXTC formats). This way, textures you upload to the 3D card are compressed on-fly by the drivers. But this can unfortunately be rather slow and significantly reduce the quality of the textures.

Or you can do it the other way, which is to pre-compress the textures using specific tools (for example, the one provided by NVIDIA) and then directly upload them to the 3D card using glCompressedTexImage2D(). Because textures aren't compressed in runtime, the compression algorithm can spend more time trying to optimize the image quality of each texture, the result is of course better looking textures (a 2048x2048 textures can typically take 5 minutes to compress using NVIDIA tool). It also gives better control on the texture quality, and it can be decided whether or not a particular texture should be pre-compressed depending on the severity of the quality loss. The last point is that mipmaps are also pre-generated and pre-compressed, that gives better mipmaps quality and alternative mipmaps can also be specified.

The second way is IMHO the best way to do it. Unfortunately, to be done efficiently, it has to be used in conjunction with GL_EXT_texture_compression_s3tc because when you pre-compress your textures you have to know what specific formats they'll be compressed in. But this isn't a big problem since the two majors of the 3D arena (namely ATI and NVIDIA) support this extension.

Even if a given 3D card doesn't support this extension, you can always decompress the textures and resize then in runtime. The result is that high resolution pre-compressed textures will be used whenever possible and normal resolution textures otherwise.

Note that there is also a third way, inbetween the two first, which consist of compressing the textures in runtime but then saving the results so that textures don't have to be re-compressed next time. Its main advantage is to eliminate a big waste of time.

 

Most of it is fairly easy

Actually, most of the tools (if not all of them) that pre-compress images in S3TC/DXTC formats use the DDS file format. DDS stands for DirectDraw Surface and is, obviously, a DirectX format. DDS files can also be used to store other formats than pre-compressed one but here we will only use it to store our pre-compressed textures.

To load a DDS file into the 3D card the first step is of course to read and store into memory the image contained in that file. Notice that you'll need an up-to-date DirectX SDK to be able to use some DirectDraw headers! This step is fairly easy but is a bit long and boring so I'm not going to cover it in details here; if you can't easily understand this part of my example program then you should check NVIDIA website that has a lot of documentation on the subject. Notice that I don't exactly follow NVIDIA's way.

The second step is to upload the image and its mipmaps. This is again fairly easy once you're familiar with the corresponding OpenGL commands. You just upload each mipmap one at the time. The not-so-easy part of it is to compute the offset to the next mipmap and also to avoid problems if the DDS file is missing some mipmaps as some tools don't make them all (see Figure 1).


Figure 1: a simplified version of the uploading routine.

Even if you're not familiar with the C++ class I use to hold the loaded DDS file, you can easily understand what is going on. Firstly I check that the image is not empty, secondly I modify the OpenGL texture settings so that even if the number of mipmaps isn't the one expected by OpenGL it will still work correctly.

Now comes the important part. We loop until each mipmap is uploaded, to achieve that we need to compute the size of each mipmap. Because S3TC/DXTC images are encoded in 4x4 pixels blocks and because the size of these block can change depending on the format used, the formula used to compute the size of the current mipmap is a bit more complicated than simply multiplying the height with the width and with the pixel size. Here we compute the number of blocks, and then we multiply that number by the size of one block. The size of the current mipmap is needed by glCompressedTexImage2D() but it also allows us to get the offset to the next mipmap.

 

Annoying problems will show up soon enough

Now that wasn't too hard, was it?

If you've done everything correctly, you should be able to use S3TC/DXTC DDS files in your OpenGL programs.

Now everything seems to be working fine... except that sooner or later you'll begin wondering why all your textures appear upside-down.

The problem is that OpenGL and DirectX use different space coordinates. While in 2D OpenGL puts the origin point (0, 0) at the bottom left of the image, DirectX puts it at the top left, and DDS files are DirectX files. This is why all your textures loaded from DDS files will show up upside-down in OpenGL.

 

How can we solve that little problem?

NVIDIA gives two quick ways to get rid of it on its website.

The first one, which is the most obvious solution, is to vertically flip the picture just before pre-compressing it. If we do so, the texture will show up correctly in OpenGL. But then it will be wrong in DirectX. Plus it's really annoying for the artist to have to always modify his textures before saving them in DDS. Thus this is only a temporary fix and can only be used for small projects such as technological demos.

The second one is to modify the texture coordinates of every 3D model we draw in OpenGL so that textures appears the right way. I think you can easily understand that it only works with simple models/effects and that it can quickly become a nightmare for the developer. And the worst part of it is that we can't always do it efficiently: for example if we use vertex shaders then we'll have to modify them to get the result we want.

We could also decompress the pictures, vertically flip them and then recompress them in runtime. But that completely kills the purpose of having pre-compressed textures.

The ideal solution would be to simply vertically flip the pictures in runtime without decompressing them right before they're uploaded to the 3D card. Unfortunately for us it's impossible to ALWAYS vertically flip a S3TC/DXTC picture.

But this fourth idea still seems to be the best. The point is that, even if we can't ALWAYS achieve it, we can sometimes. The important thing is that in OpenGL textures must have dimensions that are powers of two; considering that we are able to do what we wanted to (I'll explain why a bit further).

 

A "brief" explanation of the process...

To explain how to vertically flip DXTC textures I will only consider here DXT1. DXT3 and DXT5 pictures are roughly processed the same way.

In DXTC, pictures are encoded in blocks. Each block contains data for 16 pixels (4x4). DXT1 uses 64 bits blocks that contain color (RGB) information for each pixel, while DXT3 and DXT5 use 128 bits blocks divided in two parts: the color block and the alpha block. Figure 2 represents the color block used by DXT1/3/5.


Figure 2: DXT1 block configuration.

The important property of these color and alpha block in our case is that each pixel's information is independent of the pixel position within the block, and independent of other pixels. Why is it so important? Because it means that we can move pixels within the same block without changing their values.

This is also where the fact that textures in OpenGL have dimensions that are powers of two becomes so important. It allows us to vertically flip the textures while always leaving pixels in their own block. There are four specific cases we must cover:

  • The texture height is equal to 1. A vertical flip has virtually no effect. We can leave the picture unchanged.
  • The texture height is equal to 2. The picture is encoded in the two first rows of pixels of each block; we just have to swap these two rows in each block (the two latest rows of pixels in each block are irrelevant).
  • The texture height is equal to 4. The picture consists of a row of blocks, all the 4 rows of each block containing valuable information. We have to swap row0 with row3 and row1 with row2 (basically we vertically flip the 4x4 pictures contained in each block).
  • The texture height is greater than 4. The picture is encoded in a matrix of blocks. We must vertically flip all these blocks; this means that we have to swap each block with its corresponding "mirror" block (this mirror block is defined by symmetry using an horizontal axis at the center of the picture). And then we have to vertically flip the pixels contained in each block. (swap row0 with row3 and row1 with row2)

 

Conclusion

I covered here the basis of the algorithms used in the given example program. I hope it'll help you to understand it. I also hope it'll help you to implement DXTC using DDS files in your OpenGL programs in order to have better visuals and improved efficiency.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值