TOP

ABOUT

PROGRAM

DIALY







テクスチャを読み込む!!(4)

 1.はじめに…
 今回は,PlayStationやGame Cubeとか色々なゲーム機でも使われているDirect Draw Surfaceファイル(*.dds)について取り上げます。
なんで,このファイルを取り上げたかというとS3TC(DXTC)圧縮という技術によってファイルを1/4に圧縮したりとかできて,Jpegとかよりも画質が全然マシということでよく使われているからです。実際にPocolもテクスチャを使うときはこのフォーマットに変換して使います。
 ちなみに…テクスチャ作成といえば,GimpやPhotoShopを使ったりしますが,GimpでDDSを吐き出すプラグインやnVIDIAのDevelopper ZoneにいくとPhotoShopでDDSファイルが出力できるプラグインなんかもあったりします。




 2.DDSファイルフォーマット
 DDSファイルフォーマットですが,MSDNなんかにも載っていたりするんですけど,Hyperでんちさんのページの方がわかりやすいと思うのでそちらを参考にしてください。
Hyperでんち DDSテクスチャフォーマットの詳細解説
(http://dench.flatlib.jp/ddsformatmemo.html)



 3.読み込みの下準備
 DDSファイルの読み込みですが,OpenGLの拡張機能を使うので,まずはGLEWをインストールしましょう。

@ まずはGLEWを配布しているhttp://glew.sourceforge.net/に飛びます。

A Download Centerという文字の下あたりにBinaries Windowsというのがあるのでそれをクリックしてダウンロードページ移動して,ファイルをダウンロードしてください。


B ダウンロードしたファイルを解凍してください。解凍すると解凍したフォルダ内にさらにlibというフォルダがあるのでクリックします。

C libフォルダの中にあるglew32.libとglews32.libをGLUTをインストールしたときと同じように「C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\lib」に2つのファイルを置きます。
※Visual Studio 2008を使っている場合は「C:\Program Files\Microsoft SDKs\Windows\v6.0A\Lib」に2つのファイルを置きます。

D 次に,解凍したフォルダ内のincludeをクリックし,GLフォルダをクリックし,GLフォルダ内にあるglew.hとwglew.hの2つのファイルを「C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSKD\Include\gl」に置きます。
※Visual Studio 2008を使っている場合は「C:\Program Files\Microsoft SDKs\Windows\v6.0A\Include\gl」にglew.hとwglew.hの2つのファイルを置きます。

E 最後に,解凍したフォルダ内にあるbinフォルダにあるglew32.dllを「C\WINDOWS\system32」に置きます。これでGLEWのインストールは終了です。


 4.ファイルの読み込み
 DDSファイルの読み込みですが,読み込みに使う構造体とクラスをまず用意します。
typedef struct _DDPixelFormat
{
    GLuint size;
    GLuint flgas;
    GLuint fourCC;
    GLuint bpp;
    GLuint redMask;
    GLuint greenMask;
    GLuint blueMask;
    GLuint alphaMask;
} DDPixelFormat;

typedef struct _DDSCaps
{
    GLuint caps;
    GLuint caps2;
    GLuint caps3;
    GLuint caps4;
} DDSCaps;

typedef struct _DDColorKey
{
    GLuint lowVal;
    GLuint highVal;
} DDColorKey;

typedef struct _DDSurfaceDesc
{
    GLuint size;
    GLuint flags;
    GLuint height;
    GLuint width;
    GLuint pitch;
    GLuint depth;
    GLuint mipMapLevels;
    GLuint alphaBitDepth;
    GLuint reserved;
    GLuint surface;

    DDColorKey ckDestOverlay;
    DDColorKey ckDestBlt;
    DDColorKey ckSrcOverlay;
    DDColorKey ckSrcBlt;

    DDPixelFormat format;
    DDSCaps caps;

    GLuint textureStage;
} DDSurfaceDesc;

//////////////////////////////////////////////////////////////////////////
//  DDSImage class
//////////////////////////////////////////////////////////////////////////
class DDSImage
{
protected:
    GLuint imageSize;
    GLubyte *imageData;
    GLenum format;
    GLuint internalFormat;
    GLuint width;
    GLuint height;
    GLuint bpp;

    GLuint numMipmaps;

public:
    GLuint ID;
    DDSImage();
    ~DDSImage();
    void DecompressDDS();
    bool ReadDDS(const char *filename);
    GLuint Load(const char *filename);
};

次に読み込みですが,まず最初にファイルを開く前にGLEWの初期化を行います。glewInit()を呼んで戻り値がGLEW_OKならGLEWが使えますが,そうでないなら処理を終了するようにします。
つぎに,GLEW_OKだったらファイルを開き,最初の4バイトを読み取ります。DDSファイルは必ず最初の4バイトが"DDS "になっているので,"DDS "かどうかをstrncmp()を使ってチェックします。
次にヘッダー情報を上で定義したDDSurfaceDesc構造体に格納します。構造体に格納したら画像の幅と高さとミップマップの情報をwidth, height, numMipmapsに構造体から変数に格納します。
ピクセルデータのデータサイズですが,S3TC圧縮がかかっているので,いままでのように幅と高さとビットから求めることができないので,データ部分の最初とファイルの終わりの位置をftellでとってきて,終わりから初めの位置を引いて,サイズを求めます。
ピクセルデータのデータサイズが決まったら,あとは一気にピクセルデータを読み取ります。これで読み込みは終了です。
//-----------------------------------------------------------------------------------------------------
//  ReadFileDDS
//  Desc : DDSファイルの読み込み
//-----------------------------------------------------------------------------------------------------
bool DDSImage::ReadDDS(const char *filename)
{
    FILE *fp;
    char magic[4];
    int mipmapFactor;
    long curr, end;
    DDSurfaceDesc ddsd;

    // GLEWの初期化
    if ( glewInit() != GLEW_OK )
    {
        cout << "Error : GLEWの初期化に失敗しました\n";
        return false;
    }

    // ファイルを開く
    fp = fopen(filename, "rb");
    if ( !fp )
    {
        cout << "Error : 指定されたファイルを開けませんでした\n";
        cout << "File : " << filename << endl;
        return false;
    }

    // マジックを読み取り
    fread(&magic, sizeof(char), 4, fp);

    // マジックをチェック
    if ( strncmp(magic, "DDS ", 4 ) != 0 )
    {
        cout << "Error : DDSファイルではありません\n";
        fclose(fp);
        return false;
    }

    // ヘッダーを読み取り
    fread(&ddsd, sizeof(ddsd), 1, fp);

    // 幅・高さを格納
    height = ddsd.height;
    width = ddsd.width;
    numMipmaps = ddsd.mipMapLevels;

    // フォーマット判別
    switch ( ddsd.format.fourCC )
    {
    case FOURCC_DXT1:
        // DXT1
        format = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
        internalFormat = 3;
        mipmapFactor = 2;
        break;

    case FOURCC_DXT3:
        // DXT3
        format = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
        internalFormat = 4;
        mipmapFactor = 4;
        break;

    case FOURCC_DXT5:
        // DXT5
        format = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
        internalFormat = 4;
        mipmapFactor = 4;
        break;

    default:
        cout << "Error : 未対応の形式です。DXT1, DXT3, DXT5のいずれかを使用してください\n";
        return false;
    }

    // テクセルデータのサイズを算出
    curr = ftell(fp);
    fseek(fp, 0, SEEK_END);
    end = ftell(fp);
    fseek(fp, curr, SEEK_SET);
    imageSize = end - curr;
    imageData = new GLubyte [imageSize];

    // ピクセルデータの読み込み
    fread(imageData, sizeof(GLubyte), imageSize, fp);

    // ファイルを閉じる
    fclose(fp);

    return true;
}


DDSファイルのDXT1, DXT3, DXT5形式はS3TC圧縮がかかっているので,解凍作業が必要です。
では,どうやって解凍するかという話なのですが,ここでGLEWが関係してきます。OpenGLの拡張機能の中の一つにこのS3TC圧縮を解凍してくれるglCompressedTexImage2D()という関数が用意されているんですね。この関数を使って解凍作業を行います。

//-----------------------------------------------------------------------------------------------------
//  DecompressDDS
//  Desc : S3TC圧縮の解凍作業
//-----------------------------------------------------------------------------------------------------
void DDSImage::DecompressDDS()
{
    int blockSize;
    int offset = 0;
    GLsizei mWidth = width, mHeight = height, mSize = 0;

    // DXT1
    if ( format == GL_COMPRESSED_RGBA_S3TC_DXT1_EXT )
        blockSize = 8;
    // DXT3, DXT5
    else
        blockSize = 16;

    // 解凍
    for ( int i=0; i<(int)numMipmaps; i++ )
    {
        mSize = ( (mWidth+3)/4 ) * ( (mHeight+3)/4 ) * blockSize;
        glCompressedTexImage2D(GL_TEXTURE_2D, i, format, mWidth, mHeight, 0, mSize, imageData + offset );
        
        if ( mWidth >> 1 )  mWidth = (mWidth >> 1);
        else    mWidth = 1;

        if ( mHeight >> 1 ) mHeight = (mHeight >> 1);
        else    mHeight = 1;

        offset += mSize;
    }
}

上のような感じ解凍を行います。
テクスチャを生成する流れですが,まずファイルを読み込む→テクスチャを生成→テクスチャの設定→解凍という感じになります。
//-----------------------------------------------------------------------------------------------------
//  Load
//  Desc : DDSファイルを読み込み,テクスチャを生成する
//-----------------------------------------------------------------------------------------------------
GLuint DDSImage::Load(const char *filename)
{
    // ファイルの読み込み
    if ( !ReadDDS(filename) )
        return 0;

    // テクスチャを生成
    glGenTextures(1, &ID);

    // テクスチャをバインド
    glBindTexture(GL_TEXTURE_2D, ID);

    // 拡大・縮小する方法の指定
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    
    // テクスチャ環境の設定
    glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // 解凍作業
    DecompressDDS();

    if ( imageData )
    {
        delete [] imageData;
        imageData = NULL;
    }

    //
    glBindTexture(GL_TEXTURE_2D, 0);

    // 生成したテクスチャ番号を返す
    return ID;
}


これで,あとはポリゴンにテクスチャを張り付ければめでたく画像が表示されるはずです。


 5.おわりに…
 RAW, TGA, BMP, DDSとテクスチャの読み込みについて取り上げてきましたが,一応今回でテクスチャ読み込みシリーズは終了です。


 Download
本ソースコードおよびプログラムを使用したことによる如何なる損害も製作者は責任を負いません。
本ソースコードおよびプログラムは自己責任でご使用ください。
プログラムの作成にはMicrosoft Visual Studio 2005 Professional Editionを用いています。