Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2011-12-07

JavaScript Advent Calendar/WebGLコース7日目・WebGLとOpenGLの相違点

23:32

JavaScript Advent Calendar 2011 WebGL駅伝7日目です。


WebGLのwikiにこういうドキュメントがあったので翻訳してみたいと思います。

WebGLとOpenGLの相違点

WebGLはOpenGL ES 2.0の仕様に基いていて、モバイル機器へのポータビリティを最大化するようにOpenGL ESのセマンティックスを残しています。しかし、OpenGL ES 2.0とデスクトップ用OpenGLの似たAPIには大きな違いがあるものもります。

2の累乗でない(Non-Power of Two; NPOT)テクスチャーのサポート

デスクトップ用OpenGL 2.0以降ではNPOTなテクスチャーがサポートされていますが、OpenGL ES 2.0やWebGLでは限定的にしかサポートされていません。この制限は仕様(PDF)のセクション3.8.2("Shader Execution")とセクション3.7.11("Mipmap Generation")に書かれています。要点は次のとおりです。

  • generateMipmap(target)は、現在のテクスチャーがバインドされているレベル0画像の幅と高さがNPOTである場合、INVALID_OPERATIONエラーを出す。
  • NPOTなテクスチャーをサンプリングすると、以下の場合を除き、RGBA(0,0,0,1)を返す。
    • minification filterがNEARESTまたはLINEARにセットされている。(すなわちmipmapフィルターだと(0,0,0,1))
    • repeat modeがCLAMP_TO_EDGEにセットされている。(リピートするNPOTテクスチャーはサポートされていない)

もしREPEAT wrap modeを必要とせず、mipmapが無くても構わないなら、WebGLTextureオブジェクトを作るときに次のようにすればいいでしょう。

var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

しかし、もしREPEAT wrap modeを使う必要があるなら、DOM APIを使って画像を次の2の累乗にリサイズするのは簡単なことです。次の例がそれです。imageはロード済みの(onloadが既に呼ばれている)画像です。

function createTextureFromImage(image) {
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    if (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height)) {
        // Scale up the texture to the next highest power of two dimensions.
        var canvas = document.createElement("canvas");
        canvas.width = nextHighestPowerOfTwo(image.width);
        canvas.height = nextHighestPowerOfTwo(image.height);
        var ctx = canvas.getContext("2d");
        ctx.drawImage(image,
                      0, 0, image.width, image.height,
                      0, 0, canvas.width, canvas.height);
        image = canvas;
    }
    gl.texImage2D(gl.TEXTURE_2D, 0, image);
    gl.generateMipmap(gl.TEXTURE_2D);
    gl.bindTexture(gl.TEXTURE_2D, null);
    return texture;
}
 
function isPowerOfTwo(x) {
    return (x & (x - 1)) == 0;
}
 
function nextHighestPowerOfTwo(x) {
    --x;
    for (var i = 1; i < 32; i <<= 1) {
        x = x | x >> i;
    }
    return x + 1;
}

もちろんサーバー側で画像をリサイズすることもできます。

0番目の頂点アトリビュート

デスクトップ用のOpenGLでは、0番目の頂点アトリビュートは特別な意味を持ちます。第一に、0番目の頂点アトリビュートは配列として定義されていなければいけません。そうでなければ何も描画されません。第二に、0番目の頂点アトリビュートはpersistent stateを持ちません。すなわち、glGetVertexAttribfv(0, GL_CURRENT_VERTEX_ATTRIB, ...)はエラーになります。

OpenGL ES 2.0では0番目の頂点アトリビュートは何も特別な意味を持ちません。

WegGLはOpenGL ES 2.0の慣習に従うため、すべての頂点アトリビュートは同じように扱えます。このためデスクトップのOpenGL実装はエミュレーションが必要になってきます。しかし、このコストは一貫性のある挙動に比べて十分に小さいと判断されました。

倍精度浮動小数点数のサポートはありません

OpenGL ES 2.0は頂点アトリビュートにおいてもテクスチャーデータにおいてもGL_DOUBLE型をサポートしません。つまりFloat64ArrayはWebGLにおいては現在のところ意味を成さないということです。

3Dテクスチャーのサポートはありません

OpenGL ES 2.0は3Dテクスチャーをサポートしません

texture2DLod

後ろにLodのついた関数(texture2DLodなど)は頂点シェーダーでしか使えません。


感想

前に↓を書いたときは一辺が2の累乗の「正方形」じゃないといけないと思ってましたが、長方形もいいみたいです。それから、サイズが大きすぎるとメモリを圧迫するかと思って「辺の長さを超えない最大の2の累乗」に縮小しましたが、上のコードでは「辺の長さを超える最小の2の累乗」に拡大しています。どっちでもいいと思いますが、拡大するほうが情報のロスがなくていいかもしれません。


0番目の頂点アトリビュートが特殊というのは例を見ないとどういうことなのかわかりかねますね。


ここに挙げられてるのはあくまで「似てるけど違う点」であって、その他にもOpenGLで検索してWebGLでも同じ手を使おうと思ったらできなかったということはけっこうあります。GLSLのgl_***という組み込み変数もかなり限られています。GLSL ESの仕様(PDF)の1.1 Change Historyというところを見ると例えば

  • The output variables gl_ClipVertex and gl_FragDepth are removed.

というふうに。

それからパラメーターの設定ではWebGLだけに定義されているものがあります。

    /* WebGL-specific enums */
    const GLenum UNPACK_FLIP_Y_WEBGL            = 0x9240;
    const GLenum UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
    const GLenum CONTEXT_LOST_WEBGL             = 0x9242;
    const GLenum UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
    const GLenum BROWSER_DEFAULT_WEBGL          = 0x9244;

全部は調べてませんが、UNPACK_FLIP_Y_WEBGLというのは使ったことがありました。こんなふうに書くと

  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);

テクスチャーのy座標のゼロを下にしてくれるというものです。まあ無いなら無いで計算で反転させてもいいんですけどね。


そんなところです。

 |