OpenGL 加载DDS文件(压缩纹理)

想必很多人都见过DDS这种文件,它是一个“图片文件”,如果你安装了某些看图软件,你可以直接双击打开它来进行预览。

那么,这种DDS文件和我们常见的TGA/PNG之类的文件有何不同呢?

DDS和TGA/PNG/JPG之类的“图片文件” 一样,支持“压缩”,减少磁盘空间占用(把文件变小)。

通常我们要加载一个TGA或者PNG文件到OpenGL的时候,都要先把文件数据还原成RGB格式的像素数据,然后用glTexImage2D把像素数据传到显存。这个过程相当于“解压”,这通常非常消耗CPU资源,速度较慢。

但是DDS的压缩数据不需要“解压”就能直接传到显存,而且传到显存之后也不会解压,这极大减少了显存的使用量,并且提高了纹理加载速度,有绝对的优势。我们只需要读取好压缩数据,然后使用glCompressedTexImage2D(代替glTexImage2D)就可以直接把压缩数据传到显存,完成加载。

DDS可以保存许多种格式的像素数据,这里只讲最常用的3种(DXT1、DXT3、DXT5)。

* 当然DDS文件也能存储不压缩的像素数据。

为了在OpenGL中使用DDS压缩纹理(下文简称压缩纹理),我们需要一下2个OpenGL扩展:

GL_ARB_texture_compression

提供函数 “glCompressedTexImage2D”

GL_EXT_texture_compression_s3tc

提供以下格式的压缩纹理支持:
GL_COMPRESSED_RGB_S3TC_DXT1_EXT
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT

完整的加载过程代码:

#include <stdio.h>
#include <gl/glut.h>
#include <gl/glext.h>

// Minimum and maximum macros
#define max(a,b) (((a) > (b)) ? (a) : (b))
#define min(a,b) (((a) < (b)) ? (a) : (b))

PFNGLCOMPRESSEDTEXIMAGE2DARBPROC glCompressedTexImage2DARB = NULL;

#pragma region DDS

#define DDPF_ALPHAPIXELS    0x000001
#define DDPF_ALPHA            0x000002
#define DDPF_FOURCC            0x000004
#define DDPF_RGB            0x000040
#define DDPF_YUV            0x000200
#define DDPF_LUMINANCE        0x020000

#define D3DFMT_DXT1    ((‘D‘<<0)|(‘X‘<<8)|(‘T‘<<16)|(‘1‘<<24))
#define D3DFMT_DXT3    ((‘D‘<<0)|(‘X‘<<8)|(‘T‘<<16)|(‘3‘<<24))
#define D3DFMT_DXT5    ((‘D‘<<0)|(‘X‘<<8)|(‘T‘<<16)|(‘5‘<<24))

typedef struct
{
    DWORD    dwSize;
    DWORD    dwFlags;
    DWORD    dwFourCC;
    DWORD    dwRGBBitCount;
    DWORD    dwRBitMask;
    DWORD    dwGBitMask;
    DWORD    dwBBitMask;
    DWORD    dwABitMask;
} DDS_PIXELFORMAT;

#define DDSD_CAPS            0x000001
#define DDSD_HEIGHT            0x000002
#define DDSD_WIDTH            0x000004
#define DDSD_PITCH            0x000008
#define DDSD_PIXELFORMAT    0x001000
#define DDSD_MIPMAPCOUNT    0x020000
#define DDSD_LINEARSIZE        0x080000
#define DDSD_DEPTH            0x800000

typedef struct
{
    DWORD            dwSize;
    DWORD            dwFlags;
    DWORD            dwHeight;
    DWORD            dwWidth;
    DWORD            dwPitchOrLinearSize;
    DWORD            dwDepth;
    DWORD            dwMipMapCount;
    DWORD            dwReserved1[11];
    DDS_PIXELFORMAT    ddspf;
    DWORD            dwCaps;
    DWORD            dwCaps2;
    DWORD            dwCaps3;
    DWORD            dwCaps4;
    DWORD            dwReserved2;
} DDS_HEADER;

typedef struct
{
    DWORD        dwMagic;
    DDS_HEADER    Header;
} DDS_FILEHEADER;

// For a compressed texture, the size of each mipmap level image is typically one-fourth the size of the previous, with a minimum of 8 (DXT1) or 16 (DXT2-5) bytes (for
// square textures). Use the following formula to calculate the size of each level for a non-square texture:
#define SIZE_OF_DXT1(width, height)    ( max(1, ( (width + 3) >> 2 ) ) * max(1, ( (height + 3) >> 2 ) ) * 8 )
#define SIZE_OF_DXT2(width, height)    ( max(1, ( (width + 3) >> 2 ) ) * max(1, ( (height + 3) >> 2 ) ) * 16 )

#pragma endregion

GLuint gl_load_dds(GLvoid *pBuffer)
{
    DDS_FILEHEADER    *header;
    DWORD            compressFormat;
    GLuint            texnum;
    GLvoid            *data;
    GLsizei            imageSize;

    header = (DDS_FILEHEADER *)pBuffer;

    if (header->dwMagic != 0x20534444) {
        printf("bad dds file\n");
        return 0;
    }

    if (header->Header.dwSize != 124) {
        printf("bad header size\n");
        return 0;
    }

    if (!(header->Header.dwFlags & DDSD_LINEARSIZE)) {
        printf("bad file type\n");
        return 0;
    }

    if (!(header->Header.ddspf.dwFlags & DDPF_FOURCC)) {
        printf("bad pixel format\n");
        return 0;
    }

    compressFormat = header->Header.ddspf.dwFourCC;

    if (compressFormat != D3DFMT_DXT1 &&
        compressFormat != D3DFMT_DXT3 &&
        compressFormat != D3DFMT_DXT5) {
            printf("bad compress format\n");
            return 0;
    }

    data = (GLvoid *)(header + 1);    // header data skipped

    glGenTextures(1, &texnum);
    glBindTexture(GL_TEXTURE_2D, texnum);

    switch (compressFormat)
    {
    case D3DFMT_DXT1:
        imageSize = SIZE_OF_DXT1(header->Header.dwWidth, header->Header.dwHeight);
        glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_S3TC_DXT1_EXT, header->Header.dwWidth, header->Header.dwHeight, 0, imageSize, data);
        break;
    case D3DFMT_DXT3:
        imageSize = SIZE_OF_DXT2(header->Header.dwWidth, header->Header.dwHeight);
        glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, header->Header.dwWidth, header->Header.dwHeight, 0, imageSize, data);
        break;
    case D3DFMT_DXT5:
        imageSize = SIZE_OF_DXT2(header->Header.dwWidth, header->Header.dwHeight);
        glCompressedTexImage2DARB(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, header->Header.dwWidth, header->Header.dwHeight, 0, imageSize, data);
        break;
    }

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);

    return texnum;
}

GLuint g_texnum;

void load_textures(void)
{
    FILE    *fp;
    int        size;
    void    *data;

    fp = fopen("028_dxt5.dds", "rb");
    if (!fp) {
        return;
    }

    fseek(fp, 0, SEEK_END);
    size = ftell(fp);
    fseek(fp, 0, SEEK_SET);

    data = malloc(size);
    if (!data) {
        fclose(fp);
        return;
    }

    if (fread(data, size, 1, fp) != 1) {
        free(data);
        fclose(fp);
        return;
    }

    fclose(fp);

    // Load DDS to GL texture
    g_texnum = gl_load_dds(data);

    free(data);
}

void init(void)
{
    // GL_ARB_texture_compression
    // GL_EXT_texture_compression_s3tc
    glCompressedTexImage2DARB = (PFNGLCOMPRESSEDTEXIMAGE2DARBPROC)wglGetProcAddress("glCompressedTexImage2DARB");

    load_textures();
    glClearColor(0, 0, 0, 0);
}

void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    RECT rc;
    rc.left = 10;
    rc.top = 10;
    rc.right = rc.left + 1280;
    rc.bottom = rc.top + 720;

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, g_texnum);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4f(1, 1, 1, 1);

    glBegin(GL_QUADS);
        glTexCoord2f(0, 0);
        glVertex2f(rc.left, rc.top);
        glTexCoord2f(1, 0);
        glVertex2f(rc.right, rc.top);
        glTexCoord2f(1, 1);
        glVertex2f(rc.right, rc.bottom);
        glTexCoord2f(0, 1);
        glVertex2f(rc.left, rc.bottom);
    glEnd();

    glutSwapBuffers();
    glutPostRedisplay();
}

void reshape(int width, int height)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, width, height, 0);
    glMatrixMode(GL_MODELVIEW);

    glViewport(0, 0, width, height);
}

int main(int argc, char **argv)
{
    glutInitWindowPosition(200, 200);
    glutInitWindowSize(10+1280+10, 10+720+10);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutCreateWindow("OpenGL DDS");
    init();
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMainLoop();

    return 0;
}

关于如何制作一个DDS文件,可以使用Nvidia提供的DXT工具,下载地址:

https://developer.nvidia.com/legacy-texture-tools
http://pan.baidu.com/s/1pKKRL3P

以下是文件大小对比:

以下是图像质量对比:

原图(TGA,无压缩):

DXT1(压缩比:1/8,无Alpha通道):

DXT3(压缩包:1/4,Alpha通道还原较差):

DXT5(压缩比:1/4,Alpha通道还原较好):

参考:

http://msdn.microsoft.com/en-us/library/windows/desktop/bb943990(v=vs.85).aspxhttp://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_compression.txthttp://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_compression_s3tc.txt
时间: 2024-08-02 10:52:34

OpenGL 加载DDS文件(压缩纹理)的相关文章

openGL加载obj文件+绘制大脑表层+高亮染色

绘制大脑表层并高亮染色的工作是以openGL加载obj文件为基础的,这里是我们用到的原始程序:只能加载一个obj文件的demo. 然而,一个完整的大脑表层是由很多分区组成的,因此我们的程序需要支持两个功能: 同时加载多个obj文件. 每个大脑分区obj文件保持其相对位置. 明白了需求后,我们就可以开始修改代码了~ glmUnitize函数的作用是单位化,也就是把模型通过平移和缩放变换限制到3维坐标系中点为中心的一个单位正方体区域内.所以控制obj显示位置是在glmUnitize()函数中,源代码

如何在SCENEKIT使用SWIFT RUNTIME动态加载COLLADA文件

问题:今天接到一个项目,负责弄需求的美眉跟我讲能不能做一个原型能够加载Collada文件,流程如下: 用户用app下载Collada 压缩包(如内购项目) 压缩包解压 展示Collada文件里的内容 我开始google各种能够快速搞定需求的工具以及类库,看了下Unity3D,感觉这胖子挺臃肿的,对胖子没兴趣.苹果的SceneKit好像做3D还不错,性能高还是原生,原生态的东西味道应该不错,下面有食用方法. 步骤一: 打开不是给人看的Apple Doucmentation,经过两眼球左右互搏后有重

Spring boot 国际化自动加载资源文件问题

Spring boot 国际化自动加载资源文件问题 最近在做基于Spring boot配置的项目.中间遇到一个国际化资源加载的问题,正常来说只要在application.properties文件中定义正确的资源文件路径,Spring boot就启动时就会自动加载资源. spring.messages.basename=i18n/message 但是我的项目修改后获取消息时系统报错,找不到对应语言的资源配置.于是试图找到原因.Google好久都没找到,简直好像就我一个人遇到这鬼问题一样??.只好自

实现异步加载js文件及加载完成后回调

模块化工具类实现方式 基于AMD.CMD模式的JS模块化管理工具越来越流行,这些工具通常只需在页面中加载对应的工具JS,其他JS文件都是异步加载的,比如RequireJS就可以象下面这样做. 首先在页面加载 <script data-main="scripts/main.js" src="scripts/require.js"></script> 然后工具会自动识别data-main属性值,并加载对应的JS文件,在main.js可以加载更多模

JVM加载class文件的原理

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件.那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做[加载 ].加载完成之后,我们就可以进行一系列的运行前准备工作了,比如: 为类静态变量开辟空间,将常量池存放在方法区内存中并实现常量池地址解析,初始化类静态变量等等.这篇文章我们要好好谈谈JVM是如何加载class文件的? 1.JVM加载类的过程       当我们使用命令来执行某一个Java程序(比如Test.class)的时候:java T

加载nib文件的过程

当加载nib文件时,存储在nib文件中的任何对象都会被重新创建.这意味着会在后台执行alloc和init方法.所以,当应用程序启动时,会分配并初始化一个AppController实例.在执行init方法期间,所有IBOutlet实例变量都为nil.只有创建了nib文件中的所有对象,所有连接才算完成. 一旦建立了所有连接,会向创建的每个对象发送消息awakeFromNib.一个非常常见的错误是试图在init方法中使用IBoutlet执行一些操作.由于所有实例变量都为nil,发送给他们的所有消息不执

Android动态加载XML文件及控件来简单实现QQ好友印象的功能

在android开发中,我们常常会遇到界面布局控件不确定的情况.由于某些功能的原因或者为了体现某些app的特色等这些原因会导致我们在实现界面布局时需要动态去加载一些控件,那么下面就来介绍一下如何用动态加载控件来简单实现QQ中好友印象的功能,其中也会提到如何来动态加载一个XML的配置文件. 那么要实现好友印象的功能,我们需要通过以下这几个步骤: 1.界面一开始需要加载一个EditText和Button控件,用于填写好友印象和添加好友印象: 2.需要新建一个arrays.xml,在xml文件中添加上

自定义泪价值器2——加密class文件 解密加载class文件

public class MyClassLoader extends ClassLoader { private String classDir;//自定义类加载器 所查找的目录 MyClassLoader(String classDir){ this.classDir = classDir; } @[email protected]("deprecation") //findClass的主要作用就是 把class文件读取到内存中 那么涉及两个流,,但是class文件被加密 所以需要先

Java中动态加载jar文件和class文件

概述 诸如tomcat这样的服务器,在启动的时候会加载应用程序中lib目录下的jar文件以及classes目录下的class文件,另外像spring这类框架,也可以根据指定的路径扫描并加载指定的类文件,这个技术可以实现一个容器,容纳各类不同的子应用. Java类由于需要加载和编译字节码,动态加载class文件较为麻烦,不像C加载动态链接库只要一个文件名就可以搞定,但JDK仍提供了一整套方法来动态加载jar文件和class文件. 动态加载jar文件 // 系统类库路径 File libPath =