2014年10月2日木曜日

WebGLでDDSテクスチャを表示する

WebGLでDDSテクスチャを表示する方法,調べても日本語ではあまり情報がなかったのでまとめておく.
通常のテクスチャの表示方法については,wgld.orgを参考にする.

まず,現状ではDDSのテクスチャを読み込むには,WebGLのコンテキストからgetExtension()を利用して,拡張機能を取得して利用する必要がある.
// var gl; // WebGLのレンダリングコンテキスト

// Chromeでの拡張機能の取得
var ct = gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
// FireFoxでの拡張機能の取得
// var ct = gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc');
ブラウザによって取得のために渡す文字列が違うが,取得した後は同じように扱える.このctは,compressedTexImage2Dに渡すフォーマットに関する定数を持ったオブジェクトになっている.
今のところ,次の4つの定数が含まれている.

  • COMPRESSED_RGBA_S3TC_DXT1_EXT
  • COMPRESSED_RGBA_S3TC_DXT3_EXT
  • COMPRESSED_RGBA_S3TC_DXT5_EXT
  • COMPRESSED_RGB_S3TC_DXT1_EXT

テクスチャにデータを渡す準備ができたら,テクスチャのデータをXMLHttpRequestを利用して読み込む.
var XHR = new XMLHttpRequest();
XHR.open('GET', テクスチャのパス);

XHR.addEventListener('load', onLoad, false);

// ロード結果のフォーマットの指定
XHR.responseType = 'arraybuffer';

// 実際の読み込み
XHR.send();

これで,読み込み終わったタイミングでonLoadが呼び出されるので,DDSのテクスチャの解析を行う.

DDSファイルは,Microsoftのドキュメントにあるように,4文字を32bit整数にまとめたFOURCCと,その後に続くヘッダから始まる.

まず,FOURCCと32bit整数を相互に変換する機能が必要なので,関数を作っておく.
// FCCを32bit符号付き整数に変換する
function FourCCToInt32(value){
    return (value.charCodeAt(0) << 0) +
        (value.charCodeAt(1) << 8) +
        (value.charCodeAt(2) << 16) +
        (value.charCodeAt(3) << 24);
}

// 32bit符号付き整数をFCCに変換する
function Int32ToFourCC(value){
    return String.fromCharCode(
        (value >> 0) & 0xff,
        (value >> 8) & 0xff,
        (value >> 16) & 0xff,
        (value >> 24) & 0xff
    );
}


XHRの読み込み結果からマジックナンバーおよびヘッダ(31個の32bit整数で出来ている)を読み込み,ヘッダから必要な情報を取り出し,compressedTexImage2Dに渡す.
function onLoad()
{
    var header = new Int32Array(XHR.response, 0, 32);

    // マジックナンバーのチェック
    if(header[0] !== FourCCToInt32('DDS ')){
        return ;
    }

        // ブロックのバイトサイズ
    var blockBytes;
    // フォーマット
    var format;

    // DDS_PIXELFORMATのdwFourCCを確認し,1ブロックのサイズと
    // フォーマットを確認する
    var FOURCC_DXT1 = FourCCToInt32('DXT1');
    var FOURCC_DXT3 = FourCCToInt32('DXT3');
    var FOURCC_DXT5 = FourCCToInt32('DXT5');
    switch(header[21]){
    case FOURCC_DXT1:
        blockBytes = 8;
        format = ct.COMPRESSED_RGBA_S3TC_DXT1_EXT;
        break;
    case FOURCC_DXT3:
        blockBytes = 16;
        format = ct.COMPRESSED_RGBA_S3TC_DXT3_EXT;
        break;
    case FOURCC_DXT5:
        blockBytes = 16;
        format = ct.COMPRESSED_RGBA_S3TC_DXT5_EXT;
        break;
    }

    // DDS_HEADERのdwHeightを取得 (マジックナンバー分に注意)
    var height = header[3];

    // DDS_HEADERのdwWidthを取得 (マジックナンバー分に注意)
    var width  = header[4];

    // DDS_HEADERのdwSizeを取得し,マジックナンバー分を足して,テクスチャデータの
    // 先頭を求める
    var offset = header[1] + 4;

    // 今のテクスチャのバイトサイズを求める
    var length = Math.max(4, width) / 4 * Math.max(4, height) / 4 * blockBytes;

    // バッファを生成する
    var buffer = new Uint8Array(XHR.response, offset, length);

    // 圧縮テクスチャを渡す
    gl.compressedTexImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, buffer);

    // 本来はミップマップのために,サイズを変えながら残りのデータを読み込むが省略

    // テクスチャのパラメータを設定
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

    // テクスチャを設定して有効化
    gl.uniform1i(gl.getUniformLocation(program, 'texture'), texture);
    gl.activeTexture(gl.TEXTURE0);

    // 実際の描画
    draw();
}

実際に動作するサンプルを用意してみた.とりあえず適当に丸を書いただけのテクスチャなのでおもしろみは無い.

サンプル

他の拡張機能についても,そのうち調べてみよう.

0 件のコメント:

コメントを投稿