JavaScript Advent Calendar/WebGLコース9日目・テクスチャーの使い方

JavaScript Advent Calendar 2011 WebGL駅伝9日目、独走2日目になります。今からでも参加したい方がいましたらぜひ教えてください。

さて、今日は昨日の内容を発展させて、画像を貼ってみたいと思います。

WebGLのテクスチャーとして使える画像は、通常は辺の長さが2の累乗である必要があります。ただし、やり方によってはどんなサイズの画像でも使えます

今回は256x256で用意したこの画像を表示してみます。ただ表示するだけではおもしろくないので、色を反転させてみます。

Learning WebGLLesson 5に似た内容です。

シェーダー

今回用意したシェーダーはこんな感じです。昨日と比べてみてください。

<script type="text/x-vertex-shader" id="vs">
  // 頂点シェーダー
  attribute vec2 aPosition;
  attribute vec2 aTexCoord;
  varying vec2 vTexCoord;

  void main() {
    gl_Position = vec4(aPosition, 0.0, 1.0);
    vTexCoord = aTexCoord;
  }
</script>
<script type="text/x-fragment-shader" id="fs">
  // フラグメントシェーダー
  precision mediump float;

  varying vec2 vTexCoord;
  uniform sampler2D uSampler;

  void main() {
    gl_FragColor = vec4(1.0 - texture2D(uSampler, vTexCoord).rgb, 1.0);
  }
</script>

今日の新しいところは、uniform sampler2D uSampler;texture2D(uSampler, vTexCoord)です。

sampler2Dというのはテクスチャーの型だと思ってください。実態は整数で、テクスチャーの入ってるレジスタ番号ですが。

そのテクスチャーのある座標の色を取得するのがtexture2Dという組み込み関数です。座標はvec2で、各要素が0から1の範囲です。0から255(元画像のサイズ)ではありません。

texture2Dはvec4を返します。rgbでその最初の3つの要素を取ってきて、1.0から引くことで反転しています。GLSLはfloatとvec3の加減算がvec3になるなど、直感的にベクトル演算が書けます。要素へのアクセスは.xとか.rとか.xyとか.rgbとか書くと思った通りのものが得られます。

頂点オブジェクトを準備

シェーダープログラムをコンパイルするところまでは昨日と同じなので省略して、頂点オブジェクトを考えてみます。

verticesの各座標がcoordsの各座標に対応します。つまり、(-1,-1)の点はテクスチャーの(0,0)の点の色を使うという具合です。

  // init vertex buffer
  var vertices = [
    -1, -1,
     1, -1,
    -1,  1,
     1,  1,
  ];
  var vertBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  var coords = [
     0, 0,
     1, 0,
     0, 1,
     1, 1,
  ];
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coords), gl.STATIC_DRAW);

テクスチャーの準備

いよいよテクスチャーを作ります。

  // init texture
  var img = document.getElementById('jimmy');
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  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);

img要素をDOMから取得して、texImage2Dでセットしています。

img要素は下のようにドキュメントから切り離されていても構いません。ただし、texImage2Dを呼ぶときは画像がロード済みでないといけません。

var img = new Image();
var texture = gl.createTexture();
img.onload = function(){
  ...
}
img.src = 'foo.jpg';

texParameteriとかのことは7日目の記事を参照してください。

gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);WebGLだけのAPIで、テクスチャーの座標を上下反転させて左下を(0,0)、右上を(1,1)とするためのものです。これをしないときは左上が(0,0)、右下が(1,1)になります。

描画

いつもどおりです。

  // draw
  gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
  gl.vertexAttribPointer(aPositionLocation, 2, gl.FLOAT, false, 0, 0);

  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.vertexAttribPointer(aTexCoordLocation, 2, gl.FLOAT, false, 0, 0);

  gl.activeTexture(gl.TEXTURE0); // ←ここの数字と
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.uniform1i(uSamplerLocation, 0); // ←ここの数字をあわせる

  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

activeTextureは、これからn番目のレジスターについて行うテクスチャー関連の操作を行いますという意味です。ここでは0番目のレジスターに先ほど作ったテクスチャーを割り当て、uSamplerが0番目を指すようにしています。この3行はいつもセットで使います。

結果

元の画像と並べてみました。


テクスチャーの貼り付け方が分かったと思います。

ところで、画像ロードを非同期に行う場合、drawArrayをする時点でtexImage2Dがまだ呼ばれてなくてもエラーになることはありません。その時はGLSLのtexture2D関数が(0,0,0,1)を返すだけです。なので、とりあえずテクスチャー以外のもの(基本色とか)を描画しまって、アニメーションしていくうちに徐々にロードされたテクスチャーが表示されるようにしたほうが、ユーザーを待たせないで済みます。

明日は応用編ということで、画像処理みたいなことをやってみたいと思います。