在OpenGL中使用纹理压缩
OpenGL 2008-07-14 18:08:45 阅读25 评论0 字号:大中小 订阅
纹理压缩技术已经广泛应用在各种3D游戏之中,它们包括:DXTC(Direct X Texture Compress,DirectX纹理压缩,以S3TC为基础)、S3TC(S3 Texture Compress,S3纹理压缩,仅支持S3显卡)、VTC(Volume Texture Compression,体积纹理压缩)、PTC(Palletized Texture Compression,并行纹理压缩)、VQTC(Vector-Quantization Texture Compression,向量纹理压缩)、YAB NCTC(Narrow Channel Texture compression,狭窄通道纹理压缩)、FXT 1。下面,我为各位介绍OpenGL中使用纹理压缩的两种主要方法:ARB纹理技术扩展和S3TC。
一、概述
与其它压缩技术一样,ARB可以把高分辨率纹理放入未压缩前只能存储低分辨率纹理的空间,并提供渲染管道优化,特点有:
- 增加渲染速度
- 减低纹理内存需求
- 快速纹理下载到纹理内存
- 低磁盘存储空间需求和高速磁盘存取
纹理压缩通常分为纹理生成层和运行时间层二部分,纹理生成又分成两种方法:
- 通常方法:使用GL来压缩图像,存储到磁盘
- S3TC DDS文件格式:使用ISV的S3TC压缩工具来处理图像,在GL中载入DDS文件
运行时间层的主要工作分三步:
- 从磁盘载入压缩图像
- 上载压缩图像到GL
- 不压缩动态纹理
二、具体过程
最理想的压缩纹理技术是在游戏的开头画面预压缩所有的纹理,减少磁盘存储需要,加速纹理载入速度。下面是用两种不同的方法处理纹理位图,第一种方法使用OpenGL压缩,第二种使用直接支持OpenGL的ISV S3TC文件格式。
ARB允许未压缩的纹理通过glTexImage2D呼叫来进行纹理压缩,并设置内部格式参数。图1的上半部分为常规方式,下半部分为S3TC扩展集,基本内部格式通过压缩后将变成压缩内部格式。
1、常规压缩
使用代理纹理可以在压缩前指示欲压缩的纹理图像,然后再以glGetTexLevelParameteriv加上$#@60;pname$#@62;到GL_TEXTURE_COMPRESSED_ARB,来检查图像是否进行适当的压缩。如果纹理已经得到正确处理,会返回一个非零参量,若是纹理压缩出错,则变回相应的基本内部格式。
使用普通压缩内部格式,查询OpenGL会自动选择内部格式作为$#@60;internalFormat$#@62;,具体过程是 鰃lGetTexLevelParameteriv再加上$#@60;pname$#@62;参数,变成GL_TEXTURE_INTERNAL_FORMAT。
下一步,查询OpenGL会通过glGetTexLevelParameteriv呼叫获得压缩纹理图像的容量,然后用$#@60;pname$#@62;生成GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB,建立返回尺寸的缓冲区。glGetCompressedTexImageARB呼叫可取得压缩纹理数据,再用$#@60;img$#@62;取得缓冲区地址。压缩纹理到磁盘后,OpenGL会把下列属性加入到压缩纹理本身之中,以备运行时间层调用,属性包括:
- 缓冲区容量
- 压缩内部格式
- 宽度
- 高度
- 边界$#@60;border$#@62;(非S3TC)
- 深度$#@60;depth$#@62;(3D纹理和非S3TC技术)
如果使用S3TC,压缩内部格式完成时,$#@60;border$#@62;将变设置为零。这意味着$#@60;border$#@62;不为零会导致“glCompressedTexImage2DARB”发生“INVALID_OPERATION(内部操作)”错误。若是图像仅适用于2D纹理,$#@60;depth$#@62;与S3TC格式无关,一旦$#@60;internalFormat$#@62;(内部格式)变成了S3TC,“glCompressedTexImage1DARB”和“glCompressedTexImage3DARB”会生成“INVALID_ENUM”错误。
在另一平台使用压缩纹理,压缩内部格式不属于标准的话,必须让平台支持特殊的格式。比如用
“glGetIntegerv”呼叫加上“GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB”和“GL_COMPRESSED_TEXTURE_FORMATS_ARB”。下面是一段支持压缩内部格式的代码:
GLint * compressed_format;
GLint num_compressed_format;
glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB, &num_compressed_format);
compressed_format = (GLint*)malloc(num_compressed_format * sizeof(GLint));
glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS_ARB, compressed_format);
S3TC无须列出所支持的格式,也不用特殊的扩展集,因此S3TC不提供“GL_COMPRESSED_RGBA_S3TC_DXT1_EXT”格式,所有变化都会被自动认成是“正常”的RGBA格式。
随后的语句概括了压缩未压缩图像和把它存储进硬盘的过程:
glBindTexture(GL_TEXTURE_2D, compressed_decal_map);
glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_ARB, width, height,
0, GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_ARB, &compressed);
/* if the compression has been successful */
if (compressed == GL_TRUE)
{
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT,
&internalformat);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB,
&compressed_size);
img = (unsigned char *)malloc(compressed_size * sizeof(unsigned char));
glGetCompressedTexImageARB(GL_TEXTURE_2D, 0, img);
SaveTexture(width, height, compressed_size, img, internalFormat, 0);
}
有多种方法可以评估压缩扩展集的画质,比如:图像质量参数RED_BITS, GREEN_BITS等,或压缩纹理映象,或用glGetTexImage呼叫存取非压缩图像缓冲区。
2、S3TC DDS文件格式
ISV工具(如:S3的Adobe PhotoShop Plug-in插件和微软DirectX的Dxtex)可以把普通文件格式转换成dds文件。故名思义,DDS(Direct Draw Surface,直接绘画表面)的基本操作是把内存的数据通过DirectX DDS接口下载到硬盘。DDS文件读取程度包含在DirectX的ddraw.h中,还可以得到DDSURFACEDESC2定义。此外,DirectX的架构与OpenGL不同,其屏幕原始坐标也不一样,DirectX的原始屏幕坐标位于左上角,OpenGL则位于左下角。在转换图像或纹理调整之前,必须改变纹理的垂直方向。
下面是一段DDS读取程序的源码:
#include $#@60;ddraw.h$#@62;
gliGenericImage *
ReadDDSFile(const char *filename, int * bufsize, int * numMipmaps)
{
gliGenericImage *genericImage;
DDSURFACEDESC2 ddsd;
char filecode[ 4];
FILE *fp;
/* try to open the file */
fp = fopen(filename, "rb");
if (fp == NULL)
return NULL;
/* verify the type of file */
fread(filecode, 1, 4, fp);
if (strncmp(filecode, "DDS ", 4) != 0) {
fclose(fp);
return NULL;
}
/* get the surface desc */
fread(&ddsd, sizeof(ddsd), 1, fp);
genericImage = (gliGenericImage*) malloc(sizeof(gliGenericImage));
memset(genericImage,0,sizeof(gliGenericImage));
/* how big is it going to be including all mipmaps? */
*bufsize = ddsd.dwMipMapCount $#@62; 1 ? ddsd.dwLinearSize * 2 : ddsd.dwLinearSize;
genericImage-$#@62;pixels = (unsigned char*)malloc(*bufsize * sizeof(unsigned char));
fread(genericImage-$#@62;pixels, 1, *bufsize, fp);
/* close the file pointer */
fclose(fp);
genericImage-$#@62;width = ddsd.dwWidth;
genericImage-$#@62;height = ddsd.dwHeight;
genericImage-$#@62;components = (ddsd.ddpfPixelFormat.dwFourCC == FOURCC_DXT1) ? 3 : 4;
*numMipmaps = ddsd.dwMipMapCount;
switch(ddsd.ddpfPixelFormat.dwFourCC)
{
case FOURCC_DXT1:
genericImage-$#@62;format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
break;
case FOURCC_DXT3:
genericImage-$#@62;format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
break;
case FOURCC_DXT5:
genericImage-$#@62;format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
break;
default:
free(genericImage-$#@62;pixels);
free(genericImage);
return NULL;
}
/* return data */
return genericImage;
}
当DDS文件被看成是MIP映射时,它会压缩所有的映射图像,此问题通常发生于GL在一段时间内取出一个映射的时候。DDS文件的缓冲区读取不能直接由GL控制,你必须精确计算每一个MIP映射在缓冲区的偏移量,传递正确的数据地址和属性给
glCompressedTexImage2DARB,代码如下:
/* load the .dds file */
ddsimage = ReadDDSFile("flowers.dds",&ddsbufsize,&numMipmaps);
height = ddsimage-$#@62;height;
width = ddsimage-$#@62;width;
offset = 0;
div = (ddsimage-$#@62;format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT) ? 1 : 0;
glBindTexture(GL_TEXTURE_2D, dds_compressed_decal_map);
/* load the mipmaps */
for (i = 0; i $#@60; numMipmaps && (width $#@62; 1 && height $#@62; 1); ++i)
{
size = width * height;
size $#@62;$#@62;= div;
glCompressedTexImage2DARB(GL_TEXTURE_2D, i, ddsimage-$#@62;format, width, height,
0, size, ddsimage-$#@62;pixels + offset);
GLErrorReport();
offset += size;
width $#@62;$#@62;= 1;
height $#@62;$#@62;= 1;
}
最后,大家会注意到微软的DirectX SDK DXTex工具提供了把立体位图(带MIP映射)压缩到单个DDS文件的能力。因为DirectX围绕一个立体位图需要的所有数据而设计,压缩非常容易实现,而且DDS文件阅读器还能按照位图的不同自由定制。如果一个立体位图与DirectX相匹配,那么,它也能够用在OpenGL身上。